mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-03-13 01:00:33 -04:00
82 lines
2.4 KiB
TypeScript
82 lines
2.4 KiB
TypeScript
// We have to specify pyodide.js because we need to import that file (not .mjs)
|
|
// and 'import' defaults to .mjs
|
|
import { loadPyodide, type PyodideInterface } from 'pyodide/pyodide.js';
|
|
import pkg from 'pyodide/package.json';
|
|
|
|
const ctx: Worker & typeof globalThis = self as unknown as Worker &
|
|
typeof globalThis;
|
|
|
|
let pyodide: PyodideInterface;
|
|
|
|
interface PythonRunEvent extends MessageEvent {
|
|
data: {
|
|
code: {
|
|
contents: string;
|
|
editableContents: string;
|
|
original: { [id: string]: string };
|
|
};
|
|
};
|
|
}
|
|
|
|
async function setupPyodide() {
|
|
if (pyodide) return pyodide;
|
|
|
|
pyodide = await loadPyodide({
|
|
// TODO: host this ourselves
|
|
indexURL: `https://cdn.jsdelivr.net/pyodide/v${pkg.version}/full/`
|
|
});
|
|
|
|
// We freeze this to prevent learners from getting the worker into a
|
|
// weird state. NOTE: this has to come after pyodide is loaded, because
|
|
// pyodide modifies self while loading.
|
|
Object.freeze(self);
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
const str = pyodide.globals.get('str') as (x: unknown) => string;
|
|
|
|
function print(...args: unknown[]) {
|
|
const text = args.map(x => str(x)).join(' ');
|
|
postMessage({ type: 'print', text });
|
|
}
|
|
|
|
function input(text: string) {
|
|
// TODO: send unique ids to the main thread and the service worker, so we
|
|
// can have multiple concurrent input requests.
|
|
postMessage({ type: 'input', text });
|
|
const request = new XMLHttpRequest();
|
|
request.open('POST', '/python/intercept-input/', false);
|
|
request.send(null);
|
|
|
|
return request.responseText;
|
|
}
|
|
|
|
// I tried setting jsglobals here, to provide 'input' and 'print' to python,
|
|
// without having to modify the global window object. However, it didn't work
|
|
// because pyodide needs access to that object. Instead, I used
|
|
// registerJsModule when setting up runPython.
|
|
|
|
// Make print available to python
|
|
pyodide.registerJsModule('jscustom', {
|
|
print,
|
|
input
|
|
});
|
|
// TODO: use a fresh global object for each runPython call if we stop terminating
|
|
// the worker when the user input changes. (See python-test-evaluator.ts)
|
|
pyodide.runPython(`
|
|
import jscustom
|
|
from jscustom import print
|
|
from jscustom import input
|
|
`);
|
|
|
|
return pyodide;
|
|
}
|
|
|
|
void setupPyodide();
|
|
|
|
ctx.onmessage = async (e: PythonRunEvent) => {
|
|
const code = (e.data.code.contents || '').slice();
|
|
const pyodide = await setupPyodide();
|
|
// use pyodide.runPythonAsync if we want top-level await
|
|
pyodide.runPython(code);
|
|
};
|