Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ Additionally, you can pass in `args` and `kwargs` into your component function.
<!--html-code-start-->

```jinja
{% load reactpy %}
<!DOCTYPE html>
{% load reactpy %}
<html>
<body>
{% component "example_project.my_app.components.hello_world" recipient="World" %}
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/html/pyscript_component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% load reactpy %}
<!DOCTYPE html>
{% load reactpy %}
<html>

<head>
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/html/pyscript_local_import.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% load reactpy %}
<!DOCTYPE html>
{% load reactpy %}
<html>

<head>
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/html/pyscript_multiple_files.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% load reactpy %}
<!DOCTYPE html>
{% load reactpy %}
<html>

<head>
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/html/pyscript_setup_extra_js_object.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% load reactpy %}
<!DOCTYPE html>
{% load reactpy %}
<html>

<head>
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/html/pyscript_setup_extra_js_string.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% load reactpy %}
<!DOCTYPE html>
{% load reactpy %}
<html>

<head>
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/html/pyscript_ssr_parent.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% load reactpy %}
<!DOCTYPE html>
{% load reactpy %}
<html>

<head>
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/html/pyscript_tag.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% load reactpy %}
<!DOCTYPE html>
{% load reactpy %}
<html>

<head>
Expand Down
4 changes: 1 addition & 3 deletions docs/examples/python/pyscript_component_initial_object.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from reactpy import component, html

from reactpy_django.components import pyscript_component
from reactpy import component, html, pyscript_component


@component
Expand Down
4 changes: 1 addition & 3 deletions docs/examples/python/pyscript_component_initial_string.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from reactpy import component, html

from reactpy_django.components import pyscript_component
from reactpy import component, html, pyscript_component


@component
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from reactpy import component, html

from reactpy_django.components import pyscript_component
from reactpy import component, html, pyscript_component


@component
Expand Down
4 changes: 1 addition & 3 deletions docs/examples/python/pyscript_component_root.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from reactpy import component, html

from reactpy_django.components import pyscript_component
from reactpy import component, html, pyscript_component


@component
Expand Down
8 changes: 4 additions & 4 deletions docs/examples/python/pyscript_ssr_parent.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from reactpy import component, html

from reactpy_django.components import pyscript_component
from reactpy import component, html, pyscript_component


@component
def server_side_component():
return html.div(
"This text is from my server-side component",
pyscript_component("./example_project/my_app/components/root.py"),
pyscript_component(
"./example_project/my_app/components/root.py",
),
)
6 changes: 1 addition & 5 deletions docs/examples/python/pyscript_tag.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from reactpy import component, html

from reactpy_django.html import pyscript

example_source_code = """
import js

Expand All @@ -11,6 +9,4 @@

@component
def server_side_component():
return html.div(
pyscript(example_source_code.strip()),
)
return html.py_script(example_source_code.strip())
2 changes: 1 addition & 1 deletion docs/examples/python/use_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
def my_component():
location = use_location()

return html.div(location.pathname + location.search)
return html.div(location.path + location.query_string)
2 changes: 1 addition & 1 deletion docs/overrides/homepage_examples/add_interactivity.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def searchable_video_list(videos):
search_text, set_search_text = use_state("")
found_videos = filter_videos(videos, search_text)

return html._(
return html(
search_input(
{"onChange": lambda event: set_search_text(event["target"]["value"])},
value=search_text,
Expand Down
2 changes: 1 addition & 1 deletion docs/src/reference/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ This allows you to embedded any number of client-side PyScript components within
| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `#!python *file_paths` | `#!python str` | File path to your client-side component. If multiple paths are provided, the contents are automatically merged. | N/A |
| `#!python initial` | `#!python str | VdomDict | ComponentType` | The initial HTML that is displayed prior to the PyScript component loads. This can either be a string containing raw HTML, a `#!python reactpy.html` snippet, or a non-interactive component. | `#!python ""` |
| `#!python initial` | `#!python str | VdomDict | Component` | The initial HTML that is displayed prior to the PyScript component loads. This can either be a string containing raw HTML, a `#!python reactpy.html` snippet, or a non-interactive component. | `#!python ""` |
| `#!python root` | `#!python str` | The name of the root component function. | `#!python "root"` |

