@pyscript/core 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -2
- package/cjs/custom/pyscript.js +5 -9
- package/cjs/custom-types.js +0 -2
- package/cjs/custom.js +6 -6
- package/cjs/interpreter/_python.js +4 -2
- package/cjs/interpreter/micropython.js +10 -12
- package/cjs/interpreter/webr.js +48 -0
- package/cjs/plugins.js +89 -0
- package/cjs/runtime/_python.js +34 -0
- package/cjs/runtime/_utils.js +141 -0
- package/cjs/runtime/micropython.js +40 -0
- package/cjs/runtime/pyodide.js +40 -0
- package/cjs/runtime/ruby.js +50 -0
- package/cjs/runtime/wasmoon.js +46 -0
- package/cjs/runtimes.js +63 -0
- package/cjs/worker/class.js +11 -2
- package/cjs/worker/hooks.js +11 -3
- package/cjs/worker/xworker.js +1 -1
- package/core.js +2 -2
- package/docs/README.md +481 -0
- package/esm/custom/pyscript.js +5 -9
- package/esm/custom.js +6 -6
- package/esm/interpreter/_python.js +4 -2
- package/esm/interpreter/micropython.js +10 -12
- package/esm/worker/class.js +11 -2
- package/esm/worker/hooks.js +11 -2
- package/esm/worker/xworker.js +1 -1
- package/package.json +12 -4
- package/pyscript.js +2 -2
- package/types/coincident/window.d.ts +4 -4
- package/types/custom.d.ts +54 -0
- package/types/index.d.ts +1 -1
- package/types/interpreter/_python.d.ts +7 -0
- package/types/interpreter/_utils.d.ts +11 -0
- package/types/interpreter/micropython.d.ts +18 -0
- package/types/interpreter/pyodide.d.ts +19 -0
- package/types/interpreter/ruby-wasm-wasi.d.ts +15 -0
- package/types/interpreter/wasmoon.d.ts +21 -0
- package/types/interpreters.d.ts +9 -0
- package/types/listeners.d.ts +2 -0
- package/types/pyscript/pyscript.core/esm/custom.d.ts +3 -3
- package/types/pyscript/pyscript.core/esm/interpreter/_python.d.ts +2 -2
- package/types/pyscript/pyscript.core/esm/interpreter/micropython.d.ts +1 -2
- package/types/pyscript/pyscript.core/esm/interpreter/webr.d.ts +14 -0
- package/types/pyscript/pyscript.core/esm/plugins.d.ts +21 -14
- package/types/pyscript/pyscript.core/esm/worker/hooks.d.ts +4 -1
- package/types/script-handler.d.ts +2 -1
- package/types/worker/class.d.ts +4 -4
- package/types/worker/hooks.d.ts +6 -2
package/README.md
CHANGED
@@ -4,9 +4,13 @@
|
|
4
4
|
|
5
5
|
---
|
6
6
|
|
7
|
+
## Documentation
|
8
|
+
|
9
|
+
Please read [the documentation page](./docs/README.md) to know all the user-facing details around this module.
|
10
|
+
|
7
11
|
## Development
|
8
12
|
|
9
|
-
The working folder (source code of truth) is the `./esm` one, while the `./cjs` is populated as dual module and to test (but it's 1:1 code, no
|
13
|
+
The working folder (source code of truth) is the `./esm` one, while the `./cjs` is populated as dual module and to test (but it's 1:1 code, no transpilation except for imports/exports).
|
10
14
|
|
11
15
|
```sh
|
12
16
|
# install all dependencies needed by core
|
@@ -19,7 +23,7 @@ This project requires some automatic artifact creation to:
|
|
19
23
|
|
20
24
|
* create a _Worker_ as a _Blob_ based on the same code used by this repo
|
21
25
|
* create automatically the list of runtimes available via the module
|
22
|
-
* create the `core.js` file used by most integration tests
|
26
|
+
* create the `core.js` or the `pyscript.js` file used by most integration tests
|
23
27
|
* create a sha256 version of the Blob content for CSP cases
|
24
28
|
|
25
29
|
Accordingly, to build latest project:
|
@@ -31,3 +35,11 @@ npm run build
|
|
31
35
|
# optionally spin a server with CORS, COOP, and COEP enabled
|
32
36
|
npm run server
|
33
37
|
```
|
38
|
+
|
39
|
+
If **no minification** is desired or helpful while debugging potential issues, please use `NO_MIN=1` in front of the _build_ step:
|
40
|
+
|
41
|
+
```sh
|
42
|
+
NO_MIN=1 npm run build
|
43
|
+
|
44
|
+
npm run server
|
45
|
+
```
|
package/cjs/custom/pyscript.js
CHANGED
@@ -91,14 +91,10 @@ document.head.appendChild(document.createElement("style")).textContent = `
|
|
91
91
|
env: "py-script",
|
92
92
|
interpreter: "pyodide",
|
93
93
|
codeBeforeRunWorker() {
|
94
|
-
|
95
|
-
const prefix = 'print("codeBeforeRunWorker")';
|
96
|
-
return [prefix].concat(...set).join("\n");
|
94
|
+
return [...hooks.codeBeforeRunWorker].join("\n");
|
97
95
|
},
|
98
96
|
codeAfterRunWorker() {
|
99
|
-
|
100
|
-
const prefix = 'print("codeAfterRunWorker")';
|
101
|
-
return [prefix].concat(...set).join("\n");
|
97
|
+
return [...hooks.codeAfterRunWorker].join("\n");
|
102
98
|
},
|
103
99
|
onBeforeRun(pyodide, element) {
|
104
100
|
bootstrapNodeAndPlugins(pyodide, element, before, "onBeforeRun");
|
@@ -119,10 +115,10 @@ document.head.appendChild(document.createElement("style")).textContent = `
|
|
119
115
|
onAfterRunAsync(pyodide, element) {
|
120
116
|
bootstrapNodeAndPlugins(pyodide, element, after, "onAfterRunAsync");
|
121
117
|
},
|
122
|
-
async
|
118
|
+
async onInterpreterReady(pyodide, element) {
|
123
119
|
// allows plugins to do whatever they want with the element
|
124
120
|
// before regular stuff happens in here
|
125
|
-
for (const callback of hooks.
|
121
|
+
for (const callback of hooks.onInterpreterReady)
|
126
122
|
callback(pyodide, element);
|
127
123
|
if (isScript(element)) {
|
128
124
|
const {
|
@@ -183,7 +179,7 @@ const hooks = {
|
|
183
179
|
/** @type {Set<function>} */
|
184
180
|
onAfterRunAsync: new Set(),
|
185
181
|
/** @type {Set<function>} */
|
186
|
-
|
182
|
+
onInterpreterReady: new Set(),
|
187
183
|
|
188
184
|
/** @type {Set<string>} */
|
189
185
|
codeBeforeRunWorker: new Set(),
|
package/cjs/custom-types.js
CHANGED
package/cjs/custom.js
CHANGED
@@ -48,7 +48,7 @@ const handleCustomType = (node) => {
|
|
48
48
|
version,
|
49
49
|
config,
|
50
50
|
env,
|
51
|
-
|
51
|
+
onInterpreterReady,
|
52
52
|
} = options;
|
53
53
|
const name = getRuntimeID(runtime, version);
|
54
54
|
const id = env || `${name}${config ? `|${config}` : ""}`;
|
@@ -69,7 +69,7 @@ const handleCustomType = (node) => {
|
|
69
69
|
onAfterRunAsync,
|
70
70
|
} = options;
|
71
71
|
|
72
|
-
const hooks = new Hook(options);
|
72
|
+
const hooks = new Hook(interpreter, options);
|
73
73
|
|
74
74
|
const XWorker = function XWorker(...args) {
|
75
75
|
return Worker.apply(hooks, args);
|
@@ -123,7 +123,7 @@ const handleCustomType = (node) => {
|
|
123
123
|
|
124
124
|
resolve(resolved);
|
125
125
|
|
126
|
-
|
126
|
+
onInterpreterReady?.(resolved, node);
|
127
127
|
});
|
128
128
|
}
|
129
129
|
}
|
@@ -137,17 +137,17 @@ exports.handleCustomType = handleCustomType;
|
|
137
137
|
const registry = new Map();
|
138
138
|
|
139
139
|
/**
|
140
|
-
* @typedef {Object}
|
140
|
+
* @typedef {Object} CustomOptions custom configuration
|
141
141
|
* @prop {'pyodide' | 'micropython' | 'wasmoon' | 'ruby-wasm-wasi'} interpreter the interpreter to use
|
142
142
|
* @prop {string} [version] the optional interpreter version to use
|
143
143
|
* @prop {string} [config] the optional config to use within such interpreter
|
144
|
-
* @prop {(environment: object, node: Element) => void} [
|
144
|
+
* @prop {(environment: object, node: Element) => void} [onInterpreterReady] the callback that will be invoked once
|
145
145
|
*/
|
146
146
|
|
147
147
|
/**
|
148
148
|
* Allows custom types and components on the page to receive interpreters to execute any code
|
149
149
|
* @param {string} type the unique `<script type="...">` identifier
|
150
|
-
* @param {
|
150
|
+
* @param {CustomOptions} options the custom type configuration
|
151
151
|
*/
|
152
152
|
const define = (type, options) => {
|
153
153
|
if (defaultRegistry.has(type) || registry.has(type))
|
@@ -10,12 +10,14 @@ const runAsync = (interpreter, code) =>
|
|
10
10
|
interpreter.runPythonAsync(clean(code));
|
11
11
|
exports.runAsync = runAsync;
|
12
12
|
|
13
|
-
const setGlobal = (interpreter, name, value) =>
|
13
|
+
const setGlobal = (interpreter, name, value) => {
|
14
14
|
interpreter.globals.set(name, value);
|
15
|
+
};
|
15
16
|
exports.setGlobal = setGlobal;
|
16
17
|
|
17
|
-
const deleteGlobal = (interpreter, name) =>
|
18
|
+
const deleteGlobal = (interpreter, name) => {
|
18
19
|
interpreter.globals.delete(name);
|
20
|
+
};
|
19
21
|
exports.deleteGlobal = deleteGlobal;
|
20
22
|
|
21
23
|
const writeFile = ({ FS }, path, buffer) =>
|
@@ -1,12 +1,6 @@
|
|
1
1
|
'use strict';
|
2
2
|
const { fetchPaths, stdio } = require("./_utils.js");
|
3
|
-
const {
|
4
|
-
run,
|
5
|
-
runAsync,
|
6
|
-
setGlobal,
|
7
|
-
deleteGlobal,
|
8
|
-
writeFile
|
9
|
-
} = require("./_python.js");
|
3
|
+
const { run, setGlobal, deleteGlobal, writeFile } = require("./_python.js");
|
10
4
|
|
11
5
|
const type = "micropython";
|
12
6
|
|
@@ -14,19 +8,23 @@ const type = "micropython";
|
|
14
8
|
/* c8 ignore start */
|
15
9
|
module.exports = {
|
16
10
|
type,
|
17
|
-
module: (version = "1.20.0-
|
11
|
+
module: (version = "1.20.0-268") =>
|
18
12
|
`https://cdn.jsdelivr.net/npm/@micropython/micropython-webassembly-pyscript@${version}/micropython.mjs`,
|
19
13
|
async engine({ loadMicroPython }, config, url) {
|
20
14
|
const { stderr, stdout, get } = stdio();
|
21
15
|
url = url.replace(/\.m?js$/, ".wasm");
|
22
|
-
const
|
23
|
-
if (config.fetch) await fetchPaths(this,
|
24
|
-
return
|
16
|
+
const interpreter = await get(loadMicroPython({ stderr, stdout, url }));
|
17
|
+
if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
|
18
|
+
return interpreter;
|
25
19
|
},
|
26
20
|
setGlobal,
|
27
21
|
deleteGlobal,
|
28
22
|
run,
|
29
|
-
|
23
|
+
// TODO: MicroPython doesn't have a Pyodide like top-level await,
|
24
|
+
// this method should still not throw errors once invoked
|
25
|
+
async runAsync(...args) {
|
26
|
+
return this.run(...args);
|
27
|
+
},
|
30
28
|
writeFile,
|
31
29
|
};
|
32
30
|
/* c8 ignore stop */
|
@@ -0,0 +1,48 @@
|
|
1
|
+
'use strict';
|
2
|
+
const { fetchPaths, stdio, writeFile } = require("./_utils.js");
|
3
|
+
|
4
|
+
const type = "webr";
|
5
|
+
|
6
|
+
const io = new WeakMap;
|
7
|
+
|
8
|
+
// REQUIRES INTEGRATION TEST
|
9
|
+
/* c8 ignore start */
|
10
|
+
module.exports = {
|
11
|
+
type,
|
12
|
+
module: (version = "0.1.1") =>
|
13
|
+
`https://webr.r-wasm.org/v${version}/webr.mjs`,
|
14
|
+
async engine({ WebR }, config) {
|
15
|
+
const { stderr, stdout, get } = stdio();
|
16
|
+
const webR = new WebR();
|
17
|
+
await webR.init();
|
18
|
+
const interpreter = await get(new webR.Shelter());
|
19
|
+
io.set(interpreter, { webR, stderr, stdout });
|
20
|
+
if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
|
21
|
+
return interpreter;
|
22
|
+
},
|
23
|
+
setGlobal() {
|
24
|
+
// UNSUPPORTED
|
25
|
+
// const { webR } = io.get(interpreter);
|
26
|
+
// return webR.objs.globalEnv.bind(name, value);
|
27
|
+
},
|
28
|
+
deleteGlobal() {
|
29
|
+
// UNSUPPORTED
|
30
|
+
// const { webR } = io.get(interpreter);
|
31
|
+
// return webR.objs.globalEnv.bind(name, void 0);
|
32
|
+
},
|
33
|
+
run(interpreter, code) {
|
34
|
+
return this.runAsync(interpreter, code);
|
35
|
+
},
|
36
|
+
async runAsync(interpreter, code) {
|
37
|
+
const ioHandler = io.get(interpreter);
|
38
|
+
const { output, result } = await interpreter.captureR(code);
|
39
|
+
for (const { type, data } of output)
|
40
|
+
ioHandler[type](data);
|
41
|
+
return result;
|
42
|
+
},
|
43
|
+
writeFile: (interpreter, path, buffer) => {
|
44
|
+
const { webR } = io.get(interpreter);
|
45
|
+
return writeFile(webR.FS, path, buffer);
|
46
|
+
},
|
47
|
+
};
|
48
|
+
/* c8 ignore stop */
|
package/cjs/plugins.js
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
'use strict';
|
2
|
+
const { $$ } = require("basic-devtools");
|
3
|
+
|
4
|
+
const { getDetails } = require("./script-handler.js");
|
5
|
+
const { registry, configs } = require("./runtimes.js");
|
6
|
+
const { getRuntimeID } = require("./loader.js");
|
7
|
+
const { io } = require("./runtime/_utils.js");
|
8
|
+
|
9
|
+
const PLUGINS_SELECTORS = [];
|
10
|
+
exports.PLUGINS_SELECTORS = PLUGINS_SELECTORS;
|
11
|
+
|
12
|
+
/**
|
13
|
+
* @typedef {Object} Runtime plugin configuration
|
14
|
+
* @prop {string} type the runtime type
|
15
|
+
* @prop {object} runtime the bootstrapped runtime
|
16
|
+
* @prop {(url:string, options?: object) => Worker} XWorker an XWorker constructor that defaults to same runtime on the Worker.
|
17
|
+
* @prop {object} config a cloned config used to bootstrap the runtime
|
18
|
+
* @prop {(code:string) => any} run an utility to run code within the runtime
|
19
|
+
* @prop {(code:string) => Promise<any>} runAsync an utility to run code asynchronously within the runtime
|
20
|
+
* @prop {(path:string, data:ArrayBuffer) => void} writeFile an utility to write a file in the virtual FS, if available
|
21
|
+
*/
|
22
|
+
|
23
|
+
// REQUIRES INTEGRATION TEST
|
24
|
+
/* c8 ignore start */
|
25
|
+
/**
|
26
|
+
* @param {Element} node any DOM element registered via plugin.
|
27
|
+
*/
|
28
|
+
const handlePlugin = (node) => {
|
29
|
+
for (const name of PLUGINS_SELECTORS) {
|
30
|
+
if (node.matches(name)) {
|
31
|
+
const { options, known } = plugins.get(name);
|
32
|
+
if (!known.has(node)) {
|
33
|
+
known.add(node);
|
34
|
+
const { type, version, config, env, onRuntimeReady } = options;
|
35
|
+
const name = getRuntimeID(type, version);
|
36
|
+
const id = env || `${name}${config ? `|${config}` : ""}`;
|
37
|
+
const { runtime: engine, XWorker } = getDetails(
|
38
|
+
type,
|
39
|
+
id,
|
40
|
+
name,
|
41
|
+
version,
|
42
|
+
config,
|
43
|
+
);
|
44
|
+
engine.then((runtime) => {
|
45
|
+
const module = registry.get(type);
|
46
|
+
onRuntimeReady(node, {
|
47
|
+
type,
|
48
|
+
runtime,
|
49
|
+
XWorker,
|
50
|
+
io: io.get(runtime),
|
51
|
+
config: structuredClone(configs.get(name)),
|
52
|
+
run: module.run.bind(module, runtime),
|
53
|
+
runAsync: module.runAsync.bind(module, runtime),
|
54
|
+
});
|
55
|
+
});
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
};
|
60
|
+
exports.handlePlugin = handlePlugin;
|
61
|
+
|
62
|
+
/**
|
63
|
+
* @type {Map<string, {options:object, known:WeakSet<Element>}>}
|
64
|
+
*/
|
65
|
+
const plugins = new Map();
|
66
|
+
|
67
|
+
/**
|
68
|
+
* @typedef {Object} PluginOptions plugin configuration
|
69
|
+
* @prop {string} type the runtime/interpreter type to receive
|
70
|
+
* @prop {string} [version] the optional runtime version to use
|
71
|
+
* @prop {string} [config] the optional config to use within such runtime
|
72
|
+
* @prop {string} [env] the optional environment to use
|
73
|
+
* @prop {(node: Element, runtime: Runtime) => void} onRuntimeReady the callback that will be invoked once
|
74
|
+
*/
|
75
|
+
|
76
|
+
/**
|
77
|
+
* Allows plugins and components on the page to receive runtimes to execute any code.
|
78
|
+
* @param {string} name the unique plugin name
|
79
|
+
* @param {PluginOptions} options the plugin configuration
|
80
|
+
*/
|
81
|
+
const registerPlugin = (name, options) => {
|
82
|
+
if (PLUGINS_SELECTORS.includes(name))
|
83
|
+
throw new Error(`plugin ${name} already registered`);
|
84
|
+
PLUGINS_SELECTORS.push(name);
|
85
|
+
plugins.set(name, { options, known: new WeakSet() });
|
86
|
+
$$(name).forEach(handlePlugin);
|
87
|
+
};
|
88
|
+
exports.registerPlugin = registerPlugin;
|
89
|
+
/* c8 ignore stop */
|
@@ -0,0 +1,34 @@
|
|
1
|
+
'use strict';
|
2
|
+
const { clean, writeFile: writeFileUtil } = require("./_utils.js");
|
3
|
+
|
4
|
+
// REQUIRES INTEGRATION TEST
|
5
|
+
/* c8 ignore start */
|
6
|
+
const run = (runtime, code) => runtime.runPython(clean(code));
|
7
|
+
exports.run = run;
|
8
|
+
|
9
|
+
const runAsync = (runtime, code) => runtime.runPythonAsync(clean(code));
|
10
|
+
exports.runAsync = runAsync;
|
11
|
+
|
12
|
+
function runEvent(runtime, code, key) {
|
13
|
+
code = `import js;event=js.__events.get(${key});${code}`;
|
14
|
+
return this.run(runtime, code);
|
15
|
+
}
|
16
|
+
exports.runEvent = runEvent
|
17
|
+
|
18
|
+
const worker = (method) =>
|
19
|
+
function (runtime, code, xworker) {
|
20
|
+
code = `from js import xworker;${code}`;
|
21
|
+
globalThis.xworker = xworker;
|
22
|
+
return this[method](runtime, code);
|
23
|
+
};
|
24
|
+
|
25
|
+
const runWorker = worker("run");
|
26
|
+
exports.runWorker = runWorker;
|
27
|
+
|
28
|
+
const runWorkerAsync = worker("runAsync");
|
29
|
+
exports.runWorkerAsync = runWorkerAsync;
|
30
|
+
|
31
|
+
const writeFile = ({ FS }, path, buffer) =>
|
32
|
+
writeFileUtil(FS, path, buffer);
|
33
|
+
exports.writeFile = writeFile;
|
34
|
+
/* c8 ignore stop */
|
@@ -0,0 +1,141 @@
|
|
1
|
+
'use strict';
|
2
|
+
require("@ungap/with-resolvers");
|
3
|
+
|
4
|
+
const { getBuffer } = require("../fetch-utils.js");
|
5
|
+
const { absoluteURL } = require("../utils.js");
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Trim code only if it's a single line that prettier or other tools might have modified.
|
9
|
+
* @param {string} code code that might be a single line
|
10
|
+
* @returns {string}
|
11
|
+
*/
|
12
|
+
const clean = (code) =>
|
13
|
+
code.replace(/^[^\r\n]+$/, (line) => line.trim());
|
14
|
+
exports.clean = clean;
|
15
|
+
|
16
|
+
// REQUIRES INTEGRATION TEST
|
17
|
+
/* c8 ignore start */
|
18
|
+
const io = new WeakMap();
|
19
|
+
exports.io = io;
|
20
|
+
const stdio = (init) => {
|
21
|
+
const context = init || console;
|
22
|
+
const localIO = {
|
23
|
+
stderr: (context.stderr || console.error).bind(context),
|
24
|
+
stdout: (context.stdout || console.log).bind(context),
|
25
|
+
};
|
26
|
+
return {
|
27
|
+
stderr: (...args) => localIO.stderr(...args),
|
28
|
+
stdout: (...args) => localIO.stdout(...args),
|
29
|
+
async get(engine) {
|
30
|
+
const runtime = await engine;
|
31
|
+
io.set(runtime, localIO);
|
32
|
+
return runtime;
|
33
|
+
},
|
34
|
+
};
|
35
|
+
};
|
36
|
+
exports.stdio = stdio;
|
37
|
+
/* c8 ignore stop */
|
38
|
+
|
39
|
+
// This should be the only helper needed for all Emscripten based FS exports
|
40
|
+
const writeFile = (FS, path, buffer) => {
|
41
|
+
const { parentPath, name } = FS.analyzePath(path, true);
|
42
|
+
FS.mkdirTree(parentPath);
|
43
|
+
return FS.writeFile([parentPath, name].join("/"), new Uint8Array(buffer), {
|
44
|
+
canOwn: true,
|
45
|
+
});
|
46
|
+
};
|
47
|
+
exports.writeFile = writeFile;
|
48
|
+
|
49
|
+
// This is instead a fallback for Lua or others
|
50
|
+
const writeFileShim = (FS, path, buffer) => {
|
51
|
+
path = resolve(FS, path);
|
52
|
+
mkdirTree(FS, dirname(path));
|
53
|
+
return FS.writeFile(path, new Uint8Array(buffer), { canOwn: true });
|
54
|
+
};
|
55
|
+
exports.writeFileShim = writeFileShim;
|
56
|
+
|
57
|
+
const dirname = (path) => {
|
58
|
+
const tree = path.split("/");
|
59
|
+
tree.pop();
|
60
|
+
return tree.join("/");
|
61
|
+
};
|
62
|
+
|
63
|
+
const mkdirTree = (FS, path) => {
|
64
|
+
const current = [];
|
65
|
+
for (const branch of path.split("/")) {
|
66
|
+
current.push(branch);
|
67
|
+
if (branch) FS.mkdir(current.join("/"));
|
68
|
+
}
|
69
|
+
};
|
70
|
+
|
71
|
+
const resolve = (FS, path) => {
|
72
|
+
const tree = [];
|
73
|
+
for (const branch of path.split("/")) {
|
74
|
+
switch (branch) {
|
75
|
+
case "":
|
76
|
+
break;
|
77
|
+
case ".":
|
78
|
+
break;
|
79
|
+
case "..":
|
80
|
+
tree.pop();
|
81
|
+
break;
|
82
|
+
default:
|
83
|
+
tree.push(branch);
|
84
|
+
}
|
85
|
+
}
|
86
|
+
return [FS.cwd()].concat(tree).join("/").replace(/^\/+/, "/");
|
87
|
+
};
|
88
|
+
|
89
|
+
const { all, isArray } = require("../utils.js");
|
90
|
+
|
91
|
+
const calculateFetchPaths = (config_fetch) => {
|
92
|
+
// REQUIRES INTEGRATION TEST
|
93
|
+
/* c8 ignore start */
|
94
|
+
for (const { files, to_file, from = "" } of config_fetch) {
|
95
|
+
if (files !== undefined && to_file !== undefined)
|
96
|
+
throw new Error(
|
97
|
+
`Cannot use 'to_file' and 'files' parameters together!`,
|
98
|
+
);
|
99
|
+
if (files === undefined && to_file === undefined && from.endsWith("/"))
|
100
|
+
throw new Error(
|
101
|
+
`Couldn't determine the filename from the path ${from}, please supply 'to_file' parameter.`,
|
102
|
+
);
|
103
|
+
}
|
104
|
+
/* c8 ignore stop */
|
105
|
+
return config_fetch.flatMap(
|
106
|
+
({ from = "", to_folder = ".", to_file, files }) => {
|
107
|
+
if (isArray(files))
|
108
|
+
return files.map((file) => ({
|
109
|
+
url: joinPaths([from, file]),
|
110
|
+
path: joinPaths([to_folder, file]),
|
111
|
+
}));
|
112
|
+
const filename = to_file || from.slice(1 + from.lastIndexOf("/"));
|
113
|
+
return [{ url: from, path: joinPaths([to_folder, filename]) }];
|
114
|
+
},
|
115
|
+
);
|
116
|
+
};
|
117
|
+
|
118
|
+
const joinPaths = (parts) => {
|
119
|
+
const res = parts
|
120
|
+
.map((part) => part.trim().replace(/(^[/]*|[/]*$)/g, ""))
|
121
|
+
.filter((p) => p !== "" && p !== ".")
|
122
|
+
.join("/");
|
123
|
+
|
124
|
+
return parts[0].startsWith("/") ? `/${res}` : res;
|
125
|
+
};
|
126
|
+
|
127
|
+
const fetchResolved = (config_fetch, url) =>
|
128
|
+
fetch(absoluteURL(url, base.get(config_fetch)));
|
129
|
+
|
130
|
+
const base = new WeakMap();
|
131
|
+
exports.base = base;
|
132
|
+
|
133
|
+
const fetchPaths = (module, runtime, config_fetch) =>
|
134
|
+
all(
|
135
|
+
calculateFetchPaths(config_fetch).map(({ url, path }) =>
|
136
|
+
fetchResolved(config_fetch, url)
|
137
|
+
.then(getBuffer)
|
138
|
+
.then((buffer) => module.writeFile(runtime, path, buffer)),
|
139
|
+
),
|
140
|
+
);
|
141
|
+
exports.fetchPaths = fetchPaths;
|
@@ -0,0 +1,40 @@
|
|
1
|
+
'use strict';
|
2
|
+
const { fetchPaths, stdio } = require("./_utils.js");
|
3
|
+
const {
|
4
|
+
run,
|
5
|
+
runAsync,
|
6
|
+
runEvent,
|
7
|
+
runWorker,
|
8
|
+
runWorkerAsync,
|
9
|
+
writeFile
|
10
|
+
} = require("./_python.js");
|
11
|
+
|
12
|
+
const type = "micropython";
|
13
|
+
|
14
|
+
let patchPromise = true;
|
15
|
+
|
16
|
+
// REQUIRES INTEGRATION TEST
|
17
|
+
/* c8 ignore start */
|
18
|
+
module.exports = {
|
19
|
+
type: [type, "mpy"],
|
20
|
+
module: () => `http://localhost:8080/micropython/micropython.mjs`,
|
21
|
+
async engine({ loadMicroPython }, config, url) {
|
22
|
+
// @bug https://github.com/micropython/micropython/issues/11749
|
23
|
+
if (patchPromise) {
|
24
|
+
patchPromise = false;
|
25
|
+
globalThis.Promise = class extends Promise {};
|
26
|
+
}
|
27
|
+
const { stderr, stdout, get } = stdio();
|
28
|
+
url = url.replace(/\.m?js$/, ".wasm");
|
29
|
+
const runtime = await get(loadMicroPython({ stderr, stdout, url }));
|
30
|
+
if (config.fetch) await fetchPaths(this, runtime, config.fetch);
|
31
|
+
return runtime;
|
32
|
+
},
|
33
|
+
run,
|
34
|
+
runAsync,
|
35
|
+
runEvent,
|
36
|
+
runWorker,
|
37
|
+
runWorkerAsync,
|
38
|
+
writeFile,
|
39
|
+
};
|
40
|
+
/* c8 ignore stop */
|
@@ -0,0 +1,40 @@
|
|
1
|
+
'use strict';
|
2
|
+
const { fetchPaths, stdio } = require("./_utils.js");
|
3
|
+
const {
|
4
|
+
run,
|
5
|
+
runAsync,
|
6
|
+
runEvent,
|
7
|
+
runWorker,
|
8
|
+
runWorkerAsync,
|
9
|
+
writeFile
|
10
|
+
} = require("./_python.js");
|
11
|
+
|
12
|
+
const type = "pyodide";
|
13
|
+
|
14
|
+
// REQUIRES INTEGRATION TEST
|
15
|
+
/* c8 ignore start */
|
16
|
+
module.exports = {
|
17
|
+
type: [type, "py"],
|
18
|
+
module: (version = "0.23.2") =>
|
19
|
+
`https://cdn.jsdelivr.net/pyodide/v${version}/full/pyodide.mjs`,
|
20
|
+
async engine({ loadPyodide }, config, url) {
|
21
|
+
const { stderr, stdout, get } = stdio();
|
22
|
+
const indexURL = url.slice(0, url.lastIndexOf("/"));
|
23
|
+
const runtime = await get(loadPyodide({ stderr, stdout, indexURL }));
|
24
|
+
if (config.fetch) await fetchPaths(this, runtime, config.fetch);
|
25
|
+
if (config.packages) {
|
26
|
+
await runtime.loadPackage("micropip");
|
27
|
+
const micropip = await runtime.pyimport("micropip");
|
28
|
+
await micropip.install(config.packages);
|
29
|
+
micropip.destroy();
|
30
|
+
}
|
31
|
+
return runtime;
|
32
|
+
},
|
33
|
+
run,
|
34
|
+
runAsync,
|
35
|
+
runEvent,
|
36
|
+
runWorker,
|
37
|
+
runWorkerAsync,
|
38
|
+
writeFile,
|
39
|
+
};
|
40
|
+
/* c8 ignore stop */
|
@@ -0,0 +1,50 @@
|
|
1
|
+
'use strict';
|
2
|
+
const { clean, fetchPaths } = require("./_utils.js");
|
3
|
+
|
4
|
+
const type = "ruby";
|
5
|
+
|
6
|
+
// MISSING:
|
7
|
+
// * there is no VFS apparently or I couldn't reach any
|
8
|
+
// * I've no idea how to override the stderr and stdout
|
9
|
+
// * I've no idea how to import packages
|
10
|
+
|
11
|
+
// REQUIRES INTEGRATION TEST
|
12
|
+
/* c8 ignore start */
|
13
|
+
const worker = (method) =>
|
14
|
+
function (runtime, code, xworker) {
|
15
|
+
globalThis.xworker = xworker;
|
16
|
+
return this[method](
|
17
|
+
runtime,
|
18
|
+
`require "js";xworker=JS::eval("return xworker");${code}`,
|
19
|
+
);
|
20
|
+
};
|
21
|
+
|
22
|
+
module.exports = {
|
23
|
+
experimental: true,
|
24
|
+
type: [type, "rb"],
|
25
|
+
module: (version = "2.0.0") =>
|
26
|
+
`https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@${version}/dist/browser.esm.js`,
|
27
|
+
async engine({ DefaultRubyVM }, config, url) {
|
28
|
+
const response = await fetch(
|
29
|
+
`${url.slice(0, url.lastIndexOf("/"))}/ruby.wasm`,
|
30
|
+
);
|
31
|
+
const module = await WebAssembly.compile(await response.arrayBuffer());
|
32
|
+
const { vm: runtime } = await DefaultRubyVM(module);
|
33
|
+
if (config.fetch) await fetchPaths(this, runtime, config.fetch);
|
34
|
+
return runtime;
|
35
|
+
},
|
36
|
+
run: (runtime, code) => runtime.eval(clean(code)),
|
37
|
+
runAsync: (runtime, code) => runtime.evalAsync(clean(code)),
|
38
|
+
runEvent(runtime, code, key) {
|
39
|
+
return this.run(
|
40
|
+
runtime,
|
41
|
+
`require "js";event=JS::eval("return __events.get(${key})");${code}`,
|
42
|
+
);
|
43
|
+
},
|
44
|
+
runWorker: worker("run"),
|
45
|
+
runWorkerAsync: worker("runAsync"),
|
46
|
+
writeFile: () => {
|
47
|
+
throw new Error(`writeFile is not supported in ${type}`);
|
48
|
+
},
|
49
|
+
};
|
50
|
+
/* c8 ignore stop */
|