Skip to content

Commit

Permalink
Functional preloading implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Archmonger committed Sep 15, 2023
1 parent 14b92ee commit 4432388
Show file tree
Hide file tree
Showing 13 changed files with 196 additions and 65 deletions.
101 changes: 89 additions & 12 deletions src/js/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-replace": "^5.0.2",
"@types/react": "^17.0",
"@types/react-dom": "^17.0",
"typescript": "^4.9.5",
"prettier": "^3.0.2",
"rollup": "^3.28.1"
},
Expand Down
2 changes: 1 addition & 1 deletion src/js/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mount } from "@reactpy/client";
import { mount } from "./mount";
import { ReactPyDjangoClient } from "./client";

export function mountComponent(
Expand Down
18 changes: 18 additions & 0 deletions src/js/src/mount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from "react";
import { render } from "react-dom";
import { Layout } from "@reactpy/client/src/components";
import { ReactPyDjangoClient } from "./client";

export function mount(element: HTMLElement, client: ReactPyDjangoClient): void {
const preloadElement = document.getElementById(element.id + "-preload");
if (preloadElement) {
element.hidden = true;
client.onMessage("layout-update", ({ path, model }) => {
if (preloadElement) {
preloadElement.replaceWith(element);
element.hidden = false;
}
});
}
render(<Layout client={client} />, element);
}
9 changes: 9 additions & 0 deletions src/js/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,14 @@
"target": "ES2017",
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
},
"paths": {
"react": [
"./node_modules/preact/compat/"
],
"react-dom": [
"./node_modules/preact/compat/"
]
}
}
5 changes: 5 additions & 0 deletions src/reactpy_django/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,8 @@
"REACTPY_RECONNECT_BACKOFF_MULTIPLIER",
1.25, # Default to 25% backoff per connection attempt
)
REACTPY_PRELOAD_DEFAULT: bool = getattr(
settings,
"REACTPY_PRELOAD_DEFAULT",
False,
)
2 changes: 1 addition & 1 deletion src/reactpy_django/templates/reactpy/component.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
{% endif %}

{% if not reactpy_failure %}
{% if reactpy_preload %}<div id="{{ reactpy_uuid }}-preload">{{ reactpy_preload|safe }}</div>{% endif %}
{% if reactpy_preload_html %}<div id="{{ reactpy_uuid }}-preload">{{ reactpy_preload_html|safe }}</div>{% endif %}
<div id="{{ reactpy_uuid }}" class="{{ reactpy_class }}"></div>
<script type="module" crossorigin="anonymous">
import { mountComponent } from "{% static 'reactpy_django/client.js' %}";
Expand Down
23 changes: 15 additions & 8 deletions src/reactpy_django/templatetags/reactpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from django.http import HttpRequest
from django.urls import NoReverseMatch, reverse
from reactpy.backend.hooks import ConnectionContext
from reactpy.backend.types import Connection
from reactpy.core.types import ComponentConstructor
from reactpy.utils import vdom_to_html

Expand All @@ -35,7 +36,7 @@ def component(
dotted_path: str,
*args,
host: str | None = None,
preload: str = "False",
preload: str = str(config.REACTPY_PRELOAD_DEFAULT),
**kwargs,
):
"""This tag is used to embed an existing ReactPy component into your HTML template.
Expand Down Expand Up @@ -71,8 +72,7 @@ def component(
class_ = kwargs.pop("class", "")
component_has_args = args or kwargs
user_component: ComponentConstructor | None = None
preload = strtobool(preload)
_preloaded_html = ""
_preload_html = ""

# Validate the host
if host and config.REACTPY_DEBUG_MODE:
Expand Down Expand Up @@ -108,8 +108,8 @@ def component(
)
return failure_context(dotted_path, e)

# Preload if requested
if preload:
# Preload the component, if requested
if strtobool(preload):
if not is_local:
msg = "Cannot preload non-local components."
_logger.error(msg)
Expand All @@ -125,7 +125,7 @@ def component(
"request context processor in settings.py:TEMPLATES['OPTIONS']['context_processors']?"
_logger.error(msg)
return failure_context(dotted_path, ComponentDoesNotExistError(msg))
_preloaded_html = preload_component(user_component, args, kwargs, request)
_preload_html = preload_component(user_component, args, kwargs, request)

# Return the template rendering context
return {
Expand All @@ -141,7 +141,7 @@ def component(
"reactpy_reconnect_max_interval": config.REACTPY_RECONNECT_MAX_INTERVAL,
"reactpy_reconnect_backoff_multiplier": config.REACTPY_RECONNECT_BACKOFF_MULTIPLIER,
"reactpy_reconnect_max_retries": config.REACTPY_RECONNECT_MAX_RETRIES,
"reactpy_preload": _preloaded_html,
"reactpy_preload_html": _preload_html,
}


Expand Down Expand Up @@ -175,7 +175,14 @@ def preload_component(
user_component: ComponentConstructor, args, kwargs, request: HttpRequest
):
with SyncLayout(
ConnectionContext(user_component(*args, **kwargs), value=request)
ConnectionContext(
user_component(*args, **kwargs),
value=Connection(
scope=getattr(request, "scope", {}),
location=request.path,
carrier=request,
),
)
) as layout:
vdom_tree = layout.render()["model"]

Expand Down
39 changes: 0 additions & 39 deletions tests/test_app/components.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import asyncio
import inspect
from pathlib import Path
from time import sleep

import reactpy_django
from channels.db import database_sync_to_async
Expand Down Expand Up @@ -618,41 +617,3 @@ def broken_postprocessor_query():
mtm = relational_parent.data.many_to_many.all()

return html.div(f"This should have failed! Something went wrong: {mtm}")


@component
def preload_string():
scope = reactpy_django.hooks.use_scope()

sleep(1)
return (
"Render Stage: Final"
if scope.get("type") == "websocket"
else "Render Stage: Preload"
)


@component
def preload_vdom():
scope = reactpy_django.hooks.use_scope()

if scope.get("type") == "http":
return html.div("Render Stage: Preload")

sleep(1)
return html.div("Render Stage: Final")


@component
def preload_component():
scope = reactpy_django.hooks.use_scope()

@component
def inner(value):
return html.div(value)

if scope.get("type") == "http":
return inner("Render Stage: Preload")

sleep(1)
return inner("Render Stage: Final")
Empty file.
Loading

0 comments on commit 4432388

Please sign in to comment.