<!--pyscript-setup-required-start-->
Expand Down
2 changes: 1 addition & 1 deletion docs/src/reference/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ Shortcut that returns the browser's current `#!python Location`.

| Type | Description |
| --- | --- |
| `#!python Location` | An object containing the current URL's `#!python pathname` and `#!python search` query. |
| `#!python Location` | An object containing the current URL's `#!python path` and `#!python query_string` query. |

---

Expand Down
4 changes: 2 additions & 2 deletions docs/src/reference/template-tag.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ Each component loaded via this template tag will receive a dedicated WebSocket c
=== "my_template.html"

```jinja
{% load reactpy %}
<!DOCTYPE html>
{% load reactpy %}
<html>
<body>
<h1>{% component "example_project.my_app.components.my_title" %}</h1>
Expand Down Expand Up @@ -191,7 +191,7 @@ Your Python component file will be directly loaded into the browser. It must hav
| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `#!python *file_paths` | `#!python str` | File path to your client-side component. If multiple paths are provided, the contents are automatically merged. | N/A |
| `#!python initial` | `#!python str | VdomDict | ComponentType` | The initial HTML that is displayed prior to the PyScript component loads. This can either be a string containing raw HTML, a `#!python reactpy.html` snippet, or a non-interactive component. | `#!python ""` |
| `#!python initial` | `#!python str | VdomDict | Component` | The initial HTML that is displayed prior to the PyScript component loads. This can either be a string containing raw HTML, a `#!python reactpy.html` snippet, or a non-interactive component. | `#!python ""` |
| `#!python root` | `#!python str` | The name of the root component function. | `#!python "root"` |

<!--pyscript-webtypy-start-->
Expand Down
19 changes: 11 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,10 @@ classifiers = [
dependencies = [
"channels>=4.0.0",
"django>=4.2.0",
"reactpy>=1.1.0, <2.0.0",
"reactpy-router>=1.0.3, <2.0.0",
"reactpy>=2.0.0b10, <3.0.0",
"reactpy-router>2.0.0",
"dill>=0.3.5",
"orjson>=3.6.0",
"nest_asyncio>=1.5.0",
"typing_extensions",
]
dynamic = ["version"]
Expand All @@ -68,6 +67,7 @@ artifacts = ["/src/reactpy_django/static/"]

[tool.hatch.metadata]
license-files = { paths = ["LICENSE.md"] }
allow-direct-references = true

[tool.hatch.envs.default]
installer = "uv"
Expand All @@ -76,8 +76,8 @@ installer = "uv"
commands = [
"bun install --cwd src/js",
'bun build src/js/src/index.ts --outdir="src/reactpy_django/static/reactpy_django/" --minify --sourcemap=linked',
'cd src/build_scripts && python copy_dir.py "src/js/node_modules/@pyscript/core/dist" "src/reactpy_django/static/reactpy_django/pyscript"',
'cd src/build_scripts && python copy_dir.py "src/js/node_modules/morphdom/dist" "src/reactpy_django/static/reactpy_django/morphdom"',
'cd src/scripts && python copy_dir.py "src/js/node_modules/@pyscript/core/dist" "src/reactpy_django/static/reactpy_django/pyscript"',
'cd src/scripts && python copy_dir.py "src/js/node_modules/morphdom/dist" "src/reactpy_django/static/reactpy_django/morphdom"',
]
artifacts = []

Expand All @@ -96,18 +96,18 @@ extra-dependencies = [
"servestatic",
"django-bootstrap5",
"decorator",

"uvicorn[standard]",
]
matrix-name-format = "{variable}-{value}"

# Django 4.2
[[tool.hatch.envs.hatch-test.matrix]]
python = ["3.9", "3.10", "3.11", "3.12"]
python = ["3.11", "3.12"]
django = ["4.2"]

# Django 5.0
[[tool.hatch.envs.hatch-test.matrix]]
python = ["3.10", "3.11", "3.12"]
python = ["3.11", "3.12"]
django = ["5.0"]

# Django 5.1
Expand Down Expand Up @@ -245,6 +245,9 @@ lint.extend-ignore = [
"S403", # `dill` module is possibly insecure
"S301", # `dill` deserialization is possibly insecure unless using trusted data
"RUF029", # Function is declared async but doesn't contain await expression
"DOC201", # `return` is not documented in docstring
"DOC501", # `Exception` is not documented in docstring
"S308", # Django `mark_safe` may expose cross-site scripting vulnerabilities
]
lint.preview = true
lint.isort.known-first-party = ["reactpy_django", "test_app", "example"]
Expand Down
Binary file modified src/js/bun.lockb
Binary file not shown.
28 changes: 12 additions & 16 deletions src/js/package.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
{
"dependencies": {
"@pyscript/core": "^0.6",
"@reactpy/client": "^0.3.2",
"event-to-object": "^0.1.2",
"morphdom": "^2.7.4",
"preact": "^10.26.9",
"react": "npm:@preact/compat@17.1.2",
"react-dom": "npm:@preact/compat@17.1.2"
"@pyscript/core": "^0.7.17",
"@reactpy/client": "^1.1.0",
"morphdom": "^2.7.8"
},
"devDependencies": {
"@eslint/js": "^9.29.0",
"bun-types": "^0.5.0",
"eslint": "^9.13.0",
"globals": "^16.2.0",
"prettier": "^3.3.3",
"typescript": "^5.8.3",
"typescript-eslint": "^8.34.0"
"@eslint/js": "^10.0.1",
"bun-types": "^1.3.9",
"eslint": "^10.0.0",
"globals": "^17.3.0",
"prettier": "^3.8.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.55.0"
},
"license": "MIT",
"name": "@reactpy-django/app",
"scripts": {
"check": "prettier --check . && eslint",
"format": "prettier --write . && eslint --fix"
},
"type": "module"
}
}
40 changes: 28 additions & 12 deletions src/js/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,43 @@
import {
BaseReactPyClient,
type ReactPyClient,
createReconnectingWebSocket,
type GenericReactPyClientProps,
type ReactPyClientInterface,
type ReactPyModule,
type ReactPyUrls,
} from "@reactpy/client";
import { createReconnectingWebSocket } from "./utils";
import type { ReactPyDjangoClientProps, ReactPyUrls } from "./types";

