feat(C-03):代码在线运行+课程练一节
新增CodeRunner组件(JS worker沙箱+Python pyodide)与/utility/code-runner独立页;CourseLearn新增练习Tab(首代码块自动提取装载,无示例可直接练习)。
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* C-03 Pyodide 懒加载运行器(浏览器内 WASM,无服务端执行)。
|
||||
* 安全约束:
|
||||
* - 默认断网:不 loadPackage、不拉取额外包;只运行解释器自带能力。
|
||||
* - 超时由调用方(CodeRunner.tsx)经 Promise.race + runId 丢弃过期结果保证;
|
||||
* Pyodide 本体不支持强制 terminate,超时后输出"执行超时"并忽略迟到结果。
|
||||
*/
|
||||
|
||||
export const PYODIDE_VERSION = "0.26.4";
|
||||
export const PYODIDE_CDN = `https://cdn.jsdelivr.net/pyodide/v${PYODIDE_VERSION}/full/`;
|
||||
|
||||
type PyodideRuntime = {
|
||||
runPython: (code: string) => unknown;
|
||||
setStdout: (opts: { batched: (s: string) => void }) => void;
|
||||
setStderr: (opts: { batched: (s: string) => void }) => void;
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__pyodidePromise?: Promise<PyodideRuntime> | null;
|
||||
}
|
||||
}
|
||||
|
||||
function loadPyodideScript(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (document.querySelector('script[data-pyodide="1"]')) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const s = document.createElement("script");
|
||||
s.dataset.pyodide = "1";
|
||||
s.src = `${PYODIDE_CDN}pyodide.js`;
|
||||
s.onload = () => resolve();
|
||||
s.onerror = () => reject(new Error("Pyodide 运行时加载失败,请检查网络后重试"));
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
}
|
||||
|
||||
async function bootPyodide(
|
||||
onStdout: (s: string) => void,
|
||||
onStderr: (s: string) => void,
|
||||
): Promise<PyodideRuntime> {
|
||||
await loadPyodideScript();
|
||||
const factory = (window as unknown as { loadPyodide?: (opts: { indexURL: string }) => Promise<PyodideRuntime> }).loadPyodide;
|
||||
if (!factory) throw new Error("Pyodide 运行时初始化失败");
|
||||
const py = await factory({ indexURL: PYODIDE_CDN });
|
||||
py.setStdout({ batched: onStdout });
|
||||
py.setStderr({ batched: onStderr });
|
||||
return py;
|
||||
}
|
||||
|
||||
export function getPyodide(
|
||||
onStdout: (s: string) => void,
|
||||
onStderr: (s: string) => void,
|
||||
): Promise<PyodideRuntime> {
|
||||
if (!window.__pyodidePromise) {
|
||||
window.__pyodidePromise = bootPyodide(onStdout, onStderr).catch((e) => {
|
||||
window.__pyodidePromise = null;
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
return window.__pyodidePromise;
|
||||
}
|
||||
Reference in New Issue
Block a user