Skip to content

Commit

Permalink
Updating coincident to its latest
Browse files Browse the repository at this point in the history
  • Loading branch information
WebReflection committed Jul 25, 2024
1 parent 036b48c commit 4cc6ffd
Show file tree
Hide file tree
Showing 27 changed files with 326 additions and 324 deletions.
1 change: 0 additions & 1 deletion cjs/package.json

This file was deleted.

21 changes: 12 additions & 9 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -499,12 +499,13 @@ There are **alternative ways** to enable these headers for your site or local ho
Before showing any example, it's important to understand how the offered API differs from Web standard *workers*:
| name | example | behavior |
| :-------- | :------------------------------------------------------- | :--------|
| 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. |
| 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. |
| 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. |
| 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">` |
| name | example | behavior |
| :------------ | :--------------------------------------------------------------- | :--------|
| 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. |
| 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. |
| 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. |
| 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">` |
| serviceWorker | `XWorker('./file.py', type='pyodide', serviceWorker='../sw.js')` | When the server cannot enable *SharedArrayBuffer* or *mini-coi* like service worker is not usable or desired, it is still possible to fallback to a slower, yet working, orchestration provided by [sabayon](https://github.com/WebReflection/sabayon?tab=readme-ov-file#service-worker). |
The returning *JS* reference to any `XWorker(...)` call is literally a `Worker` instance that, among its default API, have the extra following feature:
Expand Down Expand Up @@ -544,9 +545,11 @@ Within a *Worker* execution context, the `xworker` exposes the following feature
| name | example | behavior |
| :------------ | :------------------------------------------| :--------|
| sync | `xworker.sync.from_main(1, "two")` | Executes the exposed `from_main` function in the main thread. Returns synchronously its result, if any. |
| 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. |
| 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.
| polyfill | `xworker.polyfill` | Returns `true` if *sabayon* polyfill is used behind the scene. |
| 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. |
| 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. |
| 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. |
```python
from polyscript import xworker
Expand Down
3 changes: 1 addition & 2 deletions docs/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/index.js.map

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions docs/zip-CGWtiqjJ.js

This file was deleted.

2 changes: 2 additions & 0 deletions docs/zip-gl8b5xR3.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/zip-CGWtiqjJ.js.map → docs/zip-gl8b5xR3.js.map

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion esm/custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ export const handleCustomType = async (node) => {
type: runtime,
custom: type,
config: node.getAttribute('config') || config || {},
async: node.hasAttribute('async')
async: node.hasAttribute('async'),
serviceWorker: node.getAttribute('service-worker'),
});
defineProperty(node, 'xworker', { value: xworker });
resolve({ type, xworker });
Expand Down
11 changes: 10 additions & 1 deletion esm/script-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,15 @@ export const handle = async (script) => {
// allow a shared config among scripts, beside interpreter,
// and/or source code with different config or interpreter
const {
attributes: { async: isAsync, config, env, name: wn, target, version },
attributes: {
async: isAsync,
config,
env,
name: wn,
target,
version,
['service-worker']: sw,
},
src,
type,
} = script;
Expand All @@ -143,6 +151,7 @@ export const handle = async (script) => {
...nodeInfo(script, type),
async: !!isAsync,
config: configValue,
serviceWorker: sw?.value,
});
handled.set(
defineProperty(script, 'xworker', { value: xworker }),
Expand Down
20 changes: 12 additions & 8 deletions esm/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,20 @@ export const importJS = (source, name) => import(source).then(esm => {
});

export const importCSS = href => new Promise((onload, onerror) => {
if (document.querySelector(`link[href="${href}"]`)) onload();
document.head.append(
assign(
document.createElement('link'),
{ rel: 'stylesheet', href, onload, onerror },
)
)
if (document.querySelector(`link[rel="stylesheet"][href="${href}"]`)) {
onload();
}
else {
document.head.append(
assign(
document.createElement('link'),
{ rel: 'stylesheet', href, onload, onerror },
)
);
}
});

export const isCSS = source => /\.css/i.test(new URL(source).pathname);
export const isCSS = source => /\.css$/i.test(new URL(source).pathname);
/* c8 ignore stop */

export {
Expand Down
40 changes: 7 additions & 33 deletions esm/worker/_template.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// bigger than it used to be before any changes is applied to this file.

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

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

const { parse, stringify } = JSON;

const { proxy: sync, window, isWindowProxy } = coincident(self, {
const { proxy: sync, sync: syncMainAndWorker, polyfill, window, isWindowProxy } = await coincident({
parse,
stringify,
transform: value => transform ? transform(value) : value
});

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

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

const { js_modules, sync_main_only } = configs.get(runtimeID);
const { js_modules } = configs.get(runtimeID);

const mainModules = js_modules?.main;

// this flag allows interacting with the xworker.sync exposed
// *only in the worker* and eventually invoked *only from main*.
// If that flag is `false` or not present, then SharedArrayBuffer
// must be available or not much can work in here.
let syncMainAndWorker = !sync_main_only;

// bails out out of the box with a native/meaningful error
// in case the SharedArrayBuffer is not available
try {
new SharedArrayBuffer(4);
// if this does not throw there's no reason to
// branch out of all the features ... but ...
syncMainAndWorker = true;
}
// eslint-disable-next-line no-unused-vars
catch (_) {
// if it does throw and `sync_main_only` was not `true`
// then there's no way to go further
if (syncMainAndWorker) {
throw new Error(
[
'Unable to use SharedArrayBuffer due insecure environment.',
'Please read requirements in MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements',
].join('\n'),
);
}
}

const details = create(registry.get(type));

const resolved = createResolved(
Expand Down
40 changes: 23 additions & 17 deletions esm/worker/class.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import * as JSON from '@ungap/structured-clone/json';
import fetch from '@webreflection/fetch';
import coincident from 'coincident/window';
import xworker from './xworker.js';
import { getConfigURLAndType } from '../loader.js';
import { assign, create, defineProperties, importCSS, importJS } from '../utils.js';
Expand All @@ -12,8 +10,11 @@ import Hook from './hook.js';
* @prop {string} [version] the optional interpreter version to use
* @prop {string | object} [config] the optional config to use within such interpreter
* @prop {string} [configURL] the optional configURL used to resolve config entries
* @prop {string} [serviceWorker] the optional Service Worker for SharedArrayBuffer fallback
*/

// REQUIRES INTEGRATION TEST
/* c8 ignore start */
export default (...args) =>
/**
* A XWorker is a Worker facade able to bootstrap a channel with any desired interpreter.
Expand All @@ -22,10 +23,6 @@ export default (...args) =>
* @returns {Worker}
*/
function XWorker(url, options) {
const worker = xworker();
const { postMessage } = worker;
const isHook = this instanceof Hook;

if (args.length) {
const [type, version] = args;
options = assign({}, options || { type, version });
Expand All @@ -37,28 +34,35 @@ export default (...args) =>
// fallback to a generic, ignored, config.txt file to still provide a URL.
const [ config ] = getConfigURLAndType(options.config, options.configURL);

const bootstrap = fetch(url)
.text()
.then(code => {
const hooks = isHook ? this.toJSON() : void 0;
postMessage.call(worker, { options, config, code, hooks });
});
const worker = xworker({ serviceWorker: options?.serviceWorker });
const { postMessage } = worker;
const isHook = this instanceof Hook;

const sync = assign(
coincident(worker, JSON).proxy,
worker.proxy,
{ importJS, importCSS },
);

const resolver = Promise.withResolvers();

let bootstrap = fetch(url)
.text()
.then(code => {
const hooks = isHook ? this.toJSON() : void 0;
postMessage.call(worker, { options, config, code, hooks });
})
.then(() => {
// boost postMessage performance
bootstrap = { then: fn => fn() };
});

defineProperties(worker, {
sync: { value: sync },
ready: { value: resolver.promise },
postMessage: {
value: (data, ...rest) =>
bootstrap.then(() =>
postMessage.call(worker, data, ...rest),
),
value: (data, ...rest) => bootstrap.then(
() => postMessage.call(worker, data, ...rest),
),
},
onerror: {
writable: true,
Expand Down Expand Up @@ -87,3 +91,5 @@ export default (...args) =>

return worker;
};

/* c8 ignore stop */
Loading

0 comments on commit 4cc6ffd

Please sign in to comment.