From 684bbe918653cd7cedb12fbd897c422ec245fccc Mon Sep 17 00:00:00 2001 From: Domagoj Kriskovic Date: Tue, 3 Mar 2026 13:35:01 +0100 Subject: [PATCH] loadPyodide per run, use package types GitOrigin-RevId: 0c2384ce676fde09459fbd8244c9ed675b30c954 --- .../editor/python/pyodide.worker.ts | 141 ++++++------------ 1 file changed, 48 insertions(+), 93 deletions(-) diff --git a/services/web/frontend/js/features/ide-react/components/editor/python/pyodide.worker.ts b/services/web/frontend/js/features/ide-react/components/editor/python/pyodide.worker.ts index 3569399e9a..14d0e65ba0 100644 --- a/services/web/frontend/js/features/ide-react/components/editor/python/pyodide.worker.ts +++ b/services/web/frontend/js/features/ide-react/components/editor/python/pyodide.worker.ts @@ -1,59 +1,18 @@ import path from 'path-browserify' +import type { PyodideInterface } from 'pyodide' import type { ProjectFileData, PyodideWorkerRequest, RunCodeRequest, } from './pyodide-worker-messages' -type PyodideRuntimeModule = { - loadPyodide: (options: { - indexURL: string - packageBaseUrl?: string - env?: Record - packages?: string[] - }) => Promise -} - -type PyodideInstance = { - FS: unknown - runPythonAsync: (code: string) => Promise - setStdout: (options: { batched: (line: string) => void }) => void - setStderr: (options: { batched: (line: string) => void }) => void -} - -type PyodideFs = { - analyzePath: (filePath: string) => { exists: boolean } - mkdir: (filePath: string) => void - writeFile: ( - filePath: string, - content: string | ArrayBuffer | Uint8Array - ) => void - chdir: (filePath: string) => void -} +type PyodideFS = PyodideInterface['FS'] +type PyodideModule = typeof import('pyodide') const PROJECT_FS_ROOT = '/project' const PYODIDE_INDEX_PATH = 'js/libs/pyodide/' -function getPyodideIndexUrl(baseAssetPath: string): string { - return new URL(PYODIDE_INDEX_PATH, baseAssetPath).toString() -} - -function toRuntimeProjectPath(relativePath: string): string { - return path.posix.join(PROJECT_FS_ROOT, path.posix.normalize(relativePath)) -} - -function ensureProjectRootExists(fs: PyodideFs) { - try { - const projectRootAnalysis = fs.analyzePath(PROJECT_FS_ROOT) - if (!projectRootAnalysis.exists) { - fs.mkdir(PROJECT_FS_ROOT) - } - } catch { - fs.mkdir(PROJECT_FS_ROOT) - } -} - -function ensureDirectoryExists(fs: PyodideFs, filePath: string) { +function ensureDirectoryExists(fs: PyodideFS, filePath: string) { const directory = path.dirname(filePath) if (directory === '.' || directory === '/') { return @@ -73,11 +32,12 @@ function ensureDirectoryExists(fs: PyodideFs, filePath: string) { } } -function syncProjectFiles(fs: PyodideFs, files: ProjectFileData[]) { - ensureProjectRootExists(fs) - +function syncProjectFiles(fs: PyodideFS, files: ProjectFileData[]) { for (const file of files) { - const runtimePath = toRuntimeProjectPath(file.relativePath) + const runtimePath = path.posix.join( + PROJECT_FS_ROOT, + path.posix.normalize(file.relativePath) + ) ensureDirectoryExists(fs, runtimePath) fs.writeFile(runtimePath, file.content) } @@ -85,18 +45,15 @@ function syncProjectFiles(fs: PyodideFs, files: ProjectFileData[]) { fs.chdir(PROJECT_FS_ROOT) } -let pyodideInstance: PyodideInstance | null = null -let activeRunRequestId: string | null = null +let pyodideModule: PyodideModule | null = null -async function loadPyodideModule( - pyodideIndexUrl: string -): Promise { +async function loadPyodideModule(pyodideIndexUrl: string) { const runtimeModuleUrl = `${pyodideIndexUrl}pyodide.mjs` try { return (await import( /* webpackIgnore: true */ runtimeModuleUrl - )) as PyodideRuntimeModule + )) as PyodideModule } catch (loadError) { const loadErrorMessage = loadError instanceof Error ? loadError.message : String(loadError) @@ -107,38 +64,13 @@ async function loadPyodideModule( } async function handleInit(msg: { baseAssetPath: string }) { - const pyodideIndexUrl = getPyodideIndexUrl(msg.baseAssetPath) + const pyodideIndexUrl = new URL( + PYODIDE_INDEX_PATH, + msg.baseAssetPath + ).toString() try { - const pyodideModule = await loadPyodideModule(pyodideIndexUrl) - const instance = await pyodideModule.loadPyodide({ - indexURL: pyodideIndexUrl, - packageBaseUrl: pyodideIndexUrl, - env: { MPLBACKEND: 'Agg' }, - }) - - instance.setStdout({ - batched: (line: string) => { - self.postMessage({ - type: 'output-line', - stream: 'stdout', - line, - requestId: activeRunRequestId ?? undefined, - }) - }, - }) - instance.setStderr({ - batched: (line: string) => { - self.postMessage({ - type: 'output-line', - stream: 'stderr', - line, - requestId: activeRunRequestId ?? undefined, - }) - }, - }) - - pyodideInstance = instance + pyodideModule = await loadPyodideModule(pyodideIndexUrl) self.postMessage({ type: 'loaded' }) } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) @@ -151,7 +83,7 @@ async function handleInit(msg: { baseAssetPath: string }) { } async function handleRunCode(msg: RunCodeRequest) { - if (!pyodideInstance) { + if (!pyodideModule) { const error = 'Pyodide is not initialized' self.postMessage({ type: 'output-line', @@ -166,33 +98,56 @@ async function handleRunCode(msg: RunCodeRequest) { return } - activeRunRequestId = msg.id + const instance = await pyodideModule.loadPyodide({ + env: { MPLBACKEND: 'Agg' }, + }) + + instance.setStdout({ + batched: (line: string) => { + self.postMessage({ + type: 'output-line', + stream: 'stdout', + line, + requestId: msg.id, + }) + }, + }) + instance.setStderr({ + batched: (line: string) => { + self.postMessage({ + type: 'output-line', + stream: 'stderr', + line, + requestId: msg.id, + }) + }, + }) + try { if (msg.files.length > 0) { - const fs = pyodideInstance.FS as PyodideFs + const fs = instance.FS syncProjectFiles(fs, msg.files) } - const result = await pyodideInstance.runPythonAsync(msg.code) + const result = await instance.runPythonAsync(msg.code) if (result !== undefined) { self.postMessage({ type: 'output-line', stream: 'stdout', line: String(result), - requestId: activeRunRequestId ?? undefined, + requestId: msg.id, }) } } catch (runError) { const errorMessage = runError instanceof Error ? runError.message : String(runError) + self.postMessage({ type: 'output-line', stream: 'stderr', line: errorMessage, - requestId: activeRunRequestId ?? undefined, + requestId: msg.id, }) - } finally { - activeRunRequestId = null } self.postMessage({