Skip to content

Commit 9e31735

Browse files
authored
V2.0.0b7 (#1318)
- JavaScript components will now _automagically_ use ReactPy's internal version of ReactJS, even if the user forgot to call `import_reactjs` within `html.head(...)`. As a result, this adds a few improvements: - Components from `component_from_npm` can now piggy-back on ReactPy's internal ReactJS export to prevent bugs caused by ReactJS version mismatches. - Components from `component_from_file` and `component_from_string` no longer need to be compiled/built by the user (with `vite`, `webpack`, `rollup`, etc) if they only use the following imports: `react`, `react-dom`, `react-dom/client`, and `react/jsx-runtime`. - Changes to test suite, fixtures, and tooling to allow pytest to run in parallel mode (via `hatch test --parallel`). This reduces test runtime from ~150 seconds to ~20 seconds on local machines. - Fix bug where ReactJS components from `component_from_npm` could generate a `ReactDOM.createRoot was called on existing root` warning. - Move `reactpy.pyscript` code into `reactpy.executors.pyscript`
1 parent f21e963 commit 9e31735

File tree

25 files changed

+144
-98
lines changed

25 files changed

+144
-98
lines changed

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,8 @@ omit = [
232232
"src/reactpy/__init__.py",
233233
"src/reactpy/_console/*",
234234
"src/reactpy/__main__.py",
235-
"src/reactpy/pyscript/layout_handler.py",
236-
"src/reactpy/pyscript/component_template.py",
235+
"src/reactpy/executors/pyscript/layout_handler.py",
236+
"src/reactpy/executors/pyscript/component_template.py",
237237
]
238238

239239
[tool.coverage.report]

src/build_scripts/install_playwright.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@
1414
# as *nix systems (such as WSL) return a failure code if there are *any* dependencies
1515
# that could be cleaned up via `sudo apt autoremove`. This occurs even if we weren't
1616
# the ones to install those dependencies in the first place.
17-
subprocess.run(["playwright", "install-deps"], check=False) # noqa: S607
17+
subprocess.run(["playwright", "install-deps", "chromium"], check=False) # noqa: S607

src/js/packages/@reactpy/client/src/bind.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,11 @@ export async function infer_bind_from_environment() {
66
const React = await import("react");
77
// @ts-ignore
88
const ReactDOM = await import("react-dom/client");
9-
10-
console.log(
11-
"ReactPy detected 'ReactJS' to bind your JavaScript components.",
12-
);
139
return (node: HTMLElement) => reactjs_bind(node, React, ReactDOM);
1410
} catch {
15-
console.debug(
16-
"ReactPy did not detect a component binding function or a suitable 'importmap'. Using ReactPy's internal framework (Preact) to bind your JavaScript components.",
11+
console.error(
12+
"Unknown error occurred: 'react' is missing within this ReactPy environment! \
13+
Your JavaScript components may not work as expected!",
1714
);
1815
return (node: HTMLElement) => local_preact_bind(node);
1916
}

src/js/packages/@reactpy/client/src/components.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import type {
66
ImportSourceBinding,
77
ReactPyComponent,
88
ReactPyVdom,
9-
ReactPyClientInterface,
109
} from "./types";
1110
import { createAttributes, createChildren, loadImportSource } from "./vdom";
11+
import type { ReactPyClient } from "./client";
1212

13-
const ClientContext = createContext<ReactPyClientInterface>(null as any);
13+
const ClientContext = createContext<ReactPyClient>(null as any);
1414

15-
export function Layout(props: { client: ReactPyClientInterface }): JSX.Element {
15+
export function Layout(props: { client: ReactPyClient }): JSX.Element {
1616
const currentModel: ReactPyVdom = useState({ tagName: "" })[0];
1717
const forceUpdate = useForceUpdate();
1818

src/js/packages/@reactpy/client/src/vdom.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { ReactPyClientInterface } from "./types";
21
import eventToObject from "event-to-object";
32
import type {
43
ReactPyVdom,
@@ -11,10 +10,11 @@ import type {
1110
} from "./types";
1211
import { infer_bind_from_environment } from "./bind";
1312
import log from "./logger";
13+
import type { ReactPyClient } from "./client";
1414

1515
export async function loadImportSource(
1616
vdomImportSource: ReactPyVdomImportSource,
17-
client: ReactPyClientInterface,
17+
client: ReactPyClient,
1818
): Promise<BindImportSource> {
1919
let module: ReactPyModule;
2020
if (vdomImportSource.sourceType === "URL") {
@@ -61,7 +61,7 @@ export async function loadImportSource(
6161
}
6262

6363
function createImportSourceElement(props: {
64-
client: ReactPyClientInterface;
64+
client: ReactPyClient;
6565
module: ReactPyModule;
6666
binding: ReactPyModuleBinding;
6767
model: ReactPyVdom;
@@ -180,7 +180,7 @@ export function createChildren<Child>(
180180

181181
export function createAttributes(
182182
model: ReactPyVdom,
183-
client: ReactPyClientInterface,
183+
client: ReactPyClient,
184184
): { [key: string]: any } {
185185
return Object.fromEntries(
186186
Object.entries({
@@ -203,7 +203,7 @@ export function createAttributes(
203203
}
204204

205205
function createEventHandler(
206-
client: ReactPyClientInterface,
206+
client: ReactPyClient,
207207
name: string,
208208
{ target, preventDefault, stopPropagation }: ReactPyVdomEventHandler,
209209
): [string, () => void] {
@@ -262,7 +262,7 @@ function createInlineJavaScript(
262262
class ReactPyChild extends HTMLElement {
263263
mountPoint: HTMLDivElement;
264264
binding: ImportSourceBinding | null = null;
265-
_client: ReactPyClientInterface | null = null;
265+
_client: ReactPyClient | null = null;
266266
_model: ReactPyVdom | null = null;
267267
currentImportSource: ReactPyVdomImportSource | null = null;
268268

@@ -276,7 +276,7 @@ class ReactPyChild extends HTMLElement {
276276
this.appendChild(this.mountPoint);
277277
}
278278

279-
set client(value: ReactPyClientInterface) {
279+
set client(value: ReactPyClient) {
280280
this._client = value;
281281
}
282282

src/reactpy/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@
1919
use_state,
2020
)
2121
from reactpy.core.vdom import Vdom
22-
from reactpy.pyscript.components import pyscript_component
22+
from reactpy.executors.pyscript.components import pyscript_component
2323
from reactpy.utils import Ref, reactpy_to_string, string_to_reactpy
2424

2525
__author__ = "The Reactive Python Team"
26-
__version__ = "2.0.0b6"
26+
__version__ = "2.0.0b7"
2727

2828
__all__ = [
2929
"Ref",

src/reactpy/core/_thread_local.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
class ThreadLocal(Generic[_StateType]): # nocov
1010
"""Utility for managing per-thread state information. This is only used in
11-
environments where ContextVars are not available, such as the `pyodide`
11+
environments where ContextVars are not available, such as the `pyscript`
1212
executor."""
1313

1414
def __init__(self, default: Callable[[], _StateType]):

src/reactpy/core/hooks.py

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,7 @@ def __init__(
8484
self,
8585
initial_value: _Type | Callable[[], _Type],
8686
) -> None:
87-
if callable(initial_value):
88-
self.value = initial_value()
89-
else:
90-
self.value = initial_value
91-
87+
self.value = initial_value() if callable(initial_value) else initial_value
9288
hook = HOOK_STACK.current_hook()
9389

9490
def dispatch(new: _Type | Callable[[_Type], _Type]) -> None:
@@ -434,10 +430,7 @@ def use_callback(
434430
def setup(function: _CallbackFunc) -> _CallbackFunc:
435431
return memoize(lambda: function)
436432

437-
if function is not None:
438-
return setup(function)
439-
else:
440-
return setup
433+
return setup(function) if function is not None else setup
441434

442435

443436
class _LambdaCaller(Protocol):
@@ -553,17 +546,16 @@ def _try_to_infer_closure_values(
553546
func: Callable[..., Any] | None,
554547
values: Sequence[Any] | ellipsis | None,
555548
) -> Sequence[Any] | None:
556-
if values is ...:
557-
if isinstance(func, FunctionType):
558-
return (
559-
[cell.cell_contents for cell in func.__closure__]
560-
if func.__closure__
561-
else []
562-
)
563-
else:
564-
return None
565-
else:
549+
if values is not ...:
566550
return values
551+
if isinstance(func, FunctionType):
552+
return (
553+
[cell.cell_contents for cell in func.__closure__]
554+
if func.__closure__
555+
else []
556+
)
557+
else:
558+
return None
567559

568560

569561
def strictly_equal(x: Any, y: Any) -> bool:

src/reactpy/executors/asgi/pyscript.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313
from reactpy.executors.asgi.middleware import ReactPyMiddleware
1414
from reactpy.executors.asgi.standalone import ReactPy, ReactPyApp
1515
from reactpy.executors.asgi.types import AsgiWebsocketScope
16+
from reactpy.executors.pyscript.utils import (
17+
pyscript_component_html,
18+
pyscript_setup_html,
19+
)
1620
from reactpy.executors.utils import vdom_head_to_html
17-
from reactpy.pyscript.utils import pyscript_component_html, pyscript_setup_html
1821
from reactpy.types import ReactPyConfig, VdomDict
1922

2023

src/reactpy/executors/asgi/standalone.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
AsgiV3WebsocketApp,
2424
AsgiWebsocketScope,
2525
)
26+
from reactpy.executors.pyscript.utils import pyscript_setup_html
2627
from reactpy.executors.utils import server_side_component_html, vdom_head_to_html
27-
from reactpy.pyscript.utils import pyscript_setup_html
2828
from reactpy.types import (
2929
PyScriptOptions,
3030
ReactPyConfig,

0 commit comments

Comments
 (0)