diff --git a/docs/beginning-pyscript.md b/docs/beginning-pyscript.md index 2cde017..cc5d933 100644 --- a/docs/beginning-pyscript.md +++ b/docs/beginning-pyscript.md @@ -106,8 +106,8 @@ module in the document's `` tag: 🦜 Polyglot - Piratical PyScript - - + + @@ -157,8 +157,8 @@ In the end, our HTML should look like this: 🦜 Polyglot - Piratical PyScript - - + +

Polyglot 🦜 💬 🇬🇧 ➡️ 🏴‍☠️

diff --git a/docs/user-guide/builtins.md b/docs/user-guide/builtins.md index 05c30ee..ee6f823 100644 --- a/docs/user-guide/builtins.md +++ b/docs/user-guide/builtins.md @@ -193,6 +193,21 @@ response = await fetch("https://example.com", method="POST", body="HELLO").text( bug). However, you could use a pass-through proxy service to get around this limitation (i.e. the proxy service makes the call on your behalf). +### `pyscript.ffi.to_js` + +A utility function to convert Python references into their JavaScript +equivalents. For example, a Python dictionary is converted into a JavaScript +object literal (rather than a JavaScript `Map`), unless a `dict_converter` +is explicitly specified and the runtime is Pyodide. + +### `pyscript.ffi.create_proxy` + +A utility function explicitly for when a callback function is added via an +event listener. It ensures the function still exists beyond the assignment of +the function to an event. Should you not `create_proxy` around the callback +function, it will be immediately garbage collected after being bound to the +event. + ## Main-thread only features ### `pyscript.PyWorker` diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md index ff5614b..5abfcb7 100644 --- a/docs/user-guide/configuration.md +++ b/docs/user-guide/configuration.md @@ -160,6 +160,10 @@ examples could be equivalently re-written as: ... etc ... ``` +If the source part of the configuration is either a `.zip` or `.tar.gz` file +and its destination is a folder path followed by a star (e.g. `/*` or +`./dest/*`), then PyScript will extract the referenced archive automatically +into the target directory in the browser's built in file system. !!! warning @@ -312,12 +316,9 @@ plugins = ["custom_plugin", "!error"] ### JavaScript modules -It's easy to import and use JavaScript modules in your Python code. - -!!! warning - - This feature currently only works with Pyodide. MicroPython support will - come in a future release. +It's easy to import and use JavaScript modules in your Python code. This +section of the docs examines the configuration needed to make this work. How +to make use of JavaScript is dealt with [elsewhere](../dom/#working-with-javascript). To do so, requires telling PyScript about the JavaScript modules you want to use. This is the purpose of the `js_modules` related configuration fields. diff --git a/docs/user-guide/dom.md b/docs/user-guide/dom.md index edd8233..ae58a08 100644 --- a/docs/user-guide/dom.md +++ b/docs/user-guide/dom.md @@ -68,6 +68,10 @@ equivalent values: `["hello", 1, 2, 3]`. instantiation very differently. By explicitly calling the JavaScript class's `new` method PyScript both signals and honours this difference. +Should you require lower level API access to FFI features, you can find such +builtin functions under the `pyscript.ffi` namespace in both Pyodide and +MicroPython. The available functions are described in our section on +[builtin helpers](../builtins). ## PyDom @@ -253,3 +257,237 @@ paragraphs.style['background-color'] = 'lightyellow' a collection by setting the proper CSS rules, using `style` with the same API as a dictionary. * `html`: allows to change the `html` attribute on all the elements of a collection. * `value`: allows to change the `value` attribute on all the elements of a collection. + +## Working with JavaScript + +There are three ways in which JavaScript can get into a web page. + +1. As a global reference attached to the `window` object in the web page + because the code was referenced as the source of a `script` tag in your HTML + (the very old school way to do this). +2. Using the [Universal Module Definition](https://github.com/umdjs/umd) (UMD) + - an out-of-date and non-standard way to create JavaScript modules. +3. As a standard + [JavaScript Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) + which is the modern, standards compliant way to define and use a JavaScript + module. If possible, this is the way you should do things. + +Sadly, this being the messy world of the web, methods 1 and 2 are still quite +common, and so you need to know about them so you're able to discern and work +around them. There's nothing WE can do about this situation, but we can +suggest "best practice" ways to work around each situation. + +Remember, as mentioned +[elsewhere in our documentation](../configuration/#javascript-modules), +the standard way to get JavaScript modules into your PyScript Python context is +to link a _source_ standard JavaScript module to a _destination_ name: + +```toml title="Reference a JavaScript module in the configuration." +[js_modules.main] +"https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet-src.esm.js" = "leaflet" +``` + +Then, reference the module via the destination name in your Python code, by +importing it from the `pyscript.js_modules` namespace: + +```python title="Import the JavaScript module into Python" +from pyscript.js_modules import leaflet as L + +map = L.map("map") + +# etc.... +``` + +We'll deal with each of the potential JavaScript related situations in turn: + +### JavaScript as a global reference + +In this situation, you have some JavaScript code that just globally defines +"stuff" in the context of your web page via a `script` tag. Your HTML will +contain something like this: + +```html title="JavaScript as a global reference" + + + + + + +``` + +When you find yourself in this situation, simply use the `window` object in +your Python code (found in the `pyscript` namespace) to interact with the +resulting JavaScript objects: + +```python title="Python interaction with the JavaScript global reference" +from pyscript import window, document + + +# The window object is the global context of your web page. +html = window.html + +# Just use the object "as usual"... +# e.g. show escaped HTML in the body: <> +document.body.append(html.escape("<>")) +``` + +You can find an example of this technique here: + +[https://pyscript.com/@agiammarchi/floral-glade/v1](https://pyscript.com/@agiammarchi/floral-glade/v1) + +### JavaScript as a non-standard UMD module + +Sadly, these sorts of non-standard JavaScript modules are still quite +prevalent. But the good news is there are strategies you can use to help you +get them to work properly. + +The non-standard UMD approach tries to check for `export` and `module` fields +in the JavaScript module and, if it doesn’t find them, falls back to treating +the module in the same way as a global reference described above. + +If you find you have a UMD JavaScript module, there are services online to +automagically convert it to the modern and standards compliant way to d +o JavaScript modules. A common (and usually reliable) service is provided by +[https://esm.run/your-module-name](https://esm.run/your-module-name), a +service that provides an out of the box way to consume the module in the +correct and standard manner: + +```html title="Use esm.run to automatically convert a non-standard UMD module" + + +``` + +If a similar test works for the module you want to use, use the esm.run CDN +service within the `py` or `mpy` configuration file as explained at the start +of this section on JavaScript (i.e. you'll use it via the `pyscript.js_modules` +namespace). + +If this doesn't work, assume the module is not updated nor migrated to a state +that can be automatically translated by services like esm.run. You could try an +alternative (more modern) JavaScript module to achieve you ends or (if it +really must be this module), you can wrap it in a new JavaScript module that +conforms to the modern standards. + +The following four files demonstrate this approach: + +```html title="index.html - still grab the script so it appears as a global reference." + +... + + +... +``` + +```js title="wrapper.js - this grabs the JavaScript functionality from the global context and wraps it (exports it) in the modern standards compliant manner." +// get all utilities needed from the global. +const { escape, unescape } = globalThis.html; + +// export utilities like a standards compliant module would do. +export { escape, unescape }; +``` + +```toml title="pyscript.toml - configure your JS modules as before, but use your wrapper instead of the original module." +[js_modules.main] +# will simulate a standard JS module +"./wrapper.js" = "html_escaper" +``` + +```python title="main.py - just import the module as usual and make use of it." +from pyscript import document + +# import the module either via +from pyscript.js_modules import html_escaper +# or via +from pyscript.js_modules.html_escaper import escape, unescape + +# show on body: <> +document.body.append(html.escape("<>")) +``` + +You can see this approach in action here: + +[https://pyscript.com/@agiammarchi/floral-glade/v2](https://pyscript.com/@agiammarchi/floral-glade/v2) + +### A standard JavaScript module + +This is both the easiest and best way to import any standard JS module into +Python. + +You don't need to reference the script in your HTML, just define how the source +JavaScript module maps into the `pyscript.js_modules` namespace in your +configuration file, as explained above. + +That's it! + +Here is an example project that uses this approach: + +[https://pyscript.com/@agiammarchi/floral-glade/v3](https://pyscript.com/@agiammarchi/floral-glade/v3) + + +### My own JavaScript code + +If you have your own JavaScript work, just remember to write it as a standard +JavaScript module. Put simply, ensure you `export` the things you need to. For +instance, in the following fragment of JavaScript, the two functions are +exported from the module: + +```js title="code.js - containing two functions exported as capabilities of the module." +/* +Some simple JavaScript functions for example purposes. +*/ + +export function hello(name) { + return "Hello " + name; +} + +export function fibonacci(n) { + if (n == 1) return 0; + if (n == 2) return 1; + return fibonacci(n - 1) + fibonacci(n - 2); +} +``` + +Next, just reference this module in the usual way in your TOML or JSON +configuration file: + +```TOML title="pyscript.toml - references the code.js module so it will appear as the code module in the pyscript.js_modules namespace." +[js_modules.main] +"code.js" = "code" +``` + +In your HTML, reference your Python script with this configuration file: + +```html title="Reference the expected configuration file." + +``` + +Finally, just use your JavaScript module’s exported functions inside PyScript: + +```python title="Just call your bespoke JavaScript code from Python." +from pyscript.js_modules import code + + +# Just use the JS code from Python "as usual". +greeting = code.hello("Chris") +print(greeting) +result = code.fibonacci(12) +print(result) +``` + +You can see this in action in the following example project: + +[https://pyscript.com/@ntoll/howto-javascript/latest](https://pyscript.com/@ntoll/howto-javascript/latest) diff --git a/docs/user-guide/first-steps.md b/docs/user-guide/first-steps.md index 6256d1e..0d1cb86 100644 --- a/docs/user-guide/first-steps.md +++ b/docs/user-guide/first-steps.md @@ -20,9 +20,9 @@ CSS: - + - + diff --git a/docs/user-guide/plugins.md b/docs/user-guide/plugins.md index 5921e6c..3575683 100644 --- a/docs/user-guide/plugins.md +++ b/docs/user-guide/plugins.md @@ -14,7 +14,7 @@ Here's an example of how a PyScript plugin looks like: ```js // import the hooks from PyScript first... -import { hooks } from "https://pyscript.net/releases/2024.3.1/core.js"; +import { hooks } from "https://pyscript.net/releases/2024.3.2/core.js"; // Use the `main` attribute on hooks do define plugins that run on the main thread hooks.main.onReady.add((wrap, element) => { diff --git a/mkdocs.yml b/mkdocs.yml index a3d5827..0e190a1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -71,7 +71,7 @@ nav: - First steps: user-guide/first-steps.md - Architecture: user-guide/architecture.md - Configuration: user-guide/configuration.md - - The DOM: user-guide/dom.md + - The DOM & JavaScript: user-guide/dom.md - Workers: user-guide/workers.md - Builtin helpers: user-guide/builtins.md - Python terminal: user-guide/terminal.md diff --git a/version.json b/version.json index 0ca80dc..30bce84 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "2024.3.1" + "version": "2024.3.2" }