Skip to content

Commit 6d19d97

Browse files
committed
Added hybrid mode
1 parent 62019ec commit 6d19d97

17 files changed

+288
-180
lines changed

docs/README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,59 @@ Please read the [XWorker](#xworker) dedicated section to know more.
254254
</details>
255255

256256

257+
## Extra `config` features
258+
259+
It is possible to land in either the *main* world or the *worker* one native *JS* modules (aka: *ESM*).
260+
261+
In *polyscript*, this is possible by defining one or more `[js_modules.X]` fields in the config, where `X` is either *main* or *worker*:
262+
263+
* `[js_modules.main]` is a list of *source* -> *module name* pairs, similarly to how `[files]` field work, where the *module* name will then be reachable via `polyscript.js_modules.actual_name` in both *main* and *worker* world. As the *main* module lands on the main thread, where there is also likely some UI, it is also possible to define one or more related *CSS* to that module, as long as they target the very same name (see the example to better understand).
264+
* `[js_modules.worker]` is a list of *source* -> *module name* pairs that actually land only in `<script type="x" worker>` cases. These modules are still reachable through the very same `polyscript.js_modules.actual_name` convention and this feature is meant to be used for modules that only works best, or work regardless, outside the *main* world. As example, if your *JS* module implies that `document` or `window` references, among other *DOM* related APIs, are globally available, it means that that module should be part of the `[js_modules.main]` list instead ... however, if the module works out of the box in a *Worker* environment, it is best for performance reasons to explicitly define such module under this field. Please note that *CSS* files are not accepted within this list because there's no way *CSS* can be useful or land in any meaningful way within a *Worker* environment.
265+
266+
### js_modules config example
267+
268+
```toml
269+
[js_modules.main]
270+
# this modules work best on main
271+
"https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet-src.esm.js" = "leaflet"
272+
"https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet.css" = "leaflet" # CSS
273+
# this works in both main and worker
274+
"https://cdn.jsdelivr.net/npm/html-escaper" = "html_escaper"
275+
276+
[js_modules.worker]
277+
# this works out of the box in a worker too
278+
"https://cdn.jsdelivr.net/npm/html-escaper" = "html_escaper"
279+
# this works only in a worker
280+
"https://cdn.jsdelivr.net/npm/worker-only" = "worker_only"
281+
```
282+
283+
```html
284+
<!-- main case -->
285+
<script type="pyodide" config="./that.toml">
286+
# these both works
287+
from polyscript.js_modules import leaflet as L
288+
from polyscript.js_modules import html_escaper
289+
290+
# this fails as it's not reachable in main
291+
from polyscript.js_modules import worker_only
292+
</script>
293+
294+
<!-- worker case -->
295+
<script type="pyodide" config="./that.toml" worker>
296+
# these works by proxying the main module and landing
297+
# on main only when accessed, never before
298+
# the CSS file also lands automatically on demand
299+
from polyscript.js_modules import leaflet as L
300+
301+
# this works out of the box in the worker
302+
from polyscript.js_modules import html_escaper
303+
304+
# this works only in a worker 👍
305+
from polyscript.js_modules import worker_only
306+
</script>
307+
```
308+
309+
257310
## How Events Work
258311
259312
The event should contain the *interpreter* or *custom type* prefix, followed by the *event* type it'd like to handle.

docs/core.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/core.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

esm/custom.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import '@ungap/with-resolvers';
22
import { $$ } from 'basic-devtools';
33

4-
import { assign, create, createOverload, createResolved, dedent, defineProperty, nodeInfo } from './utils.js';
4+
import { JSModules, assign, create, createOverload, createResolved, dedent, defineProperty, nodeInfo } from './utils.js';
55
import { getDetails } from './script-handler.js';
66
import { registry as defaultRegistry, prefixes, configs } from './interpreters.js';
77
import { getRuntimeID } from './loader.js';
@@ -99,7 +99,10 @@ export const handleCustomType = (node) => {
9999
XWorker,
100100
};
101101

102-
module.registerJSModule(interpreter, 'polyscript', { XWorker });
102+
module.registerJSModule(interpreter, 'polyscript', {
103+
js_modules: JSModules,
104+
XWorker,
105+
});
103106

104107
// patch methods accordingly to hooks (and only if needed)
105108
for (const suffix of ['Run', 'RunAsync']) {

esm/interpreter/_utils.js

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import '@ungap/with-resolvers';
2+
import { UNDEFINED } from 'proxy-target/types';
23

34
import { getBuffer } from '../fetch-utils.js';
4-
import { absoluteURL, all, entries, isArray } from '../utils.js';
5+
import { absoluteURL, all, entries, importCSS, importJS, isArray, isCSS } from '../utils.js';
56

67
// REQUIRES INTEGRATION TEST
78
/* c8 ignore start */
@@ -167,19 +168,20 @@ export const fetchFiles = (module, interpreter, config_files) =>
167168
),
168169
);
169170

