Skip to content

Commit 062d6be

Browse files
committed
Make polyscript.js_modules crawable
1 parent 2f83165 commit 062d6be

12 files changed

+177
-114
lines changed

docs/README.md

+20
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,26 @@ In *polyscript*, this is possible by defining one or more `[js_modules.X]` field
264264
* `[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).
265265
* `[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.
266266

267+
All registeed modules can be then imported as such:
268+
269+
```python
270+
# just import js_modules and reach names after
271+
from polyscript import js_modules
272+
js_modules.my_module.util()
273+
274+
# import directly and reach names after
275+
from polyscript.js_modules import my_module
276+
my_module.util()
277+
278+
# import deeply up to the module exports
279+
from polyscript.js_modules.my_module import util
280+
util()
281+
282+
# import default or other fields with aliases
283+
from polyscript.js_modules.other import defalut as fn
284+
fn()
285+
```
286+
267287
### js_modules config example
268288

269289
**TOML**

docs/core.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/core.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

esm/custom.js

+2-2
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 { JSModules, assign, create, createOverload, createResolved, dedent, defineProperty, nodeInfo } from './utils.js';
4+
import { JSModules, assign, create, createOverload, createResolved, dedent, defineProperty, nodeInfo, registerJSModules } 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,7 @@ export const handleCustomType = (node) => {
9999
XWorker,
100100
};
101101

102-
module.registerJSModule(interpreter, 'polyscript.js_modules', JSModules);
102+
registerJSModules(runtime, module, interpreter, JSModules);
103103
module.registerJSModule(interpreter, 'polyscript', {
104104
js_modules: JSModules,
105105
XWorker,

esm/script-handler.js

+2-2
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 { JSModules, all, dispatch, resolve, defineProperty, nodeInfo } from './utils.js';
7+
import { JSModules, all, dispatch, resolve, defineProperty, nodeInfo, registerJSModules } from './utils.js';
88
import { getText } from './fetch-utils.js';
99

1010
const getRoot = (script) => {
@@ -60,7 +60,7 @@ const execute = async (script, source, XWorker, isAsync) => {
6060
configurable: true,
6161
get: () => script,
6262
});
63-
module.registerJSModule(interpreter, 'polyscript.js_modules', JSModules);
63+
registerJSModules(type, module, interpreter, JSModules);
6464
module.registerJSModule(interpreter, 'polyscript', {
6565
js_modules: JSModules,
6666
XWorker,

esm/utils.js

+18
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,24 @@ export const JSModules = new Proxy(jsModules, {
7272
ownKeys: map => [...map.keys()],
7373
});
7474

75+
const has = (_, field) => !field.startsWith('_');
76+
77+
const proxy = (modules, name) => new Proxy(
78+
modules,
79+
{ has, get: (modules, field) => modules[name][field] }
80+
);
81+
82+
export const registerJSModules = (type, module, interpreter, modules) => {
83+
// Pyodide resolves JS modules magically
84+
if (type === 'pyodide') return;
85+
86+
// other runtimes need this pretty ugly dance (it works though)
87+
const jsModules = 'polyscript.js_modules';
88+
for (const name of Reflect.ownKeys(modules))
89+
module.registerJSModule(interpreter, `${jsModules}.${name}`, proxy(modules, name));
90+
module.registerJSModule(interpreter, jsModules, modules);
91+
};
92+
7593
export const importJS = (source, name) => import(source).then(esm => {
7694
jsModules.set(name, { ...esm });
7795
});

esm/worker/_template.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import * as JSON from '@ungap/structured-clone/json';
88
import coincident from 'coincident/window';
99

10-
import { assign, create, createFunction, createOverload, createResolved, dispatch } from '../utils.js';
10+
import { assign, create, createFunction, createOverload, createResolved, dispatch, registerJSModules } from '../utils.js';
1111
import createJSModules from './js_modules.js';
1212
import { configs, registry } from '../interpreters.js';
1313
import { getRuntime, getRuntimeID } from '../loader.js';
@@ -140,7 +140,7 @@ add('message', ({ data: { options, config: baseURL, code, hooks } }) => {
140140

141141
let target = '';
142142

143-
details.registerJSModule(interpreter, 'polyscript.js_modules', JSModules);
143+
registerJSModules(type, details, interpreter, JSModules);
144144
details.registerJSModule(interpreter, 'polyscript', {
145145
xworker,
146146
js_modules: JSModules,

esm/worker/js_modules.js

+35-35
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,43 @@
1-
import { absoluteURL, defineProperties, defineProperty, entries, isCSS, js_modules } from '../utils.js';
1+
import { absoluteURL, entries, isArray, isCSS, js_modules } from '../utils.js';
22
import { base } from '../interpreter/_utils.js';
33

4+
const has = (modules, name) => modules.has(name);
5+
6+
const ownKeys = modules => [...modules.keys()];
7+
8+
const proxy = (modules, window, sync, baseURL) => new Proxy(modules, {
9+
has,
10+
ownKeys,
11+
get: (modules, name) => {
12+
let value = modules.get(name);
13+
if (isArray(value)) {
14+
let sources = value;
15+
value = null;
16+
for (let source of sources) {
17+
source = absoluteURL(source, baseURL);
18+
if (isCSS(source)) sync.importCSS(source);
19+
else {
20+
sync.importJS(source, name);
21+
value = window[js_modules].get(name);
22+
}
23+
}
24+
modules.set(name, value);
25+
}
26+
return value;
27+
},
28+
});
29+
430
export default (window, sync, mainModules) => {
5-
const JSModules = {};
6-
const descriptors = {};
7-
const known = new Set;
8-
const ops = new Map;
9-
for (const [name, value] of globalThis[js_modules]) {
10-
known.add(name);
11-
descriptors[name] = { value };
12-
}
13-
// define lazy main modules resolution
31+
let modules = globalThis[js_modules], baseURL = '';
1432
if (mainModules) {
33+
baseURL = base.get(mainModules);
1534
for (let [source, module] of entries(mainModules)) {
16-
// ignore modules already defined in worker
17-
if (known.has(module)) continue;
18-
let sources = ops.get(module);
19-
if (!sources) ops.set(module, (sources = []));
20-
sources.push(source);
21-
}
22-
for (const [name, sources] of ops) {
23-
descriptors[name] = {
24-
configurable: true,
25-
get() {
26-
let value;
27-
for (let source of sources) {
28-
source = absoluteURL(source, base.get(mainModules));
29-
if (isCSS(source)) sync.importCSS(source);
30-
else {
31-
sync.importJS(source, name);
32-
value = window[js_modules].get(name);
33-
}
34-
}
35-
// override the getter and make it no more configurable
36-
defineProperty(JSModules, name, { configurable: false, get: () => value });
37-
return value;
38-
}
39-
};
35+
let value = modules.get(module);
36+
if (!value || isArray(value)) {
37+
modules.set(module, value || (value = []));
38+
value.push(source);
39+
}
4040
}
4141
}
42-
return defineProperties(JSModules, descriptors);
42+
return proxy(modules, window, sync, baseURL);
4343
};

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,6 @@
8383
"sticky-module": "^0.1.1"
8484
},
8585
"worker": {
86-
"blob": "sha256-gFg6QJJiDmvnxzhq0SoEdF6pDhzuRcvFEbZVZRxImpU="
86+
"blob": "sha256-XS1OjXlDepikiPW23EqHwHDRYBPrT4+6/ILHLhu8E6c="
8787
}
8888
}

test/leaflet.html

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
6+
<title>Polyscript JS Modules</title>
7+
<style>#main-map, #worker-map { height: 320px; } h3 { margin-bottom: 0; }</style>
8+
<link rel="stylesheet" href="style.css">
9+
<script type="module" src="../core.js"></script>
10+
</head>
11+
<body>
12+
<!--
13+
main only: npx static-handler .
14+
main/worker: npx static-handler --coop --coep .
15+
16+
Note: --corp breaks the tiles server
17+
-->
18+
19+
<h3>Main</h3>
20+
<div id="main-map"></div>
21+
<script type="pyodide" config="./modules.toml">
22+
# needed to fix pyodide proxies
23+
from pyodide.ffi import to_js
24+
25+
from polyscript import js_modules
26+
from polyscript.js_modules import leaflet as L
27+
28+
print(js_modules.random_js.default)
29+
30+
center = to_js([51.505, -0.09])
31+
mark = to_js([51.5, -0.09])
32+
map = L.map('main-map').setView(center, 13)
33+
34+
L.tileLayer(
35+
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
36+
to_js({"attribution": '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'})
37+
).addTo(map)
38+
39+
L.marker(mark).addTo(map).bindPopup('Modules on Main are a no brainer 👍').openPopup()
40+
</script>
41+
42+
<hr>
43+
44+
<h3>Worker</h3>
45+
<div id="worker-map"></div>
46+
<script type="pyodide" config="./modules.toml" worker>
47+
from polyscript.js_modules import random_js, leaflet as L
48+
49+
print(random_js.default)
50+
51+
center = [51.505, -0.09]
52+
mark = [51.5, -0.09]
53+
map = L.map('worker-map').setView(center, 13)
54+
55+
L.tileLayer(
56+
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
57+
{"attribution": '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}
58+
).addTo(map)
59+
60+
L.marker(mark).addTo(map).bindPopup('Modules on Worker are awesome 🥳').openPopup()
61+
</script>
62+
</body>
63+
</html>

test/modules-mpy.html

-35
This file was deleted.

test/modules.html

+31-34
Original file line numberDiff line numberDiff line change
@@ -5,59 +5,56 @@
55
<meta name="viewport" content="width=device-width,initial-scale=1.0">
66
<title>Polyscript JS Modules</title>
77
<style>#main-map, #worker-map { height: 320px; } h3 { margin-bottom: 0; }</style>
8-
<link rel="stylesheet" href="style.css">
98
<script type="module" src="../core.js"></script>
109
</head>
1110
<body>
12-
<!--
13-
main only: npx static-handler .
14-
main/worker: npx static-handler --coop --coep .
15-
16-
Note: --corp breaks the tiles server
17-
-->
18-
1911
<h3>Main</h3>
20-
<div id="main-map"></div>
21-
<script type="pyodide" config="./modules.toml">
22-
# needed to fix pyodide proxies
23-
from pyodide.ffi import to_js
12+
<script type="micropython" config="./modules.toml">
13+
from polyscript.js_modules import random_js
14+
print(random_js.default)
2415

2516
from polyscript import js_modules
26-
from polyscript.js_modules import leaflet as L
27-
2817
print(js_modules.random_js.default)
2918

30-
center = to_js([51.505, -0.09])
31-
mark = to_js([51.5, -0.09])
32-
map = L.map('main-map').setView(center, 13)
19+
from polyscript.js_modules.random_js import default as value
20+
print(value)
21+
</script>
22+
23+
<script type="pyodide" config="./modules.toml">
24+
from polyscript.js_modules import random_js
25+
print(random_js.default)
3326

34-
L.tileLayer(
35-
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
36-
to_js({"attribution": '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'})
37-
).addTo(map)
27+
from polyscript import js_modules
28+
print(js_modules.random_js.default)
3829

39-
L.marker(mark).addTo(map).bindPopup('Modules on Main are a no brainer 👍').openPopup()
30+
from polyscript.js_modules.random_js import default as value
31+
print(value)
4032
</script>
4133

4234
<hr>
4335

4436
<h3>Worker</h3>
45-
<div id="worker-map"></div>
46-
<script type="pyodide" config="./modules.toml" worker>
47-
from polyscript.js_modules import random_js, leaflet as L
48-
37+
<script type="micropython" config="./modules.toml" worker>
38+
from polyscript.js_modules import random_js
4939
print(random_js.default)
5040

51-
center = [51.505, -0.09]
52-
mark = [51.5, -0.09]
53-
map = L.map('worker-map').setView(center, 13)
41+
from polyscript import js_modules
42+
print(js_modules.random_js.default)
5443

55-
L.tileLayer(
56-
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
57-
{"attribution": '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}
58-
).addTo(map)
44+
from polyscript.js_modules.random_js import default as value
45+
print(value)
46+
</script>
5947

60-
L.marker(mark).addTo(map).bindPopup('Modules on Worker are awesome 🥳').openPopup()
48+
<script type="pyodide" config="./modules.toml" worker>
49+
from polyscript.js_modules import random_js
50+
print(random_js.default)
51+
52+
from polyscript import js_modules
53+
print(js_modules.random_js.default)
54+
55+
from polyscript.js_modules.random_js import default as value
56+
print(value)
6157
</script>
58+
6259
</body>
6360
</html>

0 commit comments

Comments
 (0)