@pyscript/core 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +14 -2
  2. package/cjs/custom/pyscript.js +3 -3
  3. package/cjs/custom-types.js +0 -2
  4. package/cjs/custom.js +6 -6
  5. package/cjs/interpreter/_python.js +4 -2
  6. package/cjs/interpreter/micropython.js +10 -12
  7. package/cjs/interpreter/webr.js +48 -0
  8. package/cjs/plugins.js +89 -0
  9. package/cjs/runtime/_python.js +34 -0
  10. package/cjs/runtime/_utils.js +141 -0
  11. package/cjs/runtime/micropython.js +40 -0
  12. package/cjs/runtime/pyodide.js +40 -0
  13. package/cjs/runtime/ruby.js +50 -0
  14. package/cjs/runtime/wasmoon.js +46 -0
  15. package/cjs/runtimes.js +63 -0
  16. package/cjs/worker/class.js +11 -2
  17. package/cjs/worker/hooks.js +11 -2
  18. package/cjs/worker/xworker.js +1 -1
  19. package/core.js +2 -2
  20. package/docs/README.md +481 -0
  21. package/esm/custom/pyscript.js +3 -3
  22. package/esm/custom.js +6 -6
  23. package/esm/interpreter/_python.js +4 -2
  24. package/esm/interpreter/micropython.js +10 -12
  25. package/esm/worker/class.js +11 -2
  26. package/esm/worker/hooks.js +11 -2
  27. package/esm/worker/xworker.js +1 -1
  28. package/package.json +12 -4
  29. package/pyscript.js +2 -2
  30. package/types/coincident/window.d.ts +4 -4
  31. package/types/custom.d.ts +54 -0
  32. package/types/index.d.ts +1 -1
  33. package/types/interpreter/_python.d.ts +7 -0
  34. package/types/interpreter/_utils.d.ts +11 -0
  35. package/types/interpreter/micropython.d.ts +18 -0
  36. package/types/interpreter/pyodide.d.ts +19 -0
  37. package/types/interpreter/ruby-wasm-wasi.d.ts +15 -0
  38. package/types/interpreter/wasmoon.d.ts +21 -0
  39. package/types/interpreters.d.ts +9 -0
  40. package/types/listeners.d.ts +2 -0
  41. package/types/pyscript/pyscript.core/esm/custom.d.ts +3 -3
  42. package/types/pyscript/pyscript.core/esm/interpreter/_python.d.ts +2 -2
  43. package/types/pyscript/pyscript.core/esm/interpreter/micropython.d.ts +1 -2
  44. package/types/pyscript/pyscript.core/esm/interpreter/webr.d.ts +14 -0
  45. package/types/pyscript/pyscript.core/esm/plugins.d.ts +21 -14
  46. package/types/pyscript/pyscript.core/esm/worker/hooks.d.ts +4 -1
  47. package/types/script-handler.d.ts +2 -1
  48. package/types/worker/class.d.ts +4 -4
  49. package/types/worker/hooks.d.ts +6 -2
