Skip to content

Commit 377accc

Browse files
committed
Updating coincident to its latest
1 parent 036b48c commit 377accc

27 files changed

+326
-323
lines changed

cjs/package.json

-1
This file was deleted.

docs/README.md

+13-9
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ We encourage everyone to be careful when using this *core* API as we definitivel
211211
| type | `<script type="micropython">` | Define the *interpreter* to use with this script. Please read the [Terminology](#terminology) **interpreter** dedicated details to know more. |
212212
| version | `<script type="pyodide" version="0.23.2">` | Allow the usage of a specific version where, if numeric, must be available through the project *CDN* used by *core* but if specified as fully qualified *URL*, allows usage of any interpreter's version: `<script type="pyodide" version="http://localhost:8080/pyodide.local.mjs">` |
213213
| worker | `<script type="pyodide" worker="./file.py">` | Bootstraps an *interpreter* **only** within a *worker*, allowing `config` and `version` attributes too, also attaching an `xworker` property/field directly to the *script* tag on the main page. Please note the interpreter will not be available on the main thread when this attribute is used. |
214+
| service-worker | `<script type="pyodide" service-worker="../sw.js" worker>` | Eventually does fallback to a *Service Worker* able to enable also synchronous interactions to grant access to the `xworker.window` proxy. |
214215

215216

216217
### Script Features
@@ -499,12 +500,13 @@ There are **alternative ways** to enable these headers for your site or local ho
499500
500501
Before showing any example, it's important to understand how the offered API differs from Web standard *workers*:
501502
502-
| name | example | behavior |
503-
| :-------- | :------------------------------------------------------- | :--------|
504-
| async | `XWorker('./file.py', async=True)` | The worker code is evaluated via `runAsync` utility where, if the *interpreter* allows it, top level *await* would be possible, among other *PL* specific asynchronous features. |
505-
| config | `XWorker('./file.py', config='./cfg.toml')` | The worker will either use the config object as it is or load and parse its referencing *JSON* or *TOML* file, or syntax, to configure itself. Please see [currently supported config values](https://docs.pyscript.net/latest/reference/elements/py-config.html#supported-configuration-values) as this is currently based on `<py-config>` features. |
506-
| type | `XWorker('./file.py', type='pyodide')` | Define the *interpreter* to use with this worker which is, by default, the same one used within the running code. Please read the [Terminology](#terminology) **interpreter** dedicated details to know more. |
507-
| version | `XWorker('./file.py', type='pyodide', version='0.23.2')` | Allow the usage of a specific version where, if numeric, must be available through the project *CDN* used by *core* but if specified as fully qualified *URL*, allows usage of any interpreter's version: `<script type="pyodide" version="http://localhost:8080/pyodide.local.mjs">` |
503+
| name | example | behavior |
504+
| :------------ | :--------------------------------------------------------------- | :--------|
505+
| async | `XWorker('./file.py', async=True)` | The worker code is evaluated via `runAsync` utility where, if the *interpreter* allows it, top level *await* would be possible, among other *PL* specific asynchronous features. |
506+
| config | `XWorker('./file.py', config='./cfg.toml')` | The worker will either use the config object as it is or load and parse its referencing *JSON* or *TOML* file, or syntax, to configure itself. Please see [currently supported config values](https://docs.pyscript.net/latest/reference/elements/py-config.html#supported-configuration-values) as this is currently based on `<py-config>` features. |
507+
| type | `XWorker('./file.py', type='pyodide')` | Define the *interpreter* to use with this worker which is, by default, the same one used within the running code. Please read the [Terminology](#terminology) **interpreter** dedicated details to know more. |
508+
| version | `XWorker('./file.py', type='pyodide', version='0.23.2')` | Allow the usage of a specific version where, if numeric, must be available through the project *CDN* used by *core* but if specified as fully qualified *URL*, allows usage of any interpreter's version: `<script type="pyodide" version="http://localhost:8080/pyodide.local.mjs">` |
509+
| serviceWorker | `XWorker('./file.py', type='pyodide', serviceWorker='../sw.js')` | When the server cannot enable *SharedArrayBuffer* it is still possible to fallback to a *Service Worker* file based orchestration provided by [mini-coi](https://github.com/WebReflection/mini-coi?tab=readme-ov-file#how-to-use-mini-coi-as-service-worker), [sabayon](https://github.com/WebReflection/sabayon?tab=readme-ov-file#service-worker) or others. Please note that **this string must point to a file in the current server**. |
508510
509511
The returning *JS* reference to any `XWorker(...)` call is literally a `Worker` instance that, among its default API, have the extra following feature:
510512
@@ -544,9 +546,11 @@ Within a *Worker* execution context, the `xworker` exposes the following feature
544546
545547
| name | example | behavior |
546548
| :------------ | :------------------------------------------| :--------|
547-
| sync | `xworker.sync.from_main(1, "two")` | Executes the exposed `from_main` function in the main thread. Returns synchronously its result, if any. |
548-
| window | `xworker.window.document.title = 'Worker'` | Differently from *pyodide* or *micropython* `import js`, this field allows every single possible operation directly in the main thread. It does not refer to the local `js` environment the interpreter might have decided to expose, it is a proxy to handle otherwise impossible operations in the main thread, such as manipulating the *DOM*, reading `localStorage` otherwise not available in workers, change location or anything else usually possible to do in the main thread. |
549-
| isWindowProxy | `xworker.isWindowProxy(ref)` | **Advanced** - Allows introspection of *JS* references, helping differentiating between local worker references, and main thread global JS references. This is valid both for non primitive objects (array, dictionaries) as well as functions, as functions are also enabled via `xworker.window` in both ways: we can add a listener from the worker or invoke a function in the main. Please note that functions passed to the main thread will always be invoked asynchronously.
549+
| polyfill | `xworker.polyfill` | Returns `true` if *sabayon* polyfill is used behind the scene. |
550+
| sync | `xworker.sync.from_main(1, "two")` | Executes the exposed `from_main` function in the main thread. Returns synchronously its result when *SharedArrayBuffer* can work synchronously. Returns asynchronously otherwise exposed callbacsk from the *main* thread. |
551+
| window | `xworker.window.document.title = 'Worker'` | Differently from *pyodide* or *micropython* `import js`, this field allows every single possible operation directly in the main thread when that is possible (*SharedArrayBuffer* either available or polyfilled for `sync` operations too). It does not refer to the local `js` environment the interpreter might have decided to expose, it is a proxy to handle otherwise impossible operations in the main thread, such as manipulating the *DOM*, reading `localStorage` otherwise not available in workers, change location or anything else usually possible to do in the main thread. |
552+
| isWindowProxy | `xworker.isWindowProxy(ref)` | **Advanced** - Allows introspection of *JS* references, helping differentiating between local worker references, and main thread global JS references. This is valid both for non primitive objects (array, dictionaries) as well as functions, as functions are also enabled via `xworker.window` in both ways: we can add a listener from the worker or invoke a function in the main. Please note that functions passed to the main thread will always be invoked asynchronously. |
553+
550554
551555
```python
552556
from polyscript import xworker

docs/index.js

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

docs/index.js.map

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

docs/zip-CGWtiqjJ.js

-2
This file was deleted.

docs/zip-gl8b5xR3.js

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

docs/zip-CGWtiqjJ.js.map renamed to docs/zip-gl8b5xR3.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-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ export const handleCustomType = async (node) => {
6969
type: runtime,
7070
custom: type,
7171
config: node.getAttribute('config') || config || {},
72-
async: node.hasAttribute('async')
72+
async: node.hasAttribute('async'),
73+
serviceWorker: node.getAttribute('service-worker'),
7374
});
7475
defineProperty(node, 'xworker', { value: xworker });
7576
resolve({ type, xworker });

esm/script-handler.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,15 @@ export const handle = async (script) => {
124124
// allow a shared config among scripts, beside interpreter,
125125
// and/or source code with different config or interpreter
126126
const {
127-
attributes: { async: isAsync, config, env, name: wn, target, version },
127+
attributes: {
128+
async: isAsync,
129+
config,
130+
env,
131+
name: wn,
132+
target,
133+
version,
134+
['service-worker']: sw,
135+
},
128136
src,
129137
type,
130138
} = script;
@@ -143,6 +151,7 @@ export const handle = async (script) => {
143151
...nodeInfo(script, type),
144152
async: !!isAsync,
145153
config: configValue,
154+
serviceWorker: sw?.value,
146155
});
147156
handled.set(
148157
defineProperty(script, 'xworker', { value: xworker }),

esm/utils.js

+12-8
Original file line numberDiff line numberDiff line change
@@ -110,16 +110,20 @@ export const importJS = (source, name) => import(source).then(esm => {
110110
});
111111

112112
export const importCSS = href => new Promise((onload, onerror) => {
113-
if (document.querySelector(`link[href="${href}"]`)) onload();
114-
document.head.append(
115-
assign(
116-
document.createElement('link'),
117-
{ rel: 'stylesheet', href, onload, onerror },
118-
)
119-
)
113+
if (document.querySelector(`link[rel="stylesheet"][href="${href}"]`)) {
114+
onload();
115+
}
116+
else {
117+
document.head.append(
118+
assign(
119+
document.createElement('link'),
120+
{ rel: 'stylesheet', href, onload, onerror },
121+
)
122+
);
123+
}
120124
});
121125

122-
export const isCSS = source => /\.css/i.test(new URL(source).pathname);
126+
export const isCSS = source => /\.css$/i.test(new URL(source).pathname);
123127
/* c8 ignore stop */
124128

125129
export {

esm/worker/_template.js

+7-33
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// bigger than it used to be before any changes is applied to this file.
66

77
import * as JSON from '@ungap/structured-clone/json';
8-
import coincident from 'coincident/window';
8+
import coincident from 'coincident/window/worker';
99

1010
import { assign, create, createFunction, createOverload, createResolved, dispatch, registerJSModules } from '../utils.js';
1111
import createJSModules from './js_modules.js';
@@ -32,17 +32,19 @@ const add = (type, fn) => {
3232

3333
const { parse, stringify } = JSON;
3434

35-
const { proxy: sync, window, isWindowProxy } = coincident(self, {
35+
const { proxy: sync, sync: syncMainAndWorker, polyfill, window, isWindowProxy } = await coincident({
3636
parse,
3737
stringify,
3838
transform: value => transform ? transform(value) : value
3939
});
4040

4141
const xworker = {
42+
// propagate the fact SharedArrayBuffer is polyfilled
43+
polyfill,
4244
// allows synchronous utilities between this worker and the main thread
4345
sync,
44-
// allow access to the main thread world
45-
window,
46+
// allow access to the main thread world whenever it's possible
47+
window: syncMainAndWorker ? window : null,
4648
// allow introspection for foreign (main thread) refrences
4749
isWindowProxy,
4850
// standard worker related events / features
@@ -61,38 +63,10 @@ add('message', ({ data: { options, config: baseURL, configURL, code, hooks } })
6163

6264
const interpreter = await getRuntime(runtimeID, baseURL, configURL, config);
6365

64-
const { js_modules, sync_main_only } = configs.get(runtimeID);
66+
const { js_modules } = configs.get(runtimeID);
6567

6668
const mainModules = js_modules?.main;
6769

68-
// this flag allows interacting with the xworker.sync exposed
69-
// *only in the worker* and eventually invoked *only from main*.
70-
// If that flag is `false` or not present, then SharedArrayBuffer
71-
// must be available or not much can work in here.
72-
let syncMainAndWorker = !sync_main_only;
73-
74-
// bails out out of the box with a native/meaningful error
75-
// in case the SharedArrayBuffer is not available
76-
try {
77-
new SharedArrayBuffer(4);
78-
// if this does not throw there's no reason to
79-
// branch out of all the features ... but ...
80-
syncMainAndWorker = true;
81-
}
82-
// eslint-disable-next-line no-unused-vars
83-
catch (_) {
84-
// if it does throw and `sync_main_only` was not `true`
85-
// then there's no way to go further
86-
if (syncMainAndWorker) {
87-
throw new Error(
88-
[
89-
'Unable to use SharedArrayBuffer due insecure environment.',
90-
'Please read requirements in MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements',
91-
].join('\n'),
92-
);
93-
}
94-
}
95-
9670
const details = create(registry.get(type));
9771

9872
const resolved = createResolved(

esm/worker/class.js

+23-17
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import * as JSON from '@ungap/structured-clone/json';
21
import fetch from '@webreflection/fetch';
3-
import coincident from 'coincident/window';
42
import xworker from './xworker.js';
53
import { getConfigURLAndType } from '../loader.js';
64
import { assign, create, defineProperties, importCSS, importJS } from '../utils.js';
@@ -12,8 +10,11 @@ import Hook from './hook.js';
1210
* @prop {string} [version] the optional interpreter version to use
1311
* @prop {string | object} [config] the optional config to use within such interpreter
1412
* @prop {string} [configURL] the optional configURL used to resolve config entries
13+
* @prop {string} [serviceWorker] the optional Service Worker for SharedArrayBuffer fallback
1514
*/
1615

16+
// REQUIRES INTEGRATION TEST
17+
/* c8 ignore start */
1718
export default (...args) =>
1819
/**
1920
* A XWorker is a Worker facade able to bootstrap a channel with any desired interpreter.
@@ -22,10 +23,6 @@ export default (...args) =>
2223
* @returns {Worker}
2324
*/
2425
function XWorker(url, options) {
25-
const worker = xworker();
26-
const { postMessage } = worker;
27-
const isHook = this instanceof Hook;
28-
2926
if (args.length) {
3027
const [type, version] = args;
3128
options = assign({}, options || { type, version });
@@ -37,28 +34,35 @@ export default (...args) =>
3734
// fallback to a generic, ignored, config.txt file to still provide a URL.
3835
const [ config ] = getConfigURLAndType(options.config, options.configURL);
3936

40-
const bootstrap = fetch(url)
41-
.text()
42-
.then(code => {
43-
const hooks = isHook ? this.toJSON() : void 0;
44-
postMessage.call(worker, { options, config, code, hooks });
45-
});
37+
const worker = xworker({ serviceWorker: options?.serviceWorker });
38+
const { postMessage } = worker;
39+
const isHook = this instanceof Hook;
4640

4741
const sync = assign(
48-
coincident(worker, JSON).proxy,
42+
worker.proxy,
4943
{ importJS, importCSS },
5044
);
5145

5246
const resolver = Promise.withResolvers();
5347

48+
let bootstrap = fetch(url)
49+
.text()
50+
.then(code => {
51+
const hooks = isHook ? this.toJSON() : void 0;
52+
postMessage.call(worker, { options, config, code, hooks });
53+
})
54+
.then(() => {
55+
// boost postMessage performance
56+
bootstrap = { then: fn => fn() };
57+
});
58+
5459
defineProperties(worker, {
5560
sync: { value: sync },
5661
ready: { value: resolver.promise },
5762
postMessage: {
58-
value: (data, ...rest) =>
59-
bootstrap.then(() =>
60-
postMessage.call(worker, data, ...rest),
61-
),
63+
value: (data, ...rest) => bootstrap.then(
64+
() => postMessage.call(worker, data, ...rest),
65+
),
6266
},
6367
onerror: {
6468
writable: true,
@@ -87,3 +91,5 @@ export default (...args) =>
8791

8892
return worker;
8993
};
94+
95+
/* c8 ignore stop */

0 commit comments

Comments
 (0)