Skip to content

Better support for ready/done events #54

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
Oct 2, 2023
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
5 changes: 5 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* [XWorker](#xworker) - how `XWorker` class and its `xworker` reference work
* [Custom Scripts](#custom-scripts) - how *custom types* can be defined and used to enrich any core feature
* [Ready Event](#ready-event) - how to listen to the `type:ready` event
* [Done Event](#done-event) - how to listen to the `type:done` event
* [Examples](#examples) - some *polyscript* based live example
* [Interpreter Features](#interpreter-features) - current state of supported interpreters

Expand Down Expand Up @@ -500,6 +501,10 @@ The [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent)

The `worker` detail is essential to know if an `xworker` property is attached so that it's also easy to pollute its `sync` proxy utility.

## Done Event

Whenever a *non-custom* script is going to run some code, or whenever *any worker* is going to run its own code, a `type:done` event is dispatched through the element *after* its code has fully executed.

### Custom Types on Main

The reason this event is not automatically dispatched on custom type elements or scripts is that these will have their own `onInterpreterReady` hook to eventually do more before desiring, or needing, to notify the "*readiness*" of such custom element and, in case of wanting the event to happen, this is the tiny boilerplate needed to simulate otherwise non-custom type events:
Expand Down
2 changes: 1 addition & 1 deletion docs/core.js

Large diffs are not rendered by default.

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

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions esm/script-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,12 @@ const execute = async (script, source, XWorker, isAsync) => {
get: () => script,
});
module.registerJSModule(interpreter, 'polyscript', { XWorker });
dispatch(script, type);
return module[isAsync ? 'runAsync' : 'run'](interpreter, content);
dispatch(script, type, 'ready');
const result = module[isAsync ? 'runAsync' : 'run'](interpreter, content);
const done = dispatch.bind(null, script, type, 'done');
if (isAsync) result.then(done);
else done();
return result;
} finally {
delete document.currentScript;
}
Expand Down
5 changes: 3 additions & 2 deletions esm/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ const nodeInfo = (node, type) => ({
* Notify the main thread about element "readiness".
* @param {HTMLScriptElement | HTMLElement} target the script or custom-type element
* @param {string} type the custom/type as event prefix
* @param {string} what the kind of event to dispatch, i.e. `ready` or `done`
* @param {boolean} [worker = false] `true` if dispatched form a worker, `false` by default if in main
* @param {globalThis.CustomEvent} [CustomEvent = globalThis.CustomEvent] the `CustomEvent` to use
*/
const dispatch = (target, type, worker = false, CE = CustomEvent) => {
const dispatch = (target, type, what, worker = false, CE = CustomEvent) => {
target.dispatchEvent(
new CE(`${type}:ready`, {
new CE(`${type}:${what}`, {
bubbles: true,
detail: { worker },
})
Expand Down
7 changes: 6 additions & 1 deletion esm/worker/_template.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ add('message', ({ data: { options, config: baseURL, code, hooks } }) => {

const { CustomEvent, document } = window;
const element = id && document.getElementById(id) || null;
const notify = kind => dispatch(element, custom || type, kind, true, CustomEvent);

let target = '';

Expand Down Expand Up @@ -130,10 +131,14 @@ add('message', ({ data: { options, config: baseURL, code, hooks } }) => {
// allows transforming arguments with sync
transform = details.transform.bind(details, interpreter);

if (element) dispatch(element, custom || type, true, CustomEvent);
// notify worker ready to execute
if (element) notify('ready');

// run either sync or async code in the worker
await details[name](interpreter, code);

// notify worker done executing
if (element) notify('done');
return interpreter;
} catch (error) {
postMessage(error);
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "polyscript",
"version": "0.4.10",
"version": "0.4.11",
"description": "PyScript single core to rule them all",
"main": "./cjs/index.js",
"types": "./types/polyscript/esm/index.d.ts",
Expand Down Expand Up @@ -71,6 +71,6 @@
"html-escaper": "^3.0.3"
},
"worker": {
"blob": "sha256-xGArLSJr40wTgAn6eZ7sGIKJMD8yVtiKjA/5Dr6ZbBc="
"blob": "sha256-ixJNXrBnwM18zoc4l44JmnNzgD+eoNpGaOcZz3dXP94="
}
}
2 changes: 1 addition & 1 deletion test/integration.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>polyscript integration tests</title>
</head>
<body><ul><li><strong>micropython</strong><ul><li><a href="/test/integration/interpreter/micropython/bootstrap.html">bootstrap</a></li><li><a href="/test/integration/interpreter/micropython/config-object.html">config-object</a></li><li><a href="/test/integration/interpreter/micropython/custom-hooks.html">custom-hooks</a></li><li><a href="/test/integration/interpreter/micropython/fetch.html">fetch</a></li><li><a href="/test/integration/interpreter/micropython/no-type.html">no-type</a></li><li><a href="/test/integration/interpreter/micropython/worker-attribute.html">worker-attribute</a></li><li><a href="/test/integration/interpreter/micropython/worker-bad.html">worker-bad</a></li><li><a href="/test/integration/interpreter/micropython/worker-empty-attribute.html">worker-empty-attribute</a></li><li><a href="/test/integration/interpreter/micropython/worker-error.html">worker-error</a></li><li><a href="/test/integration/interpreter/micropython/worker-lua.html">worker-lua</a></li><li><a href="/test/integration/interpreter/micropython/worker-tag.html">worker-tag</a></li><li><a href="/test/integration/interpreter/micropython/worker-window.html">worker-window</a></li><li><a href="/test/integration/interpreter/micropython/worker.html">worker</a></li></ul><li><strong>pyodide</strong><ul><li><a href="/test/integration/interpreter/pyodide/bootstrap.html">bootstrap</a></li><li><a href="/test/integration/interpreter/pyodide/button.html">button</a></li><li><a href="/test/integration/interpreter/pyodide/fetch.html">fetch</a></li><li><a href="/test/integration/interpreter/pyodide/sync.html">sync</a></li><li><a href="/test/integration/interpreter/pyodide/worker-error.html">worker-error</a></li><li><a href="/test/integration/interpreter/pyodide/worker-transform.html">worker-transform</a></li><li><a href="/test/integration/interpreter/pyodide/worker.html">worker</a></li></ul><li><strong>ruby-wasm-wasi</strong><ul><li><a href="/test/integration/interpreter/ruby-wasm-wasi/bootstrap.html">bootstrap</a></li></ul><li><strong>wasmoon</strong><ul><li><a href="/test/integration/interpreter/wasmoon/bootstrap.html">bootstrap</a></li><li><a href="/test/integration/interpreter/wasmoon/worker.html">worker</a></li></ul></ul></body>
<body><ul><li><strong>micropython</strong><ul><li><a href="/test/integration/interpreter/micropython/bootstrap.html">bootstrap</a></li><li><a href="/test/integration/interpreter/micropython/config-object.html">config-object</a></li><li><a href="/test/integration/interpreter/micropython/custom-hooks.html">custom-hooks</a></li><li><a href="/test/integration/interpreter/micropython/fetch.html">fetch</a></li><li><a href="/test/integration/interpreter/micropython/no-type.html">no-type</a></li><li><a href="/test/integration/interpreter/micropython/ready-done.html">ready-done</a></li><li><a href="/test/integration/interpreter/micropython/worker-attribute.html">worker-attribute</a></li><li><a href="/test/integration/interpreter/micropython/worker-bad.html">worker-bad</a></li><li><a href="/test/integration/interpreter/micropython/worker-empty-attribute.html">worker-empty-attribute</a></li><li><a href="/test/integration/interpreter/micropython/worker-error.html">worker-error</a></li><li><a href="/test/integration/interpreter/micropython/worker-lua.html">worker-lua</a></li><li><a href="/test/integration/interpreter/micropython/worker-tag.html">worker-tag</a></li><li><a href="/test/integration/interpreter/micropython/worker-window.html">worker-window</a></li><li><a href="/test/integration/interpreter/micropython/worker.html">worker</a></li></ul><li><strong>pyodide</strong><ul><li><a href="/test/integration/interpreter/pyodide/bootstrap.html">bootstrap</a></li><li><a href="/test/integration/interpreter/pyodide/button.html">button</a></li><li><a href="/test/integration/interpreter/pyodide/fetch.html">fetch</a></li><li><a href="/test/integration/interpreter/pyodide/sync.html">sync</a></li><li><a href="/test/integration/interpreter/pyodide/worker-error.html">worker-error</a></li><li><a href="/test/integration/interpreter/pyodide/worker-transform.html">worker-transform</a></li><li><a href="/test/integration/interpreter/pyodide/worker.html">worker</a></li></ul><li><strong>ruby-wasm-wasi</strong><ul><li><a href="/test/integration/interpreter/ruby-wasm-wasi/bootstrap.html">bootstrap</a></li></ul><li><strong>wasmoon</strong><ul><li><a href="/test/integration/interpreter/wasmoon/bootstrap.html">bootstrap</a></li><li><a href="/test/integration/interpreter/wasmoon/worker.html">worker</a></li></ul></ul></body>
</html>
10 changes: 10 additions & 0 deletions test/integration/_shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ exports.python = {
await expect(result).toContain('clicked');
},

waitForReadyDone: ({ expect }, url) => async ({ page }) => {
await page.goto(url);
await page.waitForSelector('html.done');
const [ready, done] = await page.evaluate(() => [ready, done]);
await expect(ready.length).toBe(4);
await expect(done.length).toBe(4);
await expect(ready.join(',')).toBe('micropython:ready,micropython:ready,micropython:ready,micropython:ready');
await expect(done.join(',')).toBe('micropython:done,micropython:done,micropython:done,micropython:done');
},

error: ({ expect }, baseURL) => async ({ page }) => {
// Test that when the worker throws an error, the page does not crash and the
// error is reported to the console.
Expand Down
28 changes: 28 additions & 0 deletions test/integration/interpreter/micropython/ready-done.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="module">
import { init } from '../utils.js';
init('micropython');

globalThis.ready = [];
addEventListener('micropython:ready', ({ type }) => {
ready.push(type);
});

globalThis.done = [];
addEventListener('micropython:done', ({ type }) => {
if (done.push(type) === document.querySelectorAll('script[type="micropython"]').length)
document.documentElement.classList.add('done');
});
</script>
</head>
<body>
<script type="micropython">print(1)</script>
<script type="micropython" async>print(2)</script>
<script type="micropython" worker>print(3)</script>
<script type="micropython" worker async>print(4)</script>
</body>
</html>
2 changes: 2 additions & 0 deletions test/integration/micropython.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@ module.exports = (playwright, baseURL) => {
const result = await page.evaluate(() => document.body.innerText);
await expect(result.trim()).toBe('OK');
});

test('MicroPython ready-done events', python.waitForReadyDone(playwright, `${baseURL}/ready-done.html`));
};