package/docs/README.md ADDED
@@ -0,0 +1,481 @@
1
+ # PyScript Core Documentation
2
+
3
+ * [Terminology](#terminology) - what we mean by "_term_" in this document
4
+ * [Bootstrapping core](#bootstrapping-core) - how to enable PyScript Next in your page
5
+ * [How Scripts Work](#how-scripts-work) - how `<script type="...">` works
6
+ * [How Events Work](#how-events-work) - how `<button py-click="...">` works
7
+ * [XWorker](#xworker) - how `XWorker` class and its `xworker` reference work
8
+ * [Custom Scripts](#custom-scripts) - how *custom types* can be defined and used to enrich any core feature
9
+
10
+
11
+ ## Terminology
12
+
13
+ This section goal is to avoid confusion around topics discussed in this document, describing each *term* as exhaustively as possible.
14
+
15
+ <details>
16
+ <summary><strong>Interpreter</strong></summary>
17
+ <div>
18
+
19
+ Also commonly referred as *runtime* or *engine*, we consider an **interpreter** any "_piece of software_" able to parse, understand, and ultimately execute, a *Programming Language* through this project.
20
+
21
+ We also explicitly use that "_piece of software_" as the interpreter name it refers to. We currently bundle references to four interpreters:
22
+
23
+ * [pyodide](https://pyodide.org/en/stable/index.html) is the name of the interpreter that runs likely the most complete version of latest *Python*, enabling dozen official modules at run time, also offering a great *JS* integration in its core
24
+ * [micropython](https://micropython.org/) is the name of the interpreter that runs a small subset of the *Python* standard library and is optimized to run in constrained environments such as *Mobile* phones, or even *Desktop*, thanks to its tiny size and an extremely fast bootstrap
25
+ * [wasmoon](https://github.com/ceifa/wasmoon) is the name of the interpreter that runs *Lua* on the browser and that, among the previous two interpreters, is fully compatible with all core features
26
+ * [ruby-wasm-wasi](https://github.com/ruby/ruby.wasm) is the name of the (currently *experimental*) interpreter that adds *Ruby* to the list of programming languages currently supported
27
+
28
+ `<script>` tags specify which *interpreter* to use via the `type` attribute. This is typically the full name of the interpreter:
29
+
30
+ ```html
31
+ <script type="pyodide">
32
+ import sys
33
+ print(sys.version)
34
+ </script>
35
+
36
+ <script type="micropython">
37
+ import sys
38
+ print(sys.version)
39
+ </script>
40
+
41
+ <script type="wasmoon">
42
+ print(_VERSION)
43
+ </script>
44
+
45
+ <script type="ruby-wasm-wasi">
46
+ print "ruby #{ RUBY_VERSION }"
47
+ </script>
48
+ ```
49
+
50
+ ℹ️ - Please note we decided on purpose to not use the generic programming language name instead of its interpreter project name to avoid being too exclusive for alternative projects that would like to target that very same Programming Language (i.e. note *pyodide* & *micropython* not using *python* indeed as interpreter name).
51
+
52
+ Custom values for the `type` attribute can also be created which alias (and potential build on top of) existing interpreter types. We include `<script type="py">` (and its `<py-script>` custom element counter-part) which use the Pyodide interpreter while extending its behavior in specific ways familiar to existing PyScript users (*the `<py-config>` tag, `<py-repl>`, etc*).
53
+
54
+ </div>
55
+ </details>
56
+
57
+ <details>
58
+ <summary><strong>Target</strong></summary>
59
+ <div>
60
+
61
+ When it comes to *strings* or *attributes*, we consider the **target** any valid element's *id* on the page or, in most cases, any valid *CSS* selector.
62
+
63
+ ```html
64
+ <!-- ℹ️ - requires py-script custom type -->
65
+ <script type="py">
66
+ # target here is a string
67
+ display('Hello PyScript', target='output')
68
+ </script>
69
+ <div id="output">
70
+ <!-- will show "Hello PyScript" once the script executes -->
71
+ </div>
72
+ ```
73
+
74
+ When it comes to the `property` or `field` attached to a `<script>` element though, that *id* or *selector* would already be resolved, so that such field would always point at the very same related element.
75
+
76
+ ```html
77
+ <script type="micropython" target="output">
78
+ from js import document
79
+ document.currentScript.target.textContent = "Hello";
80
+ </script>
81
+ <div id="output">
82
+ <!-- will show "Hello" once the script executes -->
83
+ </div>
84
+ ```
85
+
86
+ ℹ️ - Please note that if no `target` attribute is specified, the *script* will automatically create a "_companion element_" when the `target` property/field is accessed for the very first time:
87
+
88
+ ```html
89
+ <script type="micropython">
90
+ from js import document
91
+
92
+ # will create a <script-micropython> element appended
93
+ # right after the currently executing script
94
+ document.currentScript.target.textContent = "Hello";
95
+ </script>
96
+ <!--
97
+ created during previous code execution
98
+
99
+ <script-micropython>Hello</script-micropython>
100
+ -->
101
+ ```
102
+
103
+ </div>
104
+ </details>
105
+
106
+ <details>
107
+ <summary><strong>Env</strong></summary>
108
+ <div>
109
+
110
+ ℹ️ - This is an **advanced feature** that is worth describing but usually it is not needed for most common use cases.
111
+
112
+ Mostly due its terseness that plays nicely as attribute's suffix, among its commonly understood meaning, we consider an *env* an identifier that guarantee the used *interpreter* would always be the same and no other interpreters, even if they point at very same project, could interfere with globals, behavior, or what's not.
113
+
114
+ In few words, every single *env* would spawn a new interpreter dedicated to such env, and global variables defined elsewhere will not affect this "_environment_" and vice-versa, an *env* cannot dictate what will happen to other interpreters.
115
+
116
+ ```html
117
+ <!-- default env per each interpreter -->
118
+ <script type="micropython">
119
+ shared = True
120
+ </script>
121
+ <script type="micropython">
122
+ # prints True - shared is global
123
+ print(shared)
124
+ </script>
125
+
126
+ <!-- dedicated interpreter -->
127
+ <script type="micropython" env="my-project-env">
128
+ # throws an error - shared doesn't exist
129
+ print(shared)
130
+ </script>
131
+ ```
132
+
133
+ ℹ️ - Please note if the interpreter takes 1 second to bootstrap, multiple *environments* will take *that* second multiplied by the number of different environments, which is why this feature is considered for **advanced** use cases only and it should be discouraged as generic practice.
134
+
135
+ </div>
136
+ </details>
137
+
138
+
139
+ ## Bootstrapping core
140
+
141
+ In order to have anything working at all in our pages, we need to at least bootstrap *@pyscript/core* functionalities, otherwise all examples and scripts mentioned in this document would just sit there ... sadly ignored by every browser:
142
+
143
+ ```html
144
+ <!doctype html>
145
+ <html>
146
+ <head>
147
+ <!-- this is a way to automatically bootstrap @pyscript/core -->
148
+ <script type="module" src="https://esm.run/@pyscript/core"></script>
149
+ </head>
150
+ <body>
151
+ <script type="micropython">
152
+ from js import document
153
+ document.body.textContent = '@pyscript/core'
154
+ </script>
155
+ </body>
156
+ </html>
157
+ ```
158
+
159
+ As *core* exposes some utility/API, using the following method would also work:
160
+
161
+ ```html
162
+ <script type="module">
163
+ import {
164
+ define, // define a custom type="..."
165
+ whenDefined, // wait for a custom type to be defined
166
+ XWorker // allows JS <-> Interpreter communication
167
+ } from 'https://esm.run/@pyscript/core';
168
+ </script>
169
+ ```
170
+
171
+ Please keep reading this document to understand how to use those utilities or how to have other *Pogramming Languages* enabled in your page via `<script>` elements.
172
+
173
+
174
+ ## How Scripts Work
175
+
176
+ The [&lt;script&gt; element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script) has at least these extremely important peculiarities compared to any other element defined by the [HTML Standard](https://html.spec.whatwg.org/multipage/):
177
+
178
+ * its only purpose is to contain *data blocks*, meaning that browsers will never try to parse its content as generic *HTML* (and browsers will completely ignore either its content or its attributes, including the `src`, when its [type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type) is not known)
179
+ * its completely unobtrusive when it comes to both *aria* and *layout*, indeed it's one of the few nodes that can be declared almost anywhere without breaking its parent tree (other notable exception would be a comment node)
180
+ * for our specific use case, it already offers attributes that are historically well understood and known, also simplifying somehow the creation of this document
181
+
182
+ The long story short is that any `<script type="non-standard-type">` has zero issues with any browser of choice, but it's true that using some specific *custom type* might lead to future issues in case that `type` could have some special meaning for the future of the Web.
183
+
184
+ We encourage everyone to be careful when using this *core* API as we definitively don't want to clash or conflict, by any mean, with what the Web might need or offer in the near to far future, but we're also confident so far our current *types* are more than safe.
185
+
186
+ ### Script Attributes
187
+
188
+ | name | example | behavior |
189
+ | :-------- | :-------------------------------------------- | :--------|
190
+ | async | `<script type="pyodide" async>` | The code is evaluated via `runAsync` utility where, if the *interpreter* allows it, top level *await* would be possible, among other *PL* specific asynchronous features. |
191
+ | config | `<script type="pyodide" config="./cfg.toml">` | The interpreter will load and parse the *JSON* or *TOML* file 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. |
192
+ | env | `<script type="pyodide" env="brand">` | Create, if not known yet, a dedicated *environment* for the specified `type`. Please read the [Terminology](#terminology) **env** dedicated details to know more. |
193
+ | src | `<script type="pyodide" src="./app.py">` | Fetch code from the specified `src` file, overriding or ignoring the content of the `<script>` itself, if any. |
194
+ | target | `<script type="pyodide" target="outcome">` | Describe as *id* or *CSS* selector the default *target* to use as `document.currentScript.target` field. Please read the [Terminology](#terminology) **target** dedicated details to know more. |
195
+ | type | `<script type="micropython">` | Define the *interpreter* to use with this script. Please read the [Terminology](#terminology) **interpreter** dedicated details to know more. |
196
+ | 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">` |
197
+
198
+
199
+ ### Script Features
200
+
201
+ These are all special, *script* related features, offered by *@pyscript/core* out of the box.
202
+
203
+ <details>
204
+ <summary><strong>document.currentScript</strong></summary>
205
+ <div>
206
+
207
+ No matter the interpreter of choice, if there is any way to reach the `document` from such interpreter, its `currentScript` will point at the exact/very-same script that is currently executing the code, even if its `async` attribute is used, mimicking what the standard [document.currentScript](https://developer.mozilla.org/en-US/docs/Web/API/Document/currentScript) offers already, and in an unobtrusive way for the rest of the page, as this property only exists for *synchronous* and blocking scripts that are running, hence never interfering with this *core* logic or vice-versa.
208
+
209
+ ```html
210
+ <script type="micropython" id="my-target">
211
+ from js import document
212
+
213
+ # explicitly grab the current script as target
214
+ my_target = document.getElementById('my-target')
215
+
216
+ # verify it is the exact same node with same id
217
+ print(document.currentScript.id == my_target.id)
218
+ </script>
219
+ ```
220
+
221
+ Not only this is helpful to crawl the surrounding *DOM* or *HTML*, every script will also have a `target` property that will point either to the element reachable through the `target` attribute, or it lazily creates once a companion element that will be appended right after the currently executing *script*.
222
+
223
+ Please read the [Terminology](#terminology) **target** dedicated details to know more.
224
+
225
+ </div>
226
+ </details>
227
+
228
+ <details>
229
+ <summary><strong>XWorker</strong></summary>
230
+ <div>
231
+
232
+ With or without access to the `document`, every (*non experimental*) interpreter will have defined, at the global level, a reference to the `XWorker` "_class_" (it's just a *function*!), which goal is to enable off-loading heavy operations on a worker, without blocking the main / UI thread (the current page) and allowing such worker to even reach the `document` or anything else available on the very same main / UI thread.
233
+
234
+ ```html
235
+ <script type="micropython">
236
+ # XWorker is globally defined
237
+ print(XWorker != None)
238
+ </script>
239
+ ```
240
+
241
+ Please read the [XWorker](#xworker) dedicated section to know more.
242
+
243
+ </div>
244
+ </details>
245
+
246
+
247
+ ## How Events Work
248
+
249
+ Inspired by the current [HTML Standard](https://html.spec.whatwg.org/multipage/webappapis.html#event-handlers):
250
+
251
+ > the event handler is exposed through a name, which is a string that always starts with "_on_" and is followed by the name of the event for which the handler is intended.
252
+
253
+ We took a similar approach, replacing that `on` prefix with whatever *interpreter* or *custom type* is available on the page, plus a *dash* `-` to avoid clashing with standards:
254
+
255
+ ```html
256
+ <script type="micropython">
257
+ def print_type(event, double):
258
+ # logs "click 4"
259
+ print(f"{event.type} {double(2)}")
260
+ </script>
261
+ <button micropython-click="print_type(event, lambda x: x * 2)">
262
+ print type
263
+ </button>
264
+ ```
265
+
266
+ If this example felt a bit verbose, be ensured custom types would work the same:
267
+
268
+ ```html
269
+ <!-- ℹ️ - requires py-script custom type -->
270
+ <button py-click="print(event.type)">
271
+ print type
272
+ </button>
273
+ ```
274
+
275
+ What is important to understand about *events* in PyScript is that the text within the attribute is executed just like any other inline or external content is, through the very same *interpreter*, with the notably extra feature that the `event` reference is made temporarily available as *global* by *core*.
276
+
277
+ This really reflects how otherwise native Web inline events handlers work and we think it's a great feature to support ... *but*:
278
+
279
+ * if your script runs *asynchronously* the `event` might be gone on the main / UI thread and by that time any of its `event.stopPropagation()` or `event.preventDefault()` goodness will be problematic, as too late to be executed
280
+ * if your *interpreter* is *experimental*, or incapable of running *synchronous* events, the `event` reference might be less useful
281
+
282
+ ℹ️ - Please note that if your code runs via *XWorker*, hence in a different thread, there are different caveats and constraints to consider. Please read the [XWorker](#xworker) dedicated section to know more.
283
+
284
+ #### The type-env attribute
285
+
286
+ Just as the `env` attribute on a `<script>` tag specifies a specific instance of an interpreter to use to run code, it is possible to use the `[type]-env` attribute to specify which instance of an interpreter or custom type should be used to run event code:
287
+
288
+ ```html
289
+ <script type="micropython">
290
+ def log():
291
+ print(1)
292
+ </script>
293
+ <!-- note the env value -->
294
+ <script type="micropython" env="two">
295
+ # the button will log 2
296
+ def log():
297
+ print(2)
298
+ </script>
299
+ <!-- note the micropython-env value -->
300
+ <button
301
+ micropython-env="two"
302
+ micropython-click="log()"
303
+ >
304
+ log
305
+ </button>
306
+ ```
307
+
308
+ As mentioned before, this will work with `py-env` too, or any custom type defined out there.
309
+
310
+
311
+ ## XWorker
312
+
313
+ Whenever computing relatively expensive stuff, such as a *matplot* image, or literally anything else that would take more than let's say 100ms to answer, running your *interpreter* of choice within a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) is likely desirable, so that the main / UI thread won't block users' actions, listeners, or any other computation going on in these days highly dynamic pages.
314
+
315
+ `@pyscript/core` adds a functionality called `XWorker` to all of the interpreters it offers, which works in each language the way `Worker` does in JavaScript.
316
+
317
+ In each Interpreter, `XWorker` is a global reference, with a counter `xworker` (lower case) global reference within the worker code.
318
+
319
+ In short, the `XWorker` global goal is to help, without much thinking, to run any desired interpreter out of a *Worker*, enabling extra features on the *worker*'s code side.
320
+
321
+ ### XWorker options
322
+
323
+ Before showing any example, it's important to understand how the offered API differs from Web standard *workers*:
324
+
325
+ | name | example | behavior |
326
+ | :-------- | :------------------------------------------------------- | :--------|
327
+ | 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. |
328
+ | config | `XWorker('./file.py', config='./cfg.toml')` | The worker will load and parse the *JSON* or *TOML* file 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. |
329
+ | 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. |
330
+ | 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">` |
331
+
332
+ The returning *JS* reference to any `XWorker(...)` call is literally a `Worker` instance that, among its default API, have the extra following feature:
333
+
334
+
335
+ | name | example | behavior |
336
+ | :-------- | :--------------------------------- | :--------|
337
+ | sync | `sync = XWorker('./file.py').sync` | Allows exposure of callbacks that can be run synchronously from the worker file, even if the defined callback is *asynchronous*. This property is also available in the global `xworker` reference. |
338
+
339
+ ```python
340
+
341
+ sync = XWorker('./file.py').sync
342
+
343
+ def from_main(some, value):
344
+ # return something interesting from main
345
+ # or do anything else
346
+ print(some)
347
+ print(value)
348
+
349
+ sync.from_main = from_main
350
+ ```
351
+
352
+ In the `xworker` counter part:
353
+
354
+ ```python
355
+ # will log 1 and "two" in default stdout console
356
+ xworker.sync.from_main(1, "two")
357
+ ```
358
+
359
+ ### The xworker global reference
360
+
361
+ The content of the file used to initialize any `XWorker` on the main thread can always reach the `xworker` counter part as globally available (that means: no *import ... form ...* is necessary, it is already there).
362
+
363
+ Within a *Worker* execution context, the `xworker` exposes the following features:
364
+
365
+ | name | example | behavior |
366
+ | :------------ | :------------------------------------------| :--------|
367
+ | sync | `xworker.sync.from_main(1, "two")` | Executes the exposed `from_main` function in the main thread. Returns synchronously its result, if any. |
368
+ | 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. |
369
+ | isWindowProxy | `xworker.isWindowProxy(ref)` | **Advanced** - Allows introspection of *JS* references, helping differentiating between local worker references, and main thread global 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.
370
+
371
+ ```python
372
+ print(xworker.window.document.title)
373
+
374
+ xworker.window.document.body.append("Hello Main Thread")
375
+
376
+ xworker.window.setTimeout(print, 100, "timers too")
377
+ ```
378
+
379
+ ℹ️ - Please note that even if non blocking, if too many operations are orchestrated from a *worker*, instead of the *main* thread, the overall performance might still be slower due the communication channel and all the primitives involved in the synchronization process. Feel free to use the `window` feature as a great enabler for unthinkable or quick solutions but keep in mind it is still an indirection.
380
+
381
+ #### The `sync` utility
382
+
383
+ This helper does not interfere with the global context but it still ensure a function can be exposed form *main* and be used from *thread* and/or vice-versa.
384
+
385
+ ```python
386
+ # main
387
+ def alert_user(message):
388
+ import js
389
+ js.alert(message)
390
+
391
+ w = XWorker('./file.py')
392
+ # expose the function to the thread
393
+ w.sync.alert_user = alert_user
394
+
395
+
396
+ # thread
397
+ if condition == None:
398
+ xworker.sync.alert_user('something wrong!')
399
+ ```
400
+
401
+
402
+ ## Custom Scripts
403
+
404
+ With `@pyscript/core` it is possible to extend any *interpreter*, allowing users or contributors to define their own `type` for the `<script>` they would like to augment with goodness or extra simplicity.
405
+
406
+ The *core* module itself exposes two methods to do so:
407
+
408
+ | name | example | behavior |
409
+ | :------------ | :------------------------ | :--------|
410
+ | define | `define('mpy', options)` | Register once a `<script type="mpy">` and a counter `<mpy-script>` selector that will bootstrap and handle all nodes in the page that match such selectors. The available `options` are described after this table. |
411
+ | whenDefined | `whenDefined('mpy')` | Return a promise that will be resolved once the custom `mpy` script will be available, returning an *interpreter* wrapper once it will be fully ready. |
412
+
413
+ ```js
414
+ import { define, whenDefined } from '@pyscript/core';
415
+
416
+ define('mpy', {
417
+ interpreter: 'micropython',
418
+ // the rest of the custom type options
419
+ });
420
+
421
+ // an "mpy" dependent plugin for the "mpy" custom type
422
+ whenDefined("mpy").then(interpreterWrapper => {
423
+ // define or perform any task via the wrapper
424
+ })
425
+ ```
426
+
427
+ ### Custom Scripts Options
428
+
429
+ **Advanced** - Even if we strive to provide the easiest way for anyone to use core interpreters and features, the life cycle of a custom script might require any hook we also use internally to make `<script type="py">` possible, which is why this list is quite long, but hopefully exhaustive, and it covers pretty much everything we do internally as well.
430
+
431
+ The list of options' fields is described as such and all of these are *optional* while defining a custom type:
432
+
433
+ | name | example | behavior |
434
+ | :------------------------ | :-------------------------------------------- | :--------|
435
+ | version | `{verstion: '0.23.2'}` | Allow the usage of a specific version of an interpreter, same way `version` attribute works with `<script>` elements. |
436
+ | config | `{config: 'type.toml'}` | Ensure such config is already parsed and available for every custom `type` that execute code. |
437
+ | env | `{env: 'my-project'}` | Guarantee same environment for every custom `type`, avoiding conflicts with any other possible default or custom environment. |
438
+ | 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. |
439
+ | 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. |
440
+ | 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. |
441
+ | 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. |
442
+ | 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. |
443
+ | 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. |
444
+ | 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. |
445
+ | 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. |
446
+ | 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. |
447
+ | 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. |
448
+
449
+ ### Custom Scripts Wrappers
450
+
451
+ Almost every interpreter has its own way of doing the same thing needed for most common use cases, and with this in mind we abstracted most operations to allow a terser *core* for anyone to consume, granting that its functionalities are the same, no matter which interpreter one prefers.
452
+
453
+ There are also cases that are not tackled directly in *core*, but necessary to anyone trying to extend *core* as it is, so that some helper felt necessary to enable users and contributors as much as they want.
454
+
455
+ In few words, while every *interpreter* is literally passed along to unlock its potentials 100%, the most common details or operations we need in core are:
456
+
457
+ | name | example | behavior |
458
+ | :------------------------ | :-------------------------------------------- | :--------|
459
+ | type | `wrap.type` | Return the current `type` (interpreter or custom type) used in the current code execution. |
460
+ | interpreter | `wrap.interpreter` | Return the *interpreter* _AS-IS_ after being bootstrapped by the desired `config`. |
461
+ | XWorker | `wrap.XWorker` | Refer to the global `XWorker` class available to the main thread code while executing. |
462
+ | io | `wrap.io` | Allow to lazily define different `stdout` or `stderr` via the running *interpreter*. This `io` field can be lazily defined and restored back for any element currently running the code. |
463
+ | config | `wrap.config` | It is the resolved *JSON* config and it is an own clone per each element running the code, usable also as "_state_" reference for the specific element, as changing it at run time will never affect any other element. |
464
+ | run | `wrap.run(code)` | It abstracts away the need to know the exact method name used to run code synchronously, whenever the *interpreter* allows such operation, facilitating future migrations from an interpreter to another. |
465
+ | runAsync | `wrap.run(code)` | It abstracts away the need to know the exact method name used to run code asynchronously, whenever the *interpreter* allows such operation, facilitating future migrations from an interpreter to another. |
466
+
467
+ This is the `wrap` mentioned with most hooks and initializers previously described, and we're more than happy to learn if we are not passing along some extra helper.
468
+
469
+ ### The io helper
470
+
471
+ ```js
472
+ // change the default stdout while running code
473
+ wrap.io.stdout = (message) => {
474
+ console.log("🌑", wrap.type, message);
475
+ };
476
+
477
+ // change the default stderr while running code
478
+ wrap.io.stderr = (message) => {
479
+ console.error("🌑", wrap.type, message);
480
+ };
481
+ ```
@@ -114,10 +114,10 @@ document.head.appendChild(document.createElement("style")).textContent = `
114
114
  onAfterRunAsync(pyodide, element) {
115
115
  bootstrapNodeAndPlugins(pyodide, element, after, "onAfterRunAsync");
116
116
  },
117
- async onRuntimeReady(pyodide, element) {
117
+ async onInterpreterReady(pyodide, element) {
118
118
  // allows plugins to do whatever they want with the element
119
119
  // before regular stuff happens in here
120
- for (const callback of hooks.onRuntimeReady)
120
+ for (const callback of hooks.onInterpreterReady)
121
121
  callback(pyodide, element);
122
122
  if (isScript(element)) {
123
123
  const {
@@ -178,7 +178,7 @@ export const hooks = {
178
178
  /** @type {Set<function>} */
179
179
  onAfterRunAsync: new Set(),
180
180
  /** @type {Set<function>} */
181
- onRuntimeReady: new Set(),
181
+ onInterpreterReady: new Set(),
182
182
 
183
183
  /** @type {Set<string>} */
184
184
  codeBeforeRunWorker: new Set(),
package/esm/custom.js CHANGED
@@ -46,7 +46,7 @@ export const handleCustomType = (node) => {
46
46
  version,
47
47
  config,
48
48
  env,
49
- onRuntimeReady,
49
+ onInterpreterReady,
50
50
  } = options;
51
51
  const name = getRuntimeID(runtime, version);
52
52
  const id = env || `${name}${config ? `|${config}` : ""}`;
@@ -67,7 +67,7 @@ export const handleCustomType = (node) => {
67
67
  onAfterRunAsync,
68
68
  } = options;
69
69
 
70
- const hooks = new Hook(options);
70
+ const hooks = new Hook(interpreter, options);
71
71
 
72
72
  const XWorker = function XWorker(...args) {
73
73
  return Worker.apply(hooks, args);
@@ -121,7 +121,7 @@ export const handleCustomType = (node) => {
121
121
 
122
122
  resolve(resolved);
123
123
 
124
- onRuntimeReady?.(resolved, node);
124
+ onInterpreterReady?.(resolved, node);
125
125
  });
126
126
  }
127
127
  }
@@ -134,17 +134,17 @@ export const handleCustomType = (node) => {
134
134
  const registry = new Map();
135
135
 
136
136
  /**
137
- * @typedef {Object} PluginOptions custom configuration
137
+ * @typedef {Object} CustomOptions custom configuration
138
138
  * @prop {'pyodide' | 'micropython' | 'wasmoon' | 'ruby-wasm-wasi'} interpreter the interpreter to use
139
139
  * @prop {string} [version] the optional interpreter version to use
140
140
  * @prop {string} [config] the optional config to use within such interpreter
141
- * @prop {(environment: object, node: Element) => void} [onRuntimeReady] the callback that will be invoked once
141
+ * @prop {(environment: object, node: Element) => void} [onInterpreterReady] the callback that will be invoked once
142
142
  */
143
143
 
144
144
  /**
145
145
  * Allows custom types and components on the page to receive interpreters to execute any code
146
146
  * @param {string} type the unique `<script type="...">` identifier
147
- * @param {PluginOptions} options the custom type configuration
147
+ * @param {CustomOptions} options the custom type configuration
148
148
  */
149
149
  export const define = (type, options) => {
150
150
  if (defaultRegistry.has(type) || registry.has(type))
@@ -7,11 +7,13 @@ export const run = (interpreter, code) => interpreter.runPython(clean(code));
7
7
  export const runAsync = (interpreter, code) =>
8
8
  interpreter.runPythonAsync(clean(code));
9
9
 
10
- export const setGlobal = (interpreter, name, value) =>
10
+ export const setGlobal = (interpreter, name, value) => {
11
11
  interpreter.globals.set(name, value);
12
+ };
12
13
 
13
- export const deleteGlobal = (interpreter, name) =>
14
+ export const deleteGlobal = (interpreter, name) => {
14
15
  interpreter.globals.delete(name);
16
+ };
15
17
 
16
18
  export const writeFile = ({ FS }, path, buffer) =>
17
19
  writeFileUtil(FS, path, buffer);
@@ -1,11 +1,5 @@
1
1
  import { fetchPaths, stdio } from "./_utils.js";
2
- import {
3
- run,
4
- runAsync,
5
- setGlobal,
6
- deleteGlobal,
7
- writeFile,
8
- } from "./_python.js";
2
+ import { run, setGlobal, deleteGlobal, writeFile } from "./_python.js";
9
3
 
10
4
  const type = "micropython";
11
5
 
@@ -13,19 +7,23 @@ const type = "micropython";
13
7
  /* c8 ignore start */
14
8
  export default {
15
9
  type,
16
- module: (version = "1.20.0-253") =>
10
+ module: (version = "1.20.0-268") =>
17
11
  `https://cdn.jsdelivr.net/npm/@micropython/micropython-webassembly-pyscript@${version}/micropython.mjs`,
18
12
  async engine({ loadMicroPython }, config, url) {
19
13
  const { stderr, stdout, get } = stdio();
20
14
  url = url.replace(/\.m?js$/, ".wasm");
21
- const runtime = await get(loadMicroPython({ stderr, stdout, url }));
22
- if (config.fetch) await fetchPaths(this, runtime, config.fetch);
23
- return runtime;
15
+ const interpreter = await get(loadMicroPython({ stderr, stdout, url }));
16
+ if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
17
+ return interpreter;
24
18
  },
25
19
  setGlobal,
26
20
  deleteGlobal,
27
21
  run,
28
- runAsync,
22
+ // TODO: MicroPython doesn't have a Pyodide like top-level await,
23
+ // this method should still not throw errors once invoked
24
+ async runAsync(...args) {
25
+ return this.run(...args);
26
+ },
29
27
  writeFile,
30
28
  };
31
29
  /* c8 ignore stop */
@@ -22,19 +22,24 @@ export default (...args) =>
22
22
  function XWorker(url, options) {
23
23
  const worker = xworker();
24
24
  const { postMessage } = worker;
25
+ const isHook = this instanceof Hook;
26
+
25
27
  if (args.length) {
26
28
  const [type, version] = args;
27
29
  options = assign({}, options || { type, version });
28
30
  if (!options.type) options.type = type;
29
31
  }
32
+
30
33
  if (options?.config) options.config = absoluteURL(options.config);
34
+
31
35
  const bootstrap = fetch(url)
32
36
  .then(getText)
33
37
  .then((code) => {
34
- const hooks = this instanceof Hook ? this : void 0;
38
+ const hooks = isHook ? this.stringHooks : void 0;
35
39
  postMessage.call(worker, { options, code, hooks });
36
40
  });
37
- return defineProperties(worker, {
41
+
42
+ defineProperties(worker, {
38
43
  postMessage: {
39
44
  value: (data, ...rest) =>
40
45
  bootstrap.then(() =>
@@ -45,4 +50,8 @@ export default (...args) =>
45
50
  value: coincident(worker, JSON).proxy,
46
51
  },
47
52
  });
53
+
54
+ if (isHook) this.onWorkerReady?.(this.interpreter, worker);
55
+
56
+ return worker;
48
57
  };