export class ReactPyDjangoClient
extends BaseReactPyClient
implements ReactPyClient
implements ReactPyClientInterface
{
urls: ReactPyUrls;
socket: { current?: WebSocket };
mountElement: HTMLElement | null = null;
mountElement: HTMLElement;
prerenderElement: HTMLElement | null = null;
offlineElement: HTMLElement | null = null;
private readonly messageQueue: any[] = [];

constructor(props: ReactPyDjangoClientProps) {
constructor(props: GenericReactPyClientProps) {
super();

this.urls = props.urls;
this.mountElement = props.mountElement;
this.prerenderElement = props.prerenderElement;
this.offlineElement = props.offlineElement;
this.prerenderElement = document.getElementById(
props.mountElement.id + "-prerender",
);
this.offlineElement = document.getElementById(
props.mountElement.id + "-offline",
);

this.socket = createReconnectingWebSocket({
url: this.urls.componentUrl,
readyPromise: this.ready,
...props.reconnectOptions,
// onMessage: Use standard ReactPy message routing
onMessage: async ({ data }) => this.handleIncoming(JSON.parse(data)),
// onClose: If offlineElement exists, show it and hide the mountElement/prerenderElement
onClose: () => {
// If offlineElement exists, show it and hide the mountElement/prerenderElement
if (this.prerenderElement) {
this.prerenderElement.remove();
this.prerenderElement = null;
Expand All @@ -38,8 +47,8 @@ export class ReactPyDjangoClient
this.offlineElement.hidden = false;
}
},
// onOpen: If offlineElement exists, hide it and show the mountElement
onOpen: () => {
// If offlineElement exists, hide it and show the mountElement
if (this.offlineElement && this.mountElement) {
this.offlineElement.hidden = true;
this.mountElement.hidden = false;
Expand All @@ -49,10 +58,17 @@ export class ReactPyDjangoClient
}

sendMessage(message: any): void {
this.socket.current?.send(JSON.stringify(message));
if (
this.socket.current &&
this.socket.current.readyState === WebSocket.OPEN
) {
this.socket.current.send(JSON.stringify(message));
} else {
this.messageQueue.push(message);
}
}

loadModule(moduleName: string): Promise<ReactPyModule> {
return import(`${this.urls.jsModules}/${moduleName}`);
return import(`${this.urls.jsModulesPath}${moduleName}`);
}
}
Loading