|
@@ -1,59 +1,18 @@
|
|
|
import path from 'path-browserify'
|
|
import path from 'path-browserify'
|
|
|
|
|
+import type { PyodideInterface } from 'pyodide'
|
|
|
import type {
|
|
import type {
|
|
|
ProjectFileData,
|
|
ProjectFileData,
|
|
|
PyodideWorkerRequest,
|
|
PyodideWorkerRequest,
|
|
|
RunCodeRequest,
|
|
RunCodeRequest,
|
|
|
} from './pyodide-worker-messages'
|
|
} from './pyodide-worker-messages'
|
|
|
|
|
|
|
|
-type PyodideRuntimeModule = {
|
|
|
|
|
- loadPyodide: (options: {
|
|
|
|
|
- indexURL: string
|
|
|
|
|
- packageBaseUrl?: string
|
|
|
|
|
- env?: Record<string, string>
|
|
|
|
|
- packages?: string[]
|
|
|
|
|
- }) => Promise<PyodideInstance>
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-type PyodideInstance = {
|
|
|
|
|
- FS: unknown
|
|
|
|
|
- runPythonAsync: (code: string) => Promise<unknown>
|
|
|
|
|
- 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 PROJECT_FS_ROOT = '/project'
|
|
|
const PYODIDE_INDEX_PATH = 'js/libs/pyodide/'
|
|
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)
|
|
const directory = path.dirname(filePath)
|
|
|
if (directory === '.' || directory === '/') {
|
|
if (directory === '.' || directory === '/') {
|
|
|
return
|
|
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) {
|
|
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)
|
|
ensureDirectoryExists(fs, runtimePath)
|
|
|
fs.writeFile(runtimePath, file.content)
|
|
fs.writeFile(runtimePath, file.content)
|
|
|
}
|
|
}
|
|
@@ -85,18 +45,15 @@ function syncProjectFiles(fs: PyodideFs, files: ProjectFileData[]) {
|
|
|
fs.chdir(PROJECT_FS_ROOT)
|
|
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<PyodideRuntimeModule> {
|
|
|
|
|
|
|
+async function loadPyodideModule(pyodideIndexUrl: string) {
|
|
|
const runtimeModuleUrl = `${pyodideIndexUrl}pyodide.mjs`
|
|
const runtimeModuleUrl = `${pyodideIndexUrl}pyodide.mjs`
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
return (await import(
|
|
return (await import(
|
|
|
/* webpackIgnore: true */ runtimeModuleUrl
|
|
/* webpackIgnore: true */ runtimeModuleUrl
|
|
|
- )) as PyodideRuntimeModule
|
|
|
|
|
|
|
+ )) as PyodideModule
|
|
|
} catch (loadError) {
|
|
} catch (loadError) {
|
|
|
const loadErrorMessage =
|
|
const loadErrorMessage =
|
|
|
loadError instanceof Error ? loadError.message : String(loadError)
|
|
loadError instanceof Error ? loadError.message : String(loadError)
|
|
@@ -107,38 +64,13 @@ async function loadPyodideModule(
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function handleInit(msg: { baseAssetPath: string }) {
|
|
async function handleInit(msg: { baseAssetPath: string }) {
|
|
|
- const pyodideIndexUrl = getPyodideIndexUrl(msg.baseAssetPath)
|
|
|
|
|
|
|
+ const pyodideIndexUrl = new URL(
|
|
|
|
|
+ PYODIDE_INDEX_PATH,
|
|
|
|
|
+ msg.baseAssetPath
|
|
|
|
|
+ ).toString()
|
|
|
|
|
|
|
|
try {
|
|
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' })
|
|
self.postMessage({ type: 'loaded' })
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
const errorMessage = error instanceof Error ? error.message : String(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) {
|
|
async function handleRunCode(msg: RunCodeRequest) {
|
|
|
- if (!pyodideInstance) {
|
|
|
|
|
|
|
+ if (!pyodideModule) {
|
|
|
const error = 'Pyodide is not initialized'
|
|
const error = 'Pyodide is not initialized'
|
|
|
self.postMessage({
|
|
self.postMessage({
|
|
|
type: 'output-line',
|
|
type: 'output-line',
|
|
@@ -166,33 +98,56 @@ async function handleRunCode(msg: RunCodeRequest) {
|
|
|
return
|
|
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 {
|
|
try {
|
|
|
if (msg.files.length > 0) {
|
|
if (msg.files.length > 0) {
|
|
|
- const fs = pyodideInstance.FS as PyodideFs
|
|
|
|
|
|
|
+ const fs = instance.FS
|
|
|
syncProjectFiles(fs, msg.files)
|
|
syncProjectFiles(fs, msg.files)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const result = await pyodideInstance.runPythonAsync(msg.code)
|
|
|
|
|
|
|
+ const result = await instance.runPythonAsync(msg.code)
|
|
|
if (result !== undefined) {
|
|
if (result !== undefined) {
|
|
|
self.postMessage({
|
|
self.postMessage({
|
|
|
type: 'output-line',
|
|
type: 'output-line',
|
|
|
stream: 'stdout',
|
|
stream: 'stdout',
|
|
|
line: String(result),
|
|
line: String(result),
|
|
|
- requestId: activeRunRequestId ?? undefined,
|
|
|
|
|
|
|
+ requestId: msg.id,
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
} catch (runError) {
|
|
} catch (runError) {
|
|
|
const errorMessage =
|
|
const errorMessage =
|
|
|
runError instanceof Error ? runError.message : String(runError)
|
|
runError instanceof Error ? runError.message : String(runError)
|
|
|
|
|
+
|
|
|
self.postMessage({
|
|
self.postMessage({
|
|
|
type: 'output-line',
|
|
type: 'output-line',
|
|
|
stream: 'stderr',
|
|
stream: 'stderr',
|
|
|
line: errorMessage,
|
|
line: errorMessage,
|
|
|
- requestId: activeRunRequestId ?? undefined,
|
|
|
|
|
|
|
+ requestId: msg.id,
|
|
|
})
|
|
})
|
|
|
- } finally {
|
|
|
|
|
- activeRunRequestId = null
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
self.postMessage({
|
|
self.postMessage({
|