Skip to content

Commit

Permalink
Merge pull request #74 from pyscript/2024-03-02
Browse files Browse the repository at this point in the history
Update docs to 2024.3.2 release.
  • Loading branch information
ntoll authored Mar 26, 2024
2 parents af47c01 + 908b8b1 commit d51e294
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 15 deletions.
8 changes: 4 additions & 4 deletions docs/beginning-pyscript.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ module in the document's `<head>` tag:
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>🦜 Polyglot - Piratical PyScript</title>
<link rel="stylesheet" href="https://pyscript.net/releases/2024.3.1/core.css">
<script type="module" src="https://pyscript.net/releases/2024.3.1/core.js"></script>
<link rel="stylesheet" href="https://pyscript.net/releases/2024.3.2/core.css">
<script type="module" src="https://pyscript.net/releases/2024.3.2/core.js"></script>
</head>
<body>

Expand Down Expand Up @@ -157,8 +157,8 @@ In the end, our HTML should look like this:
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>🦜 Polyglot - Piratical PyScript</title>
<link rel="stylesheet" href="https://pyscript.net/releases/2024.3.1/core.css">
<script type="module" src="https://pyscript.net/releases/2024.3.1/core.js"></script>
<link rel="stylesheet" href="https://pyscript.net/releases/2024.3.2/core.css">
<script type="module" src="https://pyscript.net/releases/2024.3.2/core.js"></script>
</head>
<body>
<h1>Polyglot 🦜 💬 🇬🇧 ➡️ 🏴‍☠️</h1>
Expand Down
15 changes: 15 additions & 0 deletions docs/user-guide/builtins.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
13 changes: 7 additions & 6 deletions docs/user-guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down
238 changes: 238 additions & 0 deletions docs/user-guide/dom.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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"
<!doctype html>
<!--
This JS utility escapes and unescapes HTML chars. It adds an "html" object to
the global context.
-->
<script src="https://cdn.jsdelivr.net/npm/html-escaper@3.0.3/index.js"></script>

<!--
Vanilla JS just to check the expected object is in the global context of the
web page.
-->
<script>
console.log(html);
</script>
```

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: &lt;&gt;
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"
<!doctype html>
<script type="module">
// this utility escapes and unescape HTML chars
import { escape, unescape } from "https://esm.run/html-escaper";
// esm.run returns a module ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
console.log(escape("<>"));
// log: "&lt;&gt;"
</script>
```

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."
<!doctype html>
...
<!-- land the utility still globally as generic script -->
<script src="https://cdn.jsdelivr.net/npm/html-escaper@3.0.3/index.js"></script>
...
```

```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: &lt;&gt;
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."
<script type="py" src="./main.py" config="./pyscript.toml" terminal></script>
```

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)
4 changes: 2 additions & 2 deletions docs/user-guide/first-steps.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ CSS:
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- PyScript CSS -->
<link rel="stylesheet" href="https://pyscript.net/releases/2024.3.1/core.css">
<link rel="stylesheet" href="https://pyscript.net/releases/2024.3.2/core.css">
<!-- This script tag bootstraps PyScript -->
<script type="module" src="https://pyscript.net/releases/2024.3.1/core.js"></script>
<script type="module" src="https://pyscript.net/releases/2024.3.2/core.js"></script>
</head>
<body>
<!-- your code goes here... -->
Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 &amp; JavaScript: user-guide/dom.md
- Workers: user-guide/workers.md
- Builtin helpers: user-guide/builtins.md
- Python terminal: user-guide/terminal.md
Expand Down
2 changes: 1 addition & 1 deletion version.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"version": "2024.3.1"
"version": "2024.3.2"
}

0 comments on commit d51e294

Please sign in to comment.