170-
export const fetchJSModules = (module, interpreter, js_modules) => {
171-
const modules = [];
172-
for (const [source, name] of entries(js_modules)) {
173-
modules.push(import(source).then(
174-
esm => {
175-
// { ...esm } is needed to avoid dealing w/ module records
176-
module.registerJSModule(interpreter, name, { ...esm });
177-
},
178-
err => {
179-
console.warn(`Unable to register ${name} due ${err}`);
180-
}
181-
));
171+
const RUNNING_IN_WORKER = typeof document === UNDEFINED;
172+
173+
export const fetchJSModules = ({ main, worker }) => {
174+
const promises = [];
175+
if (worker && RUNNING_IN_WORKER) {
176+
for (const [source, name] of entries(worker))
177+
promises.push(importJS(source, name));
178+
}
179+
if (main && !RUNNING_IN_WORKER) {
180+
for (const [source, name] of entries(main)) {
181+
if (isCSS(source)) importCSS(source);
182+
else promises.push(importJS(source, name));
183+
}
182184
}
183-
return all(modules);
185+
return all(promises);
184186
};
185187
/* c8 ignore stop */

esm/interpreter/micropython.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export default {
1515
const interpreter = await get(loadMicroPython({ stderr, stdout, url }));
1616
if (config.files) await fetchFiles(this, interpreter, config.files);
1717
if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
18-
if (config.js_modules) await fetchJSModules(this, interpreter, config.js_modules);
18+
if (config.js_modules) await fetchJSModules(config.js_modules);
1919
return interpreter;
2020
},
2121
registerJSModule,

esm/interpreter/pyodide.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export default {
1818
);
1919
if (config.files) await fetchFiles(this, interpreter, config.files);
2020
if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
21-
if (config.js_modules) await fetchJSModules(this, interpreter, config.js_modules);
21+
if (config.js_modules) await fetchJSModules(config.js_modules);
2222
if (config.packages) {
2323
await interpreter.loadPackage('micropip');
2424
const micropip = await interpreter.pyimport('micropip');

esm/interpreter/ruby-wasm-wasi.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { dedent } from '../utils.js';
2-
import { fetchFiles, fetchPaths } from './_utils.js';
2+
import { fetchFiles, fetchJSModules, fetchPaths } from './_utils.js';
33

44
const type = 'ruby-wasm-wasi';
55
const jsType = type.replace(/\W+/g, '_');
@@ -24,6 +24,7 @@ export default {
2424
const { vm: interpreter } = await DefaultRubyVM(module);
2525
if (config.files) await fetchFiles(this, interpreter, config.files);
2626
if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
27+
if (config.js_modules) await fetchJSModules(config.js_modules);
2728
return interpreter;
2829
},
2930
// Fallback to globally defined module fields (i.e. $xworker)

esm/interpreter/wasmoon.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { dedent } from '../utils.js';
2-
import { fetchFiles, fetchPaths, io, stdio, writeFileShim } from './_utils.js';
2+
import { fetchFiles, fetchJSModules, fetchPaths, io, stdio, writeFileShim } from './_utils.js';
33

44
const type = 'wasmoon';
55

@@ -21,6 +21,7 @@ export default {
2121
});
2222
if (config.files) await fetchFiles(this, interpreter, config.files);
2323
if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
24+
if (config.js_modules) await fetchJSModules(config.js_modules);
2425
return interpreter;
2526
},
2627
// Fallback to globally defined module fields

esm/script-handler.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import $xworker from './worker/class.js';
44
import workerURL from './worker/url.js';
55
import { getRuntime, getRuntimeID } from './loader.js';
66
import { registry } from './interpreters.js';
7-
import { all, dispatch, resolve, defineProperty, nodeInfo } from './utils.js';
7+
import { JSModules, all, dispatch, resolve, defineProperty, nodeInfo } from './utils.js';
88
import { getText } from './fetch-utils.js';
99

1010
const getRoot = (script) => {
@@ -60,7 +60,10 @@ const execute = async (script, source, XWorker, isAsync) => {
6060
configurable: true,
6161
get: () => script,
6262
});
63-
module.registerJSModule(interpreter, 'polyscript', { XWorker });
63+
module.registerJSModule(interpreter, 'polyscript', {
64+
js_modules: JSModules,
65+
XWorker,
66+
});
6467
dispatch(script, type, 'ready');
6568
const result = module[isAsync ? 'runAsync' : 'run'](interpreter, content);
6669
const done = dispatch.bind(null, script, type, 'done');

0 commit comments

Comments
 (0)