Skip to content

Commit dd7179c

Browse files
committed
Allow defining custom JS modules
1 parent 825fde6 commit dd7179c

13 files changed

+5832
-15
lines changed
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{% load reactpy %}
2+
<!DOCTYPE html>
3+
<html>
4+
5+
<head>
6+
<title>ReactPy</title>
7+
{% pyscript_setup extra_js='{"/static/moment.js":"moment"}' %}
8+
</head>
9+
10+
<body>
11+
{% component "example_project.my_app.components.root.py" %}
12+
</body>
13+
14+
</html>
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
<head>
22
<title>ReactPy</title>
3-
{% pyscript_setup config="{'experimental_create_proxy':'auto'}" %}
3+
{% pyscript_setup config='{"experimental_create_proxy":"auto"}' %}
44
</head>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{% load reactpy %}
2+
<!DOCTYPE html>
3+
<html>
4+
5+
<head>
6+
<title>ReactPy</title>
7+
{% pyscript_setup extra_js=my_extra_js_object %}
8+
</head>
9+
10+
<body>
11+
{% component "example_project.my_app.components.root.py" %}
12+
</body>
13+
14+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{% load reactpy %}
2+
<!DOCTYPE html>
3+
<html>
4+
5+
<head>
6+
<title>ReactPy</title>
7+
{% pyscript_setup extra_js='{"/static/moment.js":"moment"}' %}
8+
</head>
9+
10+
<body>
11+
{% component "example_project.my_app.components.root.py" %}
12+
</body>
13+
14+
</html>
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from reactpy import component, html
2+
3+
4+
@component
5+
def root():
6+
from pyscript.js_modules import moment
7+
8+
return html.div(
9+
{"id": "moment"},
10+
"Using the JavaScript package 'moment' to calculate time: ",
11+
moment.default().format("YYYY-MM-DD HH:mm:ss"),
12+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from django.shortcuts import render
2+
from django.templatetags.static import static
3+
4+
5+
def index(request):
6+
return render(
7+
request,
8+
"my_template.html",
9+
context={"my_extra_js_object": {static("moment.js"): "moment"}},
10+
)

docs/src/reference/template-tag.md

+44-5
Original file line numberDiff line numberDiff line change
@@ -195,16 +195,30 @@ Your PyScript component file requires a `#!python def root()` component to funct
195195

196196
??? question "How do I execute JavaScript within PyScript components?"
197197

198-
PyScript components have the ability to directly execute JavaScript using the [`pyodide` `js` module](https://pyodide.org/en/stable/usage/type-conversions.html#importing-javascript-objects-into-python) or [`pyscript` foreign function interface](https://docs.pyscript.net/2024.6.1/user-guide/dom/).
198+
PyScript components have the ability to directly execute standard library JavaScript using the [`pyodide` `js` module](https://pyodide.org/en/stable/usage/type-conversions.html#importing-javascript-objects-into-python) or [`pyscript` foreign function interface](https://docs.pyscript.net/2024.6.1/user-guide/dom/).
199199

200-
_The `#!python js` module has access to everything within the browser's JavaScript environment. Therefore, any public JavaScript functions loaded within your HTML `#!html <head>` can be called as well. However, be mindful of JavaScript load order!_
200+
The `#!python js` module has access to everything within the browser's JavaScript environment. Therefore, any global JavaScript functions loaded within your HTML `#!html <head>` can be called as well. However, be mindful of JavaScript load order!
201201

202202
=== "root.py"
203203

204204
```python
205205
{% include "../../examples/python/pyscript-js-execution.py" %}
206206
```
207207

208+
To import JavaScript modules in a fashion similar to `#!javascript import {moment} from 'static/moment.js'`, you will need to configure your `#!jinja {% pyscript_setup %}` block to make the module available to PyScript. This module will be accessed within `#!python pyscript.js_modules.*`. For more information, see the [PyScript JS modules docs](https://docs.pyscript.net/2024.6.2/user-guide/configuration/#javascript-modules).
209+
210+
=== "root.py"
211+
212+
```python
213+
{% include "../../examples/python/pyscript-js-module.py" %}
214+
```
215+
216+
=== "my_template.html"
217+
218+
```jinja
219+
{% include "../../examples/html/pyscript-js-module.html" %}
220+
```
221+
208222
<!--pyscript-js-exec-end-->
209223

210224
<!--pyscript-multifile-start-->
@@ -297,12 +311,13 @@ You can optionally include a list of Python packages to install within the PyScr
297311

298312
| Name | Type | Description | Default |
299313
| --- | --- | --- | --- |
300-
| `#!python *dependencies` | `#!python str` | Dependencies that need to be loaded on the page for your PyScript components. Each dependency must be contained within it's own string and written in Python requirements file syntax. | N/A |
314+
| `#!python *extra_py` | `#!python str` | Dependencies that need to be loaded on the page for your PyScript components. Each dependency must be contained within it's own string and written in Python requirements file syntax. | N/A |
315+
| `#!python extra_js` | `#!python str | dict` | A JSON string or Python dictionary containing a vanilla JavaScript module URL and the `#!python name: str` to access it within `#!python pyscript.js_modules.*`. | `#!python ""` |
301316
| `#!python config` | `#!python str | dict` | A JSON string or Python dictionary containing PyScript configuration values. | `#!python ""` |
302317

303-
??? question "How do I define dependencies for my PyScript component?"
318+
??? question "How do I install additional Python dependencies?"
304319

305-
Dependencies must be available on [`pypi`](https://pypi.org/), written in pure Python, and declared in your `#!jinja {% pyscript_setup %}` block using Python requirements file syntax.
320+
Dependencies must be available on [`pypi`](https://pypi.org/) and declared in your `#!jinja {% pyscript_setup %}` block using Python requirements file syntax.
306321

307322
These dependencies are automatically downloaded and installed into the PyScript client-side environment when the page is loaded.
308323

@@ -312,6 +327,30 @@ You can optionally include a list of Python packages to install within the PyScr
312327
{% include "../../examples/html/pyscript-setup-dependencies.html" %}
313328
```
314329

330+
??? question "How do I install additional Javascript dependencies?"
331+
332+
You can use the `#!python extra_js` keyword to load additional JavaScript modules into your PyScript environment.
333+
334+
=== "my_template.html"
335+
336+
```jinja
337+
{% include "../../examples/html/pyscript-setup-extra-js-object.html" %}
338+
```
339+
340+
=== "views.py"
341+
342+
```python
343+
{% include "../../examples/python/pyscript-setup-extra-js-object.py" %}
344+
```
345+
346+
The value for `#!python extra_js` is most commonly a Python dictionary, but JSON strings are also supported.
347+
348+
=== "my_template.html"
349+
350+
```jinja
351+
{% include "../../examples/html/pyscript-setup-extra-js-string.html" %}
352+
```
353+
315354
??? question "How do I modify the `pyscript` default configuration?"
316355

317356
You can modify the default [PyScript configuration](https://docs.pyscript.net/2024.6.2/user-guide/configuration/) by providing a value to the `#!python config` keyword.

src/reactpy_django/templatetags/reactpy.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -217,21 +217,25 @@ def pyscript_component(
217217

218218
@register.inclusion_tag("reactpy/pyscript_setup.html")
219219
def pyscript_setup(
220-
*dependencies: str,
220+
*extra_py: str,
221+
extra_js: str | dict = "",
221222
config: str | dict = "",
222223
):
223224
"""
224225
Args:
225-
dependencies: Dependencies that need to be loaded on the page for \
226+
extra_py: Dependencies that need to be loaded on the page for \
226227
your PyScript components. Each dependency must be contained \
227228
within it's own string and written in Python requirements file syntax.
228229
229230
Kwargs:
231+
extra_js: A JSON string or Python dictionary containing a vanilla \
232+
JavaScript module URL and the `name: str` to access it within \
233+
`pyscript.js_modules.*`.
230234
config: A JSON string or Python dictionary containing PyScript \
231235
configuration values.
232236
"""
233237
return {
234-
"pyscript_config": extend_pyscript_config(config, dependencies),
238+
"pyscript_config": extend_pyscript_config(extra_py, extra_js, config),
235239
"pyscript_layout_handler": PYSCRIPT_LAYOUT_HANDLER,
236240
"reactpy_debug_mode": reactpy_config.REACTPY_DEBUG_MODE,
237241
}

src/reactpy_django/utils.py

+18-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import contextlib
44
import inspect
5+
import json
56
import logging
67
import os
78
import re
@@ -496,10 +497,12 @@ def render_pyscript_template(file_paths: Sequence[str], uuid: str, root: str):
496497
return executor.replace(" def root(): ...", user_code)
497498

498499

499-
def extend_pyscript_config(config: dict | str, extra_packages: Sequence) -> str:
500+
def extend_pyscript_config(
501+
extra_py: Sequence, extra_js: dict | str, config: dict | str
502+
) -> str:
500503
"""Extends ReactPy's default PyScript config with user provided values."""
504+
# Lazily set up the initial config in to wait for Django's static file system
501505
if not PYSCRIPT_DEFAULT_CONFIG:
502-
# Need to perform this lazily in order to wait for static files to be available
503506
PYSCRIPT_DEFAULT_CONFIG.update(
504507
{
505508
"packages": [
@@ -514,11 +517,21 @@ def extend_pyscript_config(config: dict | str, extra_packages: Sequence) -> str:
514517
},
515518
}
516519
)
520+
521+
# Extend the Python dependency list
517522
pyscript_config = deepcopy(PYSCRIPT_DEFAULT_CONFIG)
518-
pyscript_config["packages"].extend(extra_packages)
523+
pyscript_config["packages"].extend(extra_py)
524+
525+
# Extend the JavaScript dependency list
526+
if extra_js and isinstance(extra_js, str):
527+
pyscript_config["js_modules"]["main"].update(json.loads(extra_js))
528+
elif extra_js and isinstance(extra_js, dict):
529+
pyscript_config["js_modules"]["main"].update(extra_py)
530+
531+
# Update the config
519532
if config and isinstance(config, str):
520-
pyscript_config.update(orjson.loads(config))
521-
elif isinstance(config, dict):
533+
pyscript_config.update(json.loads(config))
534+
elif config and isinstance(config, dict):
522535
pyscript_config.update(config)
523536
return orjson.dumps(pyscript_config).decode("utf-8")
524537

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from reactpy import component, html
2+
3+
4+
@component
5+
def root():
6+
from pyscript.js_modules import moment
7+
8+
time: str = moment.default().format("YYYY-MM-DD HH:mm:ss")
9+
10+
return html.div(
11+
{"id": "moment", "data-success": bool(time)},
12+
"Using the JavaScript package 'moment' to calculate time: ",
13+
time,
14+
)

0 commit comments

Comments
 (0)