Skip to content

Updating coincident to its latest #109

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion cjs/package.json

This file was deleted.

22 changes: 13 additions & 9 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ We encourage everyone to be careful when using this *core* API as we definitivel
| type | `<script type="micropython">` | Define the *interpreter* to use with this script. Please read the [Terminology](#terminology) **interpreter** dedicated details to know more. |
| 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">` |
| 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. |
| 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. |


### Script Features
Expand Down Expand Up @@ -499,12 +500,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* 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**. |

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 +546,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