Skip to content

Commit

Permalink
Merge pull request #54 from pyscript/notify-ready-done
Browse files Browse the repository at this point in the history
Better support for ready/done events
  • Loading branch information
WebReflection authored Oct 2, 2023
2 parents 3f7dad4 + c91b062 commit 96a5e18
Show file tree
Hide file tree
Showing 12 changed files with 67 additions and 12 deletions.
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`));
};

0 comments on commit 96a5e18

Please sign in to comment.