Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e09fa17

Browse files
authoredOct 23, 2024··
Cleanup PyScript renderer & use Bun for building JS (#254)
- Switch to `Bun` for building JS - Minor cleanup to pyscript renderer - Revert pyscript version to 0.5 due to [bugs](pyscript/pyscript#2228)
1 parent 7948ac4 commit e09fa17

23 files changed

+135
-3575
lines changed
 

‎.editorconfig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@ end_of_line = lf
1414
indent_size = 4
1515
max_line_length = 120
1616

17+
[*.yml]
18+
indent_size = 4
19+
1720
[*.md]
1821
indent_size = 4
1922

2023
[*.html]
24+
indent_size = 4
2125
max_line_length = off
2226

2327
[*.js]

‎.github/workflows/publish-develop-docs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ jobs:
1111
- uses: actions/checkout@v4
1212
with:
1313
fetch-depth: 0
14+
- uses: oven-sh/setup-bun@v2
15+
with:
16+
bun-version: latest
1417
- uses: actions/setup-python@v5
1518
with:
1619
python-version: 3.x

‎.github/workflows/publish-py.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ jobs:
1212
runs-on: ubuntu-latest
1313
steps:
1414
- uses: actions/checkout@v4
15+
- uses: oven-sh/setup-bun@v2
16+
with:
17+
bun-version: latest
1518
- name: Set up Python
1619
uses: actions/setup-python@v5
1720
with:

‎.github/workflows/publish-release-docs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ jobs:
1111
- uses: actions/checkout@v4
1212
with:
1313
fetch-depth: 0
14+
- uses: oven-sh/setup-bun@v2
15+
with:
16+
bun-version: latest
1417
- uses: actions/setup-python@v5
1518
with:
1619
python-version: 3.x

‎.github/workflows/test-docs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ jobs:
1717
- uses: actions/checkout@v4
1818
with:
1919
fetch-depth: 0
20+
- uses: oven-sh/setup-bun@v2
21+
with:
22+
bun-version: latest
2023
- uses: actions/setup-python@v5
2124
with:
2225
python-version: 3.x

‎.github/workflows/test-src.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ jobs:
1818
python-version: ["3.9", "3.10", "3.11", "3.12"]
1919
steps:
2020
- uses: actions/checkout@v4
21+
- uses: oven-sh/setup-bun@v2
22+
with:
23+
bun-version: latest
2124
- name: Use Python ${{ matrix.python-version }}
2225
uses: actions/setup-python@v5
2326
with:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{% pyscript_setup config='{"interpreter":"/static/pyodide/pyodide.mjs"}' %}

‎docs/src/about/code.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
If you plan to make code changes to this repository, you will need to install the following dependencies first:
2020

2121
- [Python 3.9+](https://www.python.org/downloads/)
22+
- [Bun](https://bun.sh/)
2223
- [Git](https://git-scm.com/downloads)
2324

2425
Once done, you should clone this repository:

‎docs/src/reference/template-tag.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,3 +391,17 @@ You can optionally use this tag to configure the current PyScript environment. F
391391
```python
392392
{% include "../../examples/python/pyscript-setup-config-object.py" %}
393393
```
394+
395+
??? question "Can I use a local interpreter for PyScript?"
396+
397+
Yes, you can set up a local interpreter by following PyScript's [standard documentation](https://docs.pyscript.net/latest/user-guide/offline/#local-pyodide-packages).
398+
399+
To summarize,
400+
401+
1. Download the latest Pyodide bundle from the [Pyodide GitHub releases page](https://github.com/pyodide/pyodide/releases) (for example `pyodide-0.26.3.tar.bz2`).
402+
2. Extract the contents of the bundle to your project's static files.
403+
3. Configure your `#!jinja {% pyscript_setup %}` template tag to use `pyodide` as an interpreter.
404+
405+
```jinja linenums="0"
406+
{% include "../../examples/html/pyscript-setup-local-interpreter.html" %}
407+
```

‎noxfile.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,14 @@ def test_python(session: Session) -> None:
3131
settings_files = glob(settings_glob)
3232
assert settings_files, f"No Django settings files found at '{settings_glob}'!"
3333
for settings_file in settings_files:
34-
settings_module = settings_file.strip(".py").replace("/", ".").replace("\\", ".")
34+
settings_module = (
35+
settings_file.strip(".py").replace("/", ".").replace("\\", ".")
36+
)
3537
session.run(
3638
"python",
3739
"manage.py",
3840
"test",
3941
*posargs,
40-
"-v",
41-
"2",
4242
"--settings",
4343
settings_module,
4444
)
@@ -62,8 +62,8 @@ def test_style(session: Session) -> None:
6262
def test_javascript(session: Session) -> None:
6363
install_requirements_file(session, "test-env")
6464
session.chdir(ROOT_DIR / "src" / "js")
65-
session.run("python", "-m", "nodejs.npm", "install", external=True)
66-
session.run("python", "-m", "nodejs.npm", "run", "check")
65+
session.run("bun", "install", external=True)
66+
session.run("bun", "run", "check", external=True)
6767

6868

6969
def install_requirements_file(session: Session, name: str) -> None:

‎pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[build-system]
2-
requires = ["setuptools>=42", "wheel", "nodejs-bin==18.4.0a4"]
2+
requires = ["setuptools>=42", "wheel"]
33
build-backend = "setuptools.build_meta"
44

55
[tool.mypy]

‎requirements/test-env.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,3 @@ twisted
33
channels[daphne]>=4.0.0
44
tblib
55
whitenoise
6-
nodejs-bin==18.4.0a4

‎setup.py

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
from __future__ import annotations, print_function
22

33
import shutil
4+
import subprocess
45
import sys
56
import traceback
6-
from distutils import log
7+
from logging import getLogger
78
from pathlib import Path
89

9-
from nodejs import npm
1010
from setuptools import find_namespace_packages, setup
1111
from setuptools.command.develop import develop
1212
from setuptools.command.sdist import sdist
1313

14+
log = getLogger(__name__)
15+
1416
# -----------------------------------------------------------------------------
1517
# Basic Constants
1618
# -----------------------------------------------------------------------------
@@ -97,19 +99,44 @@
9799
# ----------------------------------------------------------------------------
98100
# Build Javascript
99101
# ----------------------------------------------------------------------------
102+
def copy_js_files(source_dir: Path, destination: Path) -> None:
103+
if destination.exists():
104+
shutil.rmtree(destination)
105+
destination.mkdir()
106+
107+
for file in source_dir.iterdir():
108+
if file.is_file():
109+
shutil.copy(file, destination / file.name)
110+
else:
111+
copy_js_files(file, destination / file.name)
112+
113+
100114
def build_javascript_first(build_cls: type):
101115
class Command(build_cls):
102116
def run(self):
103117

104118
log.info("Installing Javascript...")
105-
result = npm.call(["install"], cwd=str(js_dir))
119+
result = subprocess.run(
120+
["bun", "install"], cwd=str(js_dir), check=True
121+
).returncode
106122
if result != 0:
107123
log.error(traceback.format_exc())
108124
log.error("Failed to install Javascript")
109125
raise RuntimeError("Failed to install Javascript")
110126

111127
log.info("Building Javascript...")
112-
result = npm.call(["run", "build"], cwd=str(js_dir))
128+
result = subprocess.run(
129+
[
130+
"bun",
131+
"build",
132+
"./src/index.tsx",
133+
"--outfile",
134+
str(static_dir / "client.js"),
135+
"--minify",
136+
],
137+
cwd=str(js_dir),
138+
check=True,
139+
).returncode
113140
if result != 0:
114141
log.error(traceback.format_exc())
115142
log.error("Failed to build Javascript")
@@ -118,18 +145,12 @@ def run(self):
118145
log.info("Copying @pyscript/core distribution")
119146
pyscript_dist = js_dir / "node_modules" / "@pyscript" / "core" / "dist"
120147
pyscript_static_dir = static_dir / "pyscript"
121-
if not pyscript_static_dir.exists():
122-
pyscript_static_dir.mkdir()
123-
for file in pyscript_dist.iterdir():
124-
shutil.copy(file, pyscript_static_dir / file.name)
148+
copy_js_files(pyscript_dist, pyscript_static_dir)
125149

126150
log.info("Copying Morphdom distribution")
127151
morphdom_dist = js_dir / "node_modules" / "morphdom" / "dist"
128152
morphdom_static_dir = static_dir / "morphdom"
129-
if not morphdom_static_dir.exists():
130-
morphdom_static_dir.mkdir()
131-
for file in morphdom_dist.iterdir():
132-
shutil.copy(file, morphdom_static_dir / file.name)
153+
copy_js_files(morphdom_dist, morphdom_static_dir)
133154

134155
log.info("Successfully built Javascript")
135156
super().run()

‎src/js/bun.lockb

99 KB
Binary file not shown.

‎src/js/eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default [{}];

‎src/js/package-lock.json

Lines changed: 0 additions & 3470 deletions
This file was deleted.

‎src/js/package.json

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,19 @@
11
{
2-
"description": "ReactPy-Django Client",
3-
"main": "src/index.tsx",
42
"type": "module",
53
"scripts": {
6-
"build": "rollup --config",
74
"format": "prettier --write . && eslint --fix",
85
"check": "prettier --check . && eslint"
96
},
107
"devDependencies": {
11-
"@rollup/plugin-commonjs": "^28.0.1",
12-
"@rollup/plugin-node-resolve": "^15.3.0",
13-
"@rollup/plugin-replace": "^6.0.1",
148
"@types/react": "^18.2.48",
159
"@types/react-dom": "^18.2.18",
16-
"eslint": "^8.38.0",
17-
"eslint-plugin-react": "^7.32.2",
18-
"prettier": "^3.3.3",
19-
"rollup": "^4.24.0",
20-
"typescript": "^5.6.3"
10+
"eslint": "^9.13.0",
11+
"eslint-plugin-react": "^7.37.1",
12+
"prettier": "^3.3.3"
2113
},
2214
"dependencies": {
23-
"@pyscript/core": "^0.6.7",
15+
"@pyscript/core": "^0.5",
2416
"@reactpy/client": "^0.3.1",
25-
"@rollup/plugin-typescript": "^12.1.1",
26-
"morphdom": "^2.7.4",
27-
"tslib": "^2.8.0"
17+
"morphdom": "^2.7.4"
2818
}
2919
}

‎src/js/rollup.config.mjs

Lines changed: 0 additions & 23 deletions
This file was deleted.

‎src/js/tsconfig.json

Lines changed: 0 additions & 13 deletions
This file was deleted.

‎src/reactpy_django/components.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,6 @@ def _view_to_iframe(
215215
{
216216
"src": reverse("reactpy:view_to_iframe", args=[dotted_path]) + query_string,
217217
"style": {"border": "none"},
218-
"onload": 'javascript:(function(o){o.style.height=o.contentWindow.document.body.scrollHeight+"px";}(this));',
219218
"loading": "lazy",
220219
}
221220
| extra_props

‎src/reactpy_django/pyscript/layout_handler.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def __init__(self, uuid):
1414
self.uuid = uuid
1515

1616
@staticmethod
17-
def apply_update(update, root_model):
17+
def update_model(update, root_model):
1818
"""Apply an update ReactPy's internal DOM model."""
1919
from jsonpointer import set_pointer
2020

@@ -23,21 +23,22 @@ def apply_update(update, root_model):
2323
else:
2424
root_model.update(update["model"])
2525

26-
def render(self, layout, model):
26+
def render_html(self, layout, model):
2727
"""Submit ReactPy's internal DOM model into the HTML DOM."""
28-
import js
2928
from pyscript.js_modules import morphdom
3029

30+
import js
31+
3132
# Create a new container to render the layout into
3233
container = js.document.getElementById(f"pyscript-{self.uuid}")
33-
temp_container = container.cloneNode(False)
34-
self.build_element_tree(layout, temp_container, model)
34+
temp_root_container = container.cloneNode(False)
35+
self.build_element_tree(layout, temp_root_container, model)
3536

3637
# Use morphdom to update the DOM
37-
morphdom.default(container, temp_container)
38+
morphdom.default(container, temp_root_container)
3839

3940
# Remove the cloned container to prevent memory leaks
40-
temp_container.remove()
41+
temp_root_container.remove()
4142

4243
def build_element_tree(self, layout, parent, model):
4344
"""Recursively build an element tree, starting from the root component."""
@@ -131,8 +132,8 @@ async def run(self, workspace_function):
131132
self.delete_old_workspaces()
132133
root_model: dict = {}
133134

134-
async with Layout(workspace_function()) as layout:
135+
async with Layout(workspace_function()) as root_layout:
135136
while True:
136-
update = await layout.render()
137-
self.apply_update(update, root_model)
138-
self.render(layout, root_model)
137+
update = await root_layout.render()
138+
self.update_model(update, root_model)
139+
self.render_html(root_layout, root_model)

‎src/reactpy_django/templatetags/reactpy.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,11 @@ def pyscript_setup(
226226
extra_py: Dependencies that need to be loaded on the page for \
227227
your PyScript components. Each dependency must be contained \
228228
within it's own string and written in Python requirements file syntax.
229-
229+
230230
Kwargs:
231231
extra_js: A JSON string or Python dictionary containing a vanilla \
232232
JavaScript module URL and the `name: str` to access it within \
233-
`pyscript.js_modules.*`.
233+
`pyscript.js_modules.*`.
234234
config: A JSON string or Python dictionary containing PyScript \
235235
configuration values.
236236
"""

‎tests/test_app/__init__.py

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,59 @@
11
import shutil
2+
import subprocess
23
from pathlib import Path
34

4-
from nodejs import npm
5-
65
# Make sure the JS is always re-built before running the tests
76
js_dir = Path(__file__).parent.parent.parent / "src" / "js"
8-
assert npm.call(["install"], cwd=str(js_dir)) == 0
9-
assert npm.call(["run", "build"], cwd=str(js_dir)) == 0
7+
static_dir = (
8+
Path(__file__).parent.parent.parent
9+
/ "src"
10+
/ "reactpy_django"
11+
/ "static"
12+
/ "reactpy_django"
13+
)
14+
assert subprocess.run(["bun", "install"], cwd=str(js_dir), check=True).returncode == 0
15+
assert (
16+
subprocess.run(
17+
["bun", "build", "./src/index.tsx", "--outfile", str(static_dir / "client.js")],
18+
cwd=str(js_dir),
19+
check=True,
20+
).returncode
21+
== 0
22+
)
23+
24+
25+
# Make sure the test environment is always using the latest JS
26+
def copy_js_files(source_dir: Path, destination: Path) -> None:
27+
if destination.exists():
28+
shutil.rmtree(destination)
29+
destination.mkdir()
30+
31+
for file in source_dir.iterdir():
32+
if file.is_file():
33+
shutil.copy(file, destination / file.name)
34+
else:
35+
copy_js_files(file, destination / file.name)
1036

11-
# Make sure the current PyScript distribution is always available
12-
pyscript_dist = js_dir / "node_modules" / "@pyscript" / "core" / "dist"
13-
pyscript_static_dir = (
37+
38+
# Copy PyScript
39+
copy_js_files(
40+
js_dir / "node_modules" / "@pyscript" / "core" / "dist",
1441
Path(__file__).parent.parent.parent
1542
/ "src"
1643
/ "reactpy_django"
1744
/ "static"
1845
/ "reactpy_django"
19-
/ "pyscript"
46+
/ "pyscript",
2047
)
21-
if not pyscript_static_dir.exists():
22-
pyscript_static_dir.mkdir()
23-
for file in pyscript_dist.iterdir():
24-
shutil.copy(file, pyscript_static_dir / file.name)
25-
26-
# Make sure the current Morphdom distrubiton is always available
27-
morphdom_dist = js_dir / "node_modules" / "morphdom" / "dist"
28-
morphdom_static_dir = (
48+
49+
50+
# Copy MorphDOM
51+
copy_js_files(
52+
js_dir / "node_modules" / "morphdom" / "dist",
2953
Path(__file__).parent.parent.parent
3054
/ "src"
3155
/ "reactpy_django"
3256
/ "static"
3357
/ "reactpy_django"
34-
/ "morphdom"
58+
/ "morphdom",
3559
)
36-
if not morphdom_static_dir.exists():
37-
morphdom_static_dir.mkdir()
38-
for file in morphdom_dist.iterdir():
39-
shutil.copy(file, morphdom_static_dir / file.name)

0 commit comments

Comments
 (0)
Please sign in to comment.