Skip to content

Commit 87dc308

Browse files
Merge pull request #58 from pyscript/issue-50-ok
Fix #50 - Breaking: refactory of all main/worker hooks
2 parents dcad916 + c87f490 commit 87dc308

File tree

15 files changed

+419
-167
lines changed

15 files changed

+419
-167
lines changed

docs/README.md

+107-13
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* [How Events Work](#how-events-work) - how `<button py-click="...">` works
1111
* [XWorker](#xworker) - how `XWorker` class and its `xworker` reference work
1212
* [Custom Scripts](#custom-scripts) - how *custom types* can be defined and used to enrich any core feature
13+
* [Hooks](#hooks) - how *custom types* can hook around the life cycle of each script/tag
1314
* [Ready Event](#ready-event) - how to listen to the `type:ready` event
1415
* [Done Event](#done-event) - how to listen to the `type:done` event
1516
* [Examples](#examples) - some *polyscript* based live example
@@ -431,6 +432,18 @@ import { define, whenDefined } from 'polyscript';
431432

432433
define('mpy', {
433434
interpreter: 'micropython',
435+
hooks: {
436+
main: {
437+
onReady(wrap, element) {
438+
console.log('here we go main!');
439+
}
440+
},
441+
worker: {
442+
onReady(wrap, xworker) {
443+
console.log('here we go worker!');
444+
}
445+
}
446+
}
434447
// the rest of the custom type options
435448
});
436449

@@ -448,20 +461,101 @@ The list of options' fields is described as such and all of these are *optional*
448461

449462
| name | example | behavior |
450463
| :------------------------ | :-------------------------------------------- | :--------|
451-
| version | `{verstion: '0.23.2'}` | Allow the usage of a specific version of an interpreter, same way `version` attribute works with `<script>` elements. |
452-
| config | `{config: 'type.toml'}` `{config: {}}` | Ensure such config is already parsed and available, if not already passed as object, for every custom `type` that execute code. |
453-
| onerror | `(error, element) => { throw error; }` | Allows custom types to intercept early errors possibly happened while bootstrapping elements. |
464+
| interpreter | `{interpreter: 'pyodide'}` | Specifies the interpreter to use, such as *pyodide*, *micropython*, *wasmoon* or others. |
465+
| config | `{config: 'type.toml'}` `{config: {}}` | Ensure such config is already parsed and available, if not already passed as object, for every custom `type` that execute code. |
466+
| version | `{version: '0.23.2'}` | Allow the usage of a specific version of an interpreter, same way `version` attribute works with `<script>` elements. |
454467
| env | `{env: 'my-project'}` | Guarantee same environment for every custom `type`, avoiding conflicts with any other possible default or custom environment. |
455-
| onInterpreterReady | `{onInterpreterReady(wrap, element) {}}` | This is the main entry point to define anything extra to the context of the always same interpreter. This callback is *awaited* and executed, after the desired *interpreter* is fully available and bootstrapped *once* though other optional fields, per each element that matches the defined `type`. The `wrap` reference contains many fields and utilities helpful to run most common operations, and it is passed along most other options too, when defined. |
456-
| onBeforeRun | `{onBeforeRun(wrap, element) {}}` | This is a **hook** into the logic that runs right before any *interpreter* `run(...)` is performed. It receives the same `wrap` already sent when *onInterpreterReady* executes, and it passes along the current `element` that is going to execute such code. |
457-
| onAfterRun | `{onAfterRun(wrap, element) {}}` | This is a **hook** into the logic that runs right after any *interpreter* `run(...)` is performed. It receives the same `wrap` already sent when *onInterpreterReady* executes, and it passes along the current `element` that already executed the code. |
458-
| onBeforeRunAsync | `{onBeforeRunAsync(wrap, element) {}}` | This is a **hook** into the logic that runs right before any *interpreter* `runAsync(...)` is performed. It receives the same `wrap` already sent when *onInterpreterReady* executes, and it passes along the current `element` that is going to execute such code asynchronously. |
459-
| onAfterRunAsync | `{onAfterRunAsync(wrap, element) {}}` | This is a **hook** into the logic that runs right after any *interpreter* `runAsync(...)` is performed. It receives the same `wrap` already sent when *onInterpreterReady* executes, and it passes along the current `element` that already executed the code asynchronously. |
460-
| onWorkerReady | `{onWorkerReady(interpreter, xworker) {}}` | This is a **hook** into the logic that runs right before a new `XWorker` instance has been created in the **main** thread. It makes it possible to pre-define exposed `sync` methods to the `xworker` counter-part, enabling cross thread features out of the custom type without needing any extra effort. |
461-
| codeBeforeRunWorker | `{codeBeforeRunWorker(){}}` | This is a **hook** into the logic that runs right before any *interpreter* `run(...)` is performed *within a worker*. Because all worker code is executed as `code`, this callback is expected to **return a string** that can be prepended for any worker synchronous operation. |
462-
| codeAfterRunWorker | `{codeAfterRunWorker(){}}` | This is a **hook** into the logic that runs right after any *interpreter* `run(...)` is performed *within a worker*. Because all worker code is executed as `code`, this callback is expected to **return a string** that can be appended for any worker synchronous operation. |
463-
| codeBeforeRunWorkerAsync | `{codeBeforeRunWorkerAsync(){}}` | This is a **hook** into the logic that runs right before any *interpreter* `runAsync(...)` is performed *within a worker*. Because all worker code is executed as `code`, this callback is expected to **return a string** that can be prepended for any worker asynchronous operation. |
464-
| codeAfterRunWorkerAsync | `{codeAfterRunWorkerAsync(){}}` | This is a **hook** into the logic that runs right after any *interpreter* `runAsync(...)` is performed *within a worker*. Because all worker code is executed as `code`, this callback is expected to **return a string** that can be appended for any worker asynchronous operation. |
468+
| onerror | `(error, element) => { throw error; }` | Allows custom types to intercept early errors possibly happened while bootstrapping elements. |
469+
| hooks | `{hooks: {main: {}, worker: {}}}` | Allows custom types to hook logic around every main thread or worker tag via defined hooks. |
470+
471+
## Hooks
472+
473+
Every special script or tag inevitably passes through some main or worker thread related tasks.
474+
475+
In both worlds, the exact sequence of steps around code execution is the following:
476+
477+
* **ready** - the DOM recognized the special script or tag and the associated interpreter is ready to work. A *JS* callback might be useful to instrument the interpreter before anything else happens.
478+
* **before run** - there could be some *JS* code setup specific for the script on the main thread, or the worker. This is similar to a generic *setup* callback in tests.
479+
* **code before run** - there could be some *PL* code to prepend to the one being executed. In this case the code is a string because it will be part of the evaluation.
480+
* **actual code** - the code in the script or tag or the `src` file specified in the script. This is not a hook, just the exact time the code gets executed in general.
481+
* **code after run** - there could be some *PL* code to append to the one being executed. Same as *before*, the code is a string targeting the foreign *PL*.
482+
* **after run** - there could be some *JS* to execute right after the whole code has been evaluated. This is similar to a generic *teardown* callback in tests.
483+
484+
As most interpreters can run their code either *synchronously* or *asynchronously*, the very same sequence is guaranteed to run in order in both cases, and the difference is only around the naming convention.
485+
486+
### Main Hooks
487+
488+
When it comes to *main* hooks all callbacks will receive a *wrapper* of the interpreter with its utilities, see the further section to know more, plus the element on the page that is going to execute its related code, being this a custom script/type or a custom tag.
489+
490+
This is the list of all possible, yet **optional** hooks, a custom type can define for **main**:
491+
492+
| name | type | example | behavior |
493+
| :------------------------ | :----------------------- | :-------------------------------------------- | :--------|
494+
| onReady | (Wrap, Element) => void | `onReady(wrap, element) {}` | If defined, it is invoked before any other hook to signal that the element is going to execute the code. |
495+
| onBeforeRun | (Wrap, Element) => void | `onBeforeRun(wrap, element) {}` | If defined, it is invoked before any other hook to signal that the element is going to execute the code. |
496+
| onBeforeRunAsync | (Wrap, Element) => void | `onBeforeRunAsync(wrap, element) {}` | Same as `onBeforeRun` except it's the one used whenever the script is `async`. |
497+
| codeBeforeRun | () => string | `codeBeforeRun: () => 'print("before")'` | If defined, prepend some code to evaluate right before the rest of the code gets executed. |
498+
| codeBeforeRunAsync | () => string | `codeBeforeRunAsync: () => 'print("before")'` | Same as `codeBeforeRun` except it's the one used whenever the script is `async`. |
499+
| codeAfterRun | () => string | `codeAfterRun: () => 'print("after")'` | If defined, append some code to evaluate right after the rest of the code already executed. |
500+
| codeAfterRunAsync | () => string | `codeAfterRunAsync: () => 'print("after")'` | Same as `codeAfterRun` except it's the one used whenever the script is `async`. |
501+
| onAfterRun | (Wrap, Element) => void | `onAfterRun(wrap, element) {}` | If defined, it is invoked after the foreign code has been executed already. |
502+
| onAfterRunAsync | (Wrap, Element) => void | `onAfterRunAsync(wrap, element) {}` | Same as `onAfterRun` except it's the one used whenever the script is `async`. |
503+
| onWorker | (Wrap?, XWorker) => void | `onWorker(wrap = null, xworker) {}` | If defined, whenever a script or tag with a `worker` attribute is processed it gets triggered on the main thread, to allow to expose possible `xworker` features before the code gets executed within the worker thread. The `wrap` reference is most of the time `null` unless an explicit `XWorker` call has been initialized manually and/or there is an interpreter on the main thread (*very advanced use case*). Please **note** this is the only hook that doesn't exist in the *worker* counter list of hooks. |
504+
505+
### Worker Hooks
506+
507+
When it comes to *worker* hooks, **all non code related callbacks must be serializable**, meaning that callbacks cannot use any outer scope reference, as these are forwarded as strings, hence evaluated after in the worker, to survive the main <-> worker `postMessage` dance.
508+
509+
Here an example of what works and what doesn't:
510+
511+
```js
512+
// this works 👍
513+
define('pl', {
514+
interpreter: 'programming-lang',
515+
hooks: {
516+
worker: {
517+
onReady() {
518+
// NOT suggested, just as example!
519+
if (!('i' in globalThis))
520+
globalThis.i = 0;
521+
console.log(++i);
522+
}
523+
}
524+
}
525+
});
526+
527+
// this DOES NOT WORK ⚠️
528+
let i = 0;
529+
define('pl', {
530+
interpreter: 'programming-lang',
531+
hooks: {
532+
worker: {
533+
onReady() {
534+
// that outer-scope `i` is nowhere understood
535+
// whenever this code executes in the worker
536+
// as this function gets stringified and re-evaluated
537+
console.log(++i);
538+
}
539+
}
540+
}
541+
});
542+
```
543+
544+
At the same time, as the worker doesn't have any `element` strictly related, as workers can be created also procedurally, the second argument won't be an element but the related *xworker* that is driving the logic.
545+
546+
As summary, this is the list of all possible, yet **optional** hooks, a custom type can define for **worker**:
547+
548+
| name | type | example | behavior |
549+
| :------------------------ | :----------------------- | :-------------------------------------------- | :--------|
550+
| onReady | (Wrap, XWorker) => void | `onReady(wrap, xworker) {}` | If defined, it is invoked before any other hook to signal that the xworker is going to execute the code. |
551+
| onBeforeRun | (Wrap, XWorker) => void | `onBeforeRun(wrap, xworker) {}` | If defined, it is invoked before any other hook to signal that the xworker is going to execute the code. |
552+
| onBeforeRunAsync | (Wrap, XWorker) => void | `onBeforeRunAsync(wrap, xworker) {}` | Same as `onBeforeRun` except it's the one used whenever the worker script is `async`. |
553+
| codeBeforeRun | () => string | `codeBeforeRun: () => 'print("before")'` | If defined, prepend some code to evaluate right before the rest of the code gets executed. |
554+
| codeBeforeRunAsync | () => string | `codeBeforeRunAsync: () => 'print("before")'` | Same as `codeBeforeRun` except it's the one used whenever the worker script is `async`. |
555+
| codeAfterRun | () => string | `codeAfterRun: () => 'print("after")'` | If defined, append some code to evaluate right after the rest of the code already executed. |
556+
| codeAfterRunAsync | () => string | `codeAfterRunAsync: () => 'print("after")'` | Same as `codeAfterRun` except it's the one used whenever the worker script is `async`. |
557+
| onAfterRun | (Wrap, XWorker) => void | `onAfterRun(wrap, xworker) {}` | If defined, it is invoked after the foreign code has been executed already. |
558+
| onAfterRunAsync | (Wrap, XWorker) => void | `onAfterRunAsync(wrap, xworker) {}` | Same as `onAfterRun` except it's the one used whenever the worker script is `async`. |
465559

466560
### Custom Scripts Wrappers
467561

docs/core.js

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

docs/core.js.map

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

0 commit comments

Comments
 (0)