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 e3d0866

Browse files
authoredJan 29, 2024
Refactor repo to match ReactPy Router (#216)
1 parent c3eee76 commit e3d0866

21 files changed

+3481
-1080
lines changed
 

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
name: Publish Develop Docs
2+
23
on:
34
push:
45
branches:
@@ -17,5 +18,6 @@ jobs:
1718
- name: Publish Develop Docs
1819
run: |
1920
git config user.name github-actions
20-
git config user.email github-actions@github.com
21+
git config user.email github-actions@github.com
22+
cd docs
2123
mike deploy --push develop

‎.github/workflows/publish-py.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
steps:
1414
- uses: actions/checkout@v3
1515
- name: Set up Python
16-
uses: actions/setup-python@v1
16+
uses: actions/setup-python@v4
1717
with:
1818
python-version: "3.x"
1919
- name: Install dependencies

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ jobs:
1818
- name: Publish ${{ github.event.release.name }} Docs
1919
run: |
2020
git config user.name github-actions
21-
git config user.email github-actions@github.com
21+
git config user.email github-actions@github.com
22+
cd docs
2223
mike deploy --push --update-aliases ${{ github.event.release.name }} latest

‎.github/workflows/test-docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ jobs:
2626
linkcheckMarkdown docs/ -v -r
2727
linkcheckMarkdown README.md -v -r
2828
linkcheckMarkdown CHANGELOG.md -v -r
29+
cd docs
2930
mkdocs build --strict
3031
- name: Check docs examples
3132
run: |
3233
pip install -r requirements/check-types.txt
3334
pip install -r requirements/check-style.txt
3435
mypy --show-error-codes docs/examples/python/
35-
black docs/examples/python/ --check
3636
ruff check docs/examples/python/

‎.github/workflows/test-src.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,4 @@ jobs:
2525
- name: Install Python Dependencies
2626
run: pip install -r requirements/test-run.txt
2727
- name: Run Tests
28-
run: |
29-
nox -s test
28+
run: nox -t test

‎CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ Using the following categories, list your changes in this order:
444444

445445
- Support for IDOM within the Django
446446

447-
[unreleased]: https://github.com/reactive-python/reactpy-django/compare/3.6.0...HEAD
447+
[Unreleased]: https://github.com/reactive-python/reactpy-django/compare/3.6.0...HEAD
448448
[3.6.0]: https://github.com/reactive-python/reactpy-django/compare/3.5.1...3.6.0
449449
[3.5.1]: https://github.com/reactive-python/reactpy-django/compare/3.5.0...3.5.1
450450
[3.5.0]: https://github.com/reactive-python/reactpy-django/compare/3.4.0...3.5.0

‎README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<a href="https://pypi.python.org/pypi/reactpy-django">
88
<img src="https://img.shields.io/pypi/v/reactpy-django.svg?label=PyPI">
99
</a>
10-
<a href="https://github.com/reactive-python/reactpy/blob/main/LICENSE">
10+
<a href="https://github.com/reactive-python/reactpy-django/blob/main/LICENSE.md">
1111
<img src="https://img.shields.io/badge/License-MIT-purple.svg">
1212
</a>
1313
<a href="https://reactive-python.github.io/reactpy-django/">

‎mkdocs.yml renamed to ‎docs/mkdocs.yml

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ nav:
2525

2626
theme:
2727
name: material
28-
custom_dir: docs/overrides
28+
custom_dir: overrides
2929
palette:
3030
- media: "(prefers-color-scheme: dark)"
3131
scheme: slate
@@ -114,12 +114,11 @@ extra_css:
114114
- assets/css/home.css
115115

116116
watch:
117-
- docs
118-
- mkdocs.yml
119-
- README.md
120-
- CHANGELOG.md
121-
- LICENSE.md
122-
- .mailmap
117+
- ../docs
118+
- ../README.md
119+
- ../CHANGELOG.md
120+
- ../LICENSE.md
121+
- ../.mailmap
123122

124123
site_name: ReactPy-Django
125124
site_author: Archmonger
@@ -133,6 +132,6 @@ This project has no affiliation to ReactJS or Meta Platforms, Inc.
133132
</div>'
134133
repo_url: https://github.com/reactive-python/reactpy-django
135134
site_url: https://reactive-python.github.io/reactpy-django
136-
repo_name: reactive-python/reactpy-django
135+
repo_name: ReactPy Django (GitHub)
137136
edit_uri: edit/main/docs/src
138-
docs_dir: docs/src
137+
docs_dir: src

‎docs/src/about/code.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,13 @@ pip install -r requirements.txt --upgrade --verbose
5252
By running the command below you can run the full test suite:
5353

5454
```bash linenums="0"
55-
nox -s test
55+
nox -t test
5656
```
5757

5858
Or, if you want to run the tests in the background:
5959

6060
```bash linenums="0"
61-
nox -s test -- --headless
61+
nox -t test -- --headless
6262
```
6363

6464
## Running Django tests

‎docs/src/about/docs.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pip install -r requirements.txt --upgrade
3434
Finally, to verify that everything is working properly, you can manually run the docs preview web server.
3535

3636
```bash linenums="0"
37+
cd docs
3738
mkdocs serve
3839
```
3940

‎noxfile.py

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,19 @@
11
from __future__ import annotations
22

3-
import re
43
from glob import glob
54
from pathlib import Path
65

7-
import nox
8-
from nox.sessions import Session
6+
from nox import Session, session
97

10-
HERE = Path(__file__).parent
11-
POSARGS_PATTERN = re.compile(r"^(\w+)\[(.+)\]$")
8+
ROOT_DIR = Path(__file__).parent
129

1310

14-
@nox.session(reuse_venv=True)
15-
def manage(session: Session) -> None:
16-
"""Run a manage.py command for tests/test_app"""
17-
session.install("-r", "requirements/test-env.txt")
18-
session.install("reactpy[stable]")
19-
session.install("-e", ".")
20-
session.chdir("tests")
21-
session.run("python", "manage.py", *session.posargs)
22-
23-
24-
@nox.session
25-
def test(session: Session) -> None:
26-
"""Run the complete test suite"""
27-
session.install("--upgrade", "pip", "setuptools", "wheel")
28-
session.notify("test_suite", posargs=session.posargs)
29-
session.notify("test_types")
30-
session.notify("test_style")
31-
32-
33-
@nox.session
34-
def test_suite(session: Session) -> None:
11+
@session(tags=["test"])
12+
def test_python(session: Session) -> None:
3513
"""Run the Python-based test suite"""
3614
install_requirements_file(session, "test-env")
3715
session.install(".[all]")
38-
session.chdir(HERE / "tests")
16+
session.chdir(ROOT_DIR / "tests")
3917
session.env["REACTPY_DEBUG_MODE"] = "1"
4018

4119
posargs = session.posargs[:]
@@ -53,9 +31,7 @@ def test_suite(session: Session) -> None:
5331
settings_files = glob(settings_glob)
5432
assert settings_files, f"No Django settings files found at '{settings_glob}'!"
5533
for settings_file in settings_files:
56-
settings_module = (
57-
settings_file.strip(".py").replace("/", ".").replace("\\", ".")
58-
)
34+
settings_module = settings_file.strip(".py").replace("/", ".").replace("\\", ".")
5935
session.run(
6036
"python",
6137
"manage.py",
@@ -68,28 +44,30 @@ def test_suite(session: Session) -> None:
6844
)
6945

7046

71-
@nox.session
47+
@session(tags=["test"])
7248
def test_types(session: Session) -> None:
7349
install_requirements_file(session, "check-types")
7450
install_requirements_file(session, "pkg-deps")
7551
session.run("mypy", "--show-error-codes", "src/reactpy_django", "tests/test_app")
7652

7753

78-
@nox.session
54+
@session(tags=["test"])
7955
def test_style(session: Session) -> None:
8056
"""Check that style guidelines are being followed"""
8157
install_requirements_file(session, "check-style")
82-
session.run(
83-
"black",
84-
".",
85-
"--check",
86-
"--extend-exclude",
87-
"/migrations/",
88-
)
8958
session.run("ruff", "check", ".")
9059

9160

61+
@session(tags=["test"])
62+
def test_javascript(session: Session) -> None:
63+
install_requirements_file(session, "test-env")
64+
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")
67+
68+
9269
def install_requirements_file(session: Session, name: str) -> None:
93-
file_path = HERE / "requirements" / f"{name}.txt"
70+
session.install("--upgrade", "pip", "setuptools", "wheel")
71+
file_path = ROOT_DIR / "requirements" / f"{name}.txt"
9472
assert file_path.exists(), f"requirements file {file_path} does not exist"
9573
session.install("-r", str(file_path))

‎requirements/check-style.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
black
21
ruff

‎setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
package = {
2626
"name": name,
2727
"python_requires": ">=3.9",
28-
"packages": find_namespace_packages(str(src_dir)),
28+
"packages": find_namespace_packages(src_dir),
2929
"package_dir": {"": "src"},
3030
"description": "It's React, but in Python. Now with Django integration.",
3131
"author": "Mark Bakhit",

‎src/js/package-lock.json

Lines changed: 3199 additions & 769 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/js/package.json

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
{
2-
"description": "ReactPy-Django Client",
3-
"main": "src/index.tsx",
4-
"type": "module",
5-
"scripts": {
6-
"build": "rollup --config",
7-
"format": "prettier --ignore-path .gitignore --write ."
8-
},
9-
"devDependencies": {
10-
"@rollup/plugin-commonjs": "^25.0.7",
11-
"@rollup/plugin-node-resolve": "^15.2.3",
12-
"@rollup/plugin-replace": "^5.0.5",
13-
"@types/react": "^18.2.48",
14-
"@types/react-dom": "^18.2.18",
15-
"prettier": "^3.2.3",
16-
"rollup": "^4.9.5",
17-
"typescript": "^5.3.3"
18-
},
19-
"dependencies": {
20-
"@reactpy/client": "^0.3.1",
21-
"@rollup/plugin-typescript": "^11.1.6",
22-
"tslib": "^2.6.2"
23-
}
2+
"description": "ReactPy-Django Client",
3+
"main": "src/index.tsx",
4+
"type": "module",
5+
"scripts": {
6+
"build": "rollup --config",
7+
"format": "prettier --write . && eslint --fix",
8+
"check": "prettier --check . && eslint"
9+
},
10+
"devDependencies": {
11+
"@rollup/plugin-commonjs": "^25.0.7",
12+
"@rollup/plugin-node-resolve": "^15.2.3",
13+
"@rollup/plugin-replace": "^5.0.5",
14+
"@types/react": "^18.2.48",
15+
"@types/react-dom": "^18.2.18",
16+
"prettier": "^3.2.3",
17+
"eslint": "^8.38.0",
18+
"eslint-plugin-react": "^7.32.2",
19+
"rollup": "^4.9.5",
20+
"typescript": "^5.3.3"
21+
},
22+
"dependencies": {
23+
"@reactpy/client": "^0.3.1",
24+
"@rollup/plugin-typescript": "^11.1.6",
25+
"tslib": "^2.6.2"
26+
}
2427
}

‎src/js/rollup.config.mjs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@ import replace from "@rollup/plugin-replace";
44
import typescript from "@rollup/plugin-typescript";
55

66
export default {
7-
input: "src/index.tsx",
8-
output: {
9-
file: "../reactpy_django/static/reactpy_django/client.js",
10-
format: "esm",
11-
},
12-
plugins: [
13-
resolve(),
14-
commonjs(),
15-
replace({
16-
"process.env.NODE_ENV": JSON.stringify("production"),
17-
}),
18-
typescript(),
19-
],
20-
onwarn: function (warning) {
21-
console.warn(warning.message);
22-
},
7+
input: "src/index.tsx",
8+
output: {
9+
file: "../reactpy_django/static/reactpy_django/client.js",
10+
format: "esm",
11+
},
12+
plugins: [
13+
resolve(),
14+
commonjs(),
15+
replace({
16+
"process.env.NODE_ENV": JSON.stringify("production"),
17+
}),
18+
typescript(),
19+
],
20+
onwarn: function (warning) {
21+
console.warn(warning.message);
22+
},
2323
};

‎src/js/src/client.ts

Lines changed: 47 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,58 @@
11
import {
2-
BaseReactPyClient,
3-
ReactPyClient,
4-
ReactPyModule,
2+
BaseReactPyClient,
3+
ReactPyClient,
4+
ReactPyModule,
55
} from "@reactpy/client";
66
import { createReconnectingWebSocket } from "./utils";
77
import { ReactPyDjangoClientProps, ReactPyUrls } from "./types";
88

99
export class ReactPyDjangoClient
10-
extends BaseReactPyClient
11-
implements ReactPyClient
10+
extends BaseReactPyClient
11+
implements ReactPyClient
1212
{
13-
urls: ReactPyUrls;
14-
socket: { current?: WebSocket };
15-
mountElement: HTMLElement | null = null;
16-
prerenderElement: HTMLElement | null = null;
17-
offlineElement: HTMLElement | null = null;
13+
urls: ReactPyUrls;
14+
socket: { current?: WebSocket };
15+
mountElement: HTMLElement | null = null;
16+
prerenderElement: HTMLElement | null = null;
17+
offlineElement: HTMLElement | null = null;
1818

19-
constructor(props: ReactPyDjangoClientProps) {
20-
super();
21-
this.urls = props.urls;
22-
this.socket = createReconnectingWebSocket({
23-
readyPromise: this.ready,
24-
url: this.urls.componentUrl,
25-
onMessage: async ({ data }) =>
26-
this.handleIncoming(JSON.parse(data)),
27-
...props.reconnectOptions,
28-
onClose: () => {
29-
// If offlineElement exists, show it and hide the mountElement/prerenderElement
30-
if (this.prerenderElement) {
31-
this.prerenderElement.remove();
32-
this.prerenderElement = null;
33-
}
34-
if (this.offlineElement) {
35-
this.mountElement.hidden = true;
36-
this.offlineElement.hidden = false;
37-
}
38-
},
39-
onOpen: () => {
40-
// If offlineElement exists, hide it and show the mountElement
41-
if (this.offlineElement) {
42-
this.offlineElement.hidden = true;
43-
this.mountElement.hidden = false;
44-
}
45-
},
46-
});
47-
this.mountElement = props.mountElement;
48-
this.prerenderElement = props.prerenderElement;
49-
this.offlineElement = props.offlineElement;
50-
}
19+
constructor(props: ReactPyDjangoClientProps) {
20+
super();
21+
this.urls = props.urls;
22+
this.socket = createReconnectingWebSocket({
23+
readyPromise: this.ready,
24+
url: this.urls.componentUrl,
25+
onMessage: async ({ data }) => this.handleIncoming(JSON.parse(data)),
26+
...props.reconnectOptions,
27+
onClose: () => {
28+
// If offlineElement exists, show it and hide the mountElement/prerenderElement
29+
if (this.prerenderElement) {
30+
this.prerenderElement.remove();
31+
this.prerenderElement = null;
32+
}
33+
if (this.offlineElement) {
34+
this.mountElement.hidden = true;
35+
this.offlineElement.hidden = false;
36+
}
37+
},
38+
onOpen: () => {
39+
// If offlineElement exists, hide it and show the mountElement
40+
if (this.offlineElement) {
41+
this.offlineElement.hidden = true;
42+
this.mountElement.hidden = false;
43+
}
44+
},
45+
});
46+
this.mountElement = props.mountElement;
47+
this.prerenderElement = props.prerenderElement;
48+
this.offlineElement = props.offlineElement;
49+
}
5150

52-
sendMessage(message: any): void {
53-
this.socket.current?.send(JSON.stringify(message));
54-
}
51+
sendMessage(message: any): void {
52+
this.socket.current?.send(JSON.stringify(message));
53+
}
5554

56-
loadModule(moduleName: string): Promise<ReactPyModule> {
57-
return import(`${this.urls.jsModules}/${moduleName}`);
58-
}
55+
loadModule(moduleName: string): Promise<ReactPyModule> {
56+
return import(`${this.urls.jsModules}/${moduleName}`);
57+
}
5958
}

‎src/js/src/index.tsx

Lines changed: 67 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,81 @@
1-
21
import { ReactPyDjangoClient } from "./client";
32
import React from "react";
43
import { render } from "react-dom";
54
import { Layout } from "@reactpy/client/src/components";
65

76
export function mountComponent(
8-
mountElement: HTMLElement,
9-
host: string,
10-
urlPrefix: string,
11-
componentPath: string,
12-
resolvedJsModulesPath: string,
13-
reconnectStartInterval: number,
14-
reconnectMaxInterval: number,
15-
reconnectMaxRetries: number,
16-
reconnectBackoffMultiplier: number
7+
mountElement: HTMLElement,
8+
host: string,
9+
urlPrefix: string,
10+
componentPath: string,
11+
resolvedJsModulesPath: string,
12+
reconnectStartInterval: number,
13+
reconnectMaxInterval: number,
14+
reconnectMaxRetries: number,
15+
reconnectBackoffMultiplier: number,
1716
) {
18-
// Protocols
19-
let httpProtocol = window.location.protocol;
20-
let wsProtocol = `ws${httpProtocol === "https:" ? "s" : ""}:`;
21-
22-
// WebSocket route (for Python components)
23-
let wsOrigin: string;
24-
if (host) {
25-
wsOrigin = `${wsProtocol}//${host}`;
26-
} else {
27-
wsOrigin = `${wsProtocol}//${window.location.host}`;
28-
}
17+
// Protocols
18+
let httpProtocol = window.location.protocol;
19+
let wsProtocol = `ws${httpProtocol === "https:" ? "s" : ""}:`;
2920

30-
// HTTP route (for JavaScript modules)
31-
let httpOrigin: string;
32-
let jsModulesPath: string;
33-
if (host) {
34-
httpOrigin = `${httpProtocol}//${host}`;
35-
jsModulesPath = `${urlPrefix}/web_module`;
36-
} else {
37-
httpOrigin = `${httpProtocol}//${window.location.host}`;
38-
if (resolvedJsModulesPath) {
39-
jsModulesPath = resolvedJsModulesPath;
40-
} else {
41-
jsModulesPath = `${urlPrefix}/web_module`;
42-
}
43-
}
21+
// WebSocket route (for Python components)
22+
let wsOrigin: string;
23+
if (host) {
24+
wsOrigin = `${wsProtocol}//${host}`;
25+
} else {
26+
wsOrigin = `${wsProtocol}//${window.location.host}`;
27+
}
4428

45-
// Embed the initial HTTP path into the WebSocket URL
46-
let componentUrl = new URL(`${wsOrigin}/${urlPrefix}/${componentPath}`);
47-
componentUrl.searchParams.append("http_pathname", window.location.pathname);
48-
if (window.location.search) {
49-
componentUrl.searchParams.append("http_search", window.location.search);
50-
}
29+
// HTTP route (for JavaScript modules)
30+
let httpOrigin: string;
31+
let jsModulesPath: string;
32+
if (host) {
33+
httpOrigin = `${httpProtocol}//${host}`;
34+
jsModulesPath = `${urlPrefix}/web_module`;
35+
} else {
36+
httpOrigin = `${httpProtocol}//${window.location.host}`;
37+
if (resolvedJsModulesPath) {
38+
jsModulesPath = resolvedJsModulesPath;
39+
} else {
40+
jsModulesPath = `${urlPrefix}/web_module`;
41+
}
42+
}
5143

52-
// Configure a new ReactPy client
53-
const client = new ReactPyDjangoClient({
54-
urls: {
55-
componentUrl: componentUrl,
56-
query: document.location.search,
57-
jsModules: `${httpOrigin}/${jsModulesPath}`,
58-
},
59-
reconnectOptions: {
60-
startInterval: reconnectStartInterval,
61-
maxInterval: reconnectMaxInterval,
62-
backoffMultiplier: reconnectBackoffMultiplier,
63-
maxRetries: reconnectMaxRetries,
64-
},
65-
mountElement: mountElement,
66-
prerenderElement: document.getElementById(
67-
mountElement.id + "-prerender"
68-
),
69-
offlineElement: document.getElementById(mountElement.id + "-offline"),
70-
});
44+
// Embed the initial HTTP path into the WebSocket URL
45+
let componentUrl = new URL(`${wsOrigin}/${urlPrefix}/${componentPath}`);
46+
componentUrl.searchParams.append("http_pathname", window.location.pathname);
47+
if (window.location.search) {
48+
componentUrl.searchParams.append("http_search", window.location.search);
49+
}
7150

51+
// Configure a new ReactPy client
52+
const client = new ReactPyDjangoClient({
53+
urls: {
54+
componentUrl: componentUrl,
55+
query: document.location.search,
56+
jsModules: `${httpOrigin}/${jsModulesPath}`,
57+
},
58+
reconnectOptions: {
59+
startInterval: reconnectStartInterval,
60+
maxInterval: reconnectMaxInterval,
61+
backoffMultiplier: reconnectBackoffMultiplier,
62+
maxRetries: reconnectMaxRetries,
63+
},
64+
mountElement: mountElement,
65+
prerenderElement: document.getElementById(mountElement.id + "-prerender"),
66+
offlineElement: document.getElementById(mountElement.id + "-offline"),
67+
});
7268

73-
// Replace the prerender element with the real element on the first layout update
74-
if (client.prerenderElement) {
75-
client.onMessage("layout-update", ({ path, model }) => {
76-
if (client.prerenderElement) {
77-
client.prerenderElement.replaceWith(client.mountElement);
78-
client.prerenderElement = null;
79-
}
80-
});
81-
}
69+
// Replace the prerender element with the real element on the first layout update
70+
if (client.prerenderElement) {
71+
client.onMessage("layout-update", ({ path, model }) => {
72+
if (client.prerenderElement) {
73+
client.prerenderElement.replaceWith(client.mountElement);
74+
client.prerenderElement = null;
75+
}
76+
});
77+
}
8278

83-
// Start rendering the component
84-
render(<Layout client={client} />, client.mountElement);
79+
// Start rendering the component
80+
render(<Layout client={client} />, client.mountElement);
8581
}

‎src/js/src/types.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
export type ReconnectOptions = {
2-
startInterval: number;
3-
maxInterval: number;
4-
maxRetries: number;
5-
backoffMultiplier: number;
2+
startInterval: number;
3+
maxInterval: number;
4+
maxRetries: number;
5+
backoffMultiplier: number;
66
};
77

88
export type ReactPyUrls = {
9-
componentUrl: URL;
10-
query: string;
11-
jsModules: string;
9+
componentUrl: URL;
10+
query: string;
11+
jsModules: string;
1212
};
1313

1414
export type ReactPyDjangoClientProps = {
15-
urls: ReactPyUrls;
16-
reconnectOptions: ReconnectOptions;
17-
mountElement: HTMLElement | null;
18-
prerenderElement: HTMLElement | null;
19-
offlineElement: HTMLElement | null;
15+
urls: ReactPyUrls;
16+
reconnectOptions: ReconnectOptions;
17+
mountElement: HTMLElement | null;
18+
prerenderElement: HTMLElement | null;
19+
offlineElement: HTMLElement | null;
2020
};

‎src/js/src/utils.ts

Lines changed: 64 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,74 @@
11
export function createReconnectingWebSocket(props: {
2-
url: URL;
3-
readyPromise: Promise<void>;
4-
onOpen?: () => void;
5-
onMessage: (message: MessageEvent<any>) => void;
6-
onClose?: () => void;
7-
startInterval: number;
8-
maxInterval: number;
9-
maxRetries: number;
10-
backoffMultiplier: number;
2+
url: URL;
3+
readyPromise: Promise<void>;
4+
onOpen?: () => void;
5+
onMessage: (message: MessageEvent<any>) => void;
6+
onClose?: () => void;
7+
startInterval: number;
8+
maxInterval: number;
9+
maxRetries: number;
10+
backoffMultiplier: number;
1111
}) {
12-
const { startInterval, maxInterval, maxRetries, backoffMultiplier } = props;
13-
let retries = 0;
14-
let interval = startInterval;
15-
let everConnected = false;
16-
const closed = false;
17-
const socket: { current?: WebSocket } = {};
12+
const { startInterval, maxInterval, maxRetries, backoffMultiplier } = props;
13+
let retries = 0;
14+
let interval = startInterval;
15+
let everConnected = false;
16+
const closed = false;
17+
const socket: { current?: WebSocket } = {};
1818

19-
const connect = () => {
20-
if (closed) {
21-
return;
22-
}
23-
socket.current = new WebSocket(props.url);
24-
socket.current.onopen = () => {
25-
everConnected = true;
26-
console.info("ReactPy connected!");
27-
interval = startInterval;
28-
retries = 0;
29-
if (props.onOpen) {
30-
props.onOpen();
31-
}
32-
};
33-
socket.current.onmessage = props.onMessage;
34-
socket.current.onclose = () => {
35-
if (props.onClose) {
36-
props.onClose();
37-
}
38-
if (!everConnected) {
39-
console.info("ReactPy failed to connect!");
40-
return;
41-
}
42-
console.info("ReactPy disconnected!");
43-
if (retries >= maxRetries) {
44-
console.info("ReactPy connection max retries exhausted!");
45-
return;
46-
}
47-
console.info(
48-
`ReactPy reconnecting in ${(interval / 1000).toPrecision(
49-
4
50-
)} seconds...`
51-
);
52-
setTimeout(connect, interval);
53-
interval = nextInterval(interval, backoffMultiplier, maxInterval);
54-
retries++;
55-
};
56-
};
19+
const connect = () => {
20+
if (closed) {
21+
return;
22+
}
23+
socket.current = new WebSocket(props.url);
24+
socket.current.onopen = () => {
25+
everConnected = true;
26+
console.info("ReactPy connected!");
27+
interval = startInterval;
28+
retries = 0;
29+
if (props.onOpen) {
30+
props.onOpen();
31+
}
32+
};
33+
socket.current.onmessage = props.onMessage;
34+
socket.current.onclose = () => {
35+
if (props.onClose) {
36+
props.onClose();
37+
}
38+
if (!everConnected) {
39+
console.info("ReactPy failed to connect!");
40+
return;
41+
}
42+
console.info("ReactPy disconnected!");
43+
if (retries >= maxRetries) {
44+
console.info("ReactPy connection max retries exhausted!");
45+
return;
46+
}
47+
console.info(
48+
`ReactPy reconnecting in ${(interval / 1000).toPrecision(4)} seconds...`,
49+
);
50+
setTimeout(connect, interval);
51+
interval = nextInterval(interval, backoffMultiplier, maxInterval);
52+
retries++;
53+
};
54+
};
5755

58-
props.readyPromise
59-
.then(() => console.info("Starting ReactPy client..."))
60-
.then(connect);
56+
props.readyPromise
57+
.then(() => console.info("Starting ReactPy client..."))
58+
.then(connect);
6159

62-
return socket;
60+
return socket;
6361
}
6462

6563
export function nextInterval(
66-
currentInterval: number,
67-
backoffMultiplier: number,
68-
maxInterval: number
64+
currentInterval: number,
65+
backoffMultiplier: number,
66+
maxInterval: number,
6967
): number {
70-
return Math.min(
71-
// increase interval by backoff multiplier
72-
currentInterval * backoffMultiplier,
73-
// don't exceed max interval
74-
maxInterval
75-
);
68+
return Math.min(
69+
// increase interval by backoff multiplier
70+
currentInterval * backoffMultiplier,
71+
// don't exceed max interval
72+
maxInterval,
73+
);
7674
}

‎src/js/tsconfig.json

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
{
2-
"compilerOptions": {
3-
"target": "ES2022",
4-
"module": "esnext",
5-
"moduleResolution": "node",
6-
"jsx": "react",
7-
"allowSyntheticDefaultImports": true
8-
},
9-
"paths": {
10-
"react": [
11-
"./node_modules/preact/compat/"
12-
],
13-
"react-dom": [
14-
"./node_modules/preact/compat/"
15-
]
16-
}
2+
"compilerOptions": {
3+
"target": "ES2022",
4+
"module": "esnext",
5+
"moduleResolution": "node",
6+
"jsx": "react",
7+
"allowSyntheticDefaultImports": true,
8+
},
9+
"paths": {
10+
"react": ["./node_modules/preact/compat/"],
11+
"react-dom": ["./node_modules/preact/compat/"],
12+
},
1713
}

0 commit comments

Comments
 (0)
Please sign in to comment.