Skip to content

Commit 825fde6

Browse files
committed
Use morphdom to modify the DOM
1 parent b22ce44 commit 825fde6

File tree

8 files changed

+60
-13
lines changed

8 files changed

+60
-13
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# ReactPy-Django Build Artifacts
22
src/reactpy_django/static/reactpy_django/client.js
33
src/reactpy_django/static/reactpy_django/pyscript
4+
src/reactpy_django/static/reactpy_django/morphdom
45

56
# Django #
67
logs

setup.py

+8
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@ def run(self):
123123
for file in pyscript_dist.iterdir():
124124
shutil.copy(file, pyscript_static_dir / file.name)
125125

126+
log.info("Copying Morphdom distribution")
127+
morphdom_dist = js_dir / "node_modules" / "morphdom" / "dist"
128+
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)
133+
126134
log.info("Successfully built Javascript")
127135
super().run()
128136

src/js/package-lock.json

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/js/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@pyscript/core": "^0.4.48",
2424
"@reactpy/client": "^0.3.1",
2525
"@rollup/plugin-typescript": "^11.1.6",
26+
"morphdom": "^2.7.3",
2627
"tslib": "^2.6.2"
2728
}
2829
}

src/reactpy_django/pyscript/layout_handler.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,18 @@ def apply_update(update, root_model):
2626
def render(self, layout, model):
2727
"""Submit ReactPy's internal DOM model into the HTML DOM."""
2828
import js
29+
from pyscript.js_modules import morphdom
2930

31+
# Create a new container to render the layout into
3032
container = js.document.getElementById(f"pyscript-{self.uuid}")
33+
temp_container = container.cloneNode(False)
34+
self.build_element_tree(layout, temp_container, model)
3135

32-
# FIXME: The current implementation completely recreates the DOM on every render.
33-
# This is not ideal, and should be optimized in the future.
34-
container.innerHTML = ""
35-
self.build_element_tree(layout, container, model)
36+
# Use morphdom to update the DOM
37+
morphdom.default(container, temp_container)
38+
39+
# Remove the cloned container to prevent memory leaks
40+
temp_container.remove()
3641

3742
def build_element_tree(self, layout, parent, model):
3843
"""Recursively build an element tree, starting from the root component."""

src/reactpy_django/templates/reactpy/pyscript_setup.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
{% if not reactpy_debug_mode %}
55
<link rel="stylesheet" href="{% static 'reactpy_django/pyscript-hide-debug.css' %}" />
66
{% endif %}
7-
<script type="module" async src="{% static 'reactpy_django/pyscript/core.js' %}"></script>
7+
<script type="module" async crossorigin="anonymous" src="{% static 'reactpy_django/pyscript/core.js' %}"></script>
88
<py-script async config='{{pyscript_config}}'>{{pyscript_layout_handler}}</py-script>

src/reactpy_django/utils.py

+18-7
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from django.db.models.query import QuerySet
2626
from django.http import HttpRequest, HttpResponse
2727
from django.template import engines
28+
from django.templatetags.static import static
2829
from django.utils.encoding import smart_str
2930
from django.views import View
3031
from reactpy import vdom_to_html
@@ -62,13 +63,7 @@
6263
PYSCRIPT_LAYOUT_HANDLER = (
6364
Path(__file__).parent / "pyscript" / "layout_handler.py"
6465
).read_text(encoding="utf-8")
65-
PYSCRIPT_DEFAULT_CONFIG = {
66-
"packages": [
67-
f"reactpy=={reactpy.__version__}",
68-
f"jsonpointer=={jsonpointer.__version__}",
69-
"ssl",
70-
]
71-
}
66+
PYSCRIPT_DEFAULT_CONFIG: dict[str, Any] = {}
7267

7368

7469
async def render_view(
@@ -503,6 +498,22 @@ def render_pyscript_template(file_paths: Sequence[str], uuid: str, root: str):
503498

504499
def extend_pyscript_config(config: dict | str, extra_packages: Sequence) -> str:
505500
"""Extends ReactPy's default PyScript config with user provided values."""
501+
if not PYSCRIPT_DEFAULT_CONFIG:
502+
# Need to perform this lazily in order to wait for static files to be available
503+
PYSCRIPT_DEFAULT_CONFIG.update(
504+
{
505+
"packages": [
506+
f"reactpy=={reactpy.__version__}",
507+
f"jsonpointer=={jsonpointer.__version__}",
508+
"ssl",
509+
],
510+
"js_modules": {
511+
"main": {
512+
static("reactpy_django/morphdom/morphdom-esm.js"): "morphdom"
513+
}
514+
},
515+
}
516+
)
506517
pyscript_config = deepcopy(PYSCRIPT_DEFAULT_CONFIG)
507518
pyscript_config["packages"].extend(extra_packages)
508519
if config and isinstance(config, str):

tests/test_app/__init__.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
assert npm.call(["install"], cwd=str(js_dir)) == 0
99
assert npm.call(["run", "build"], cwd=str(js_dir)) == 0
1010

11-
# Make sure the the PyScript distribution is always available
11+
# Make sure the current PyScript distribution is always available
1212
pyscript_dist = js_dir / "node_modules" / "@pyscript" / "core" / "dist"
1313
pyscript_static_dir = (
1414
Path(__file__).parent.parent.parent
@@ -22,3 +22,18 @@
2222
pyscript_static_dir.mkdir()
2323
for file in pyscript_dist.iterdir():
2424
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 = (
29+
Path(__file__).parent.parent.parent
30+
/ "src"
31+
/ "reactpy_django"
32+
/ "static"
33+
/ "reactpy_django"
34+
/ "morphdom"
35+
)
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)