reactpy 2.0.0b6__py3-none-any.whl → 2.0.0b8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- reactpy/__init__.py +2 -2
- reactpy/config.py +1 -1
- reactpy/core/_thread_local.py +1 -1
- reactpy/core/hooks.py +34 -37
- reactpy/executors/asgi/pyscript.py +4 -1
- reactpy/executors/asgi/standalone.py +1 -1
- reactpy/{pyscript → executors/pyscript}/component_template.py +1 -1
- reactpy/{pyscript → executors/pyscript}/components.py +1 -1
- reactpy/executors/utils.py +32 -7
- reactpy/reactjs/__init__.py +5 -7
- reactpy/reactjs/module.py +106 -42
- reactpy/reactjs/utils.py +49 -20
- reactpy/static/{index-sbddj6ms.js → index-64wy0fss.js} +4 -4
- reactpy/static/{index-sbddj6ms.js.map → index-64wy0fss.js.map} +1 -1
- reactpy/static/index-beq660xy.js +5 -0
- reactpy/static/index-beq660xy.js.map +12 -0
- reactpy/static/index.js +2 -2
- reactpy/static/index.js.map +6 -5
- reactpy/static/preact-dom.js +4 -0
- reactpy/static/{react-dom.js.map → preact-dom.js.map} +3 -3
- reactpy/static/preact-jsx-runtime.js +4 -0
- reactpy/static/{react-jsx-runtime.js.map → preact-jsx-runtime.js.map} +1 -1
- reactpy/static/preact.js +4 -0
- reactpy/static/{react.js.map → preact.js.map} +3 -3
- reactpy/templatetags/jinja.py +4 -1
- reactpy/testing/__init__.py +2 -7
- reactpy/testing/backend.py +20 -8
- reactpy/testing/common.py +1 -9
- reactpy/testing/display.py +68 -32
- {reactpy-2.0.0b6.dist-info → reactpy-2.0.0b8.dist-info}/METADATA +1 -1
- {reactpy-2.0.0b6.dist-info → reactpy-2.0.0b8.dist-info}/RECORD +37 -40
- reactpy/static/index-h31022cd.js +0 -5
- reactpy/static/index-h31022cd.js.map +0 -11
- reactpy/static/index-y71bxs88.js +0 -5
- reactpy/static/index-y71bxs88.js.map +0 -10
- reactpy/static/react-dom.js +0 -4
- reactpy/static/react-jsx-runtime.js +0 -4
- reactpy/static/react.js +0 -4
- reactpy/testing/utils.py +0 -27
- /reactpy/{pyscript → executors/pyscript}/__init__.py +0 -0
- /reactpy/{pyscript → executors/pyscript}/layout_handler.py +0 -0
- /reactpy/{pyscript → executors/pyscript}/utils.py +0 -0
- {reactpy-2.0.0b6.dist-info → reactpy-2.0.0b8.dist-info}/WHEEL +0 -0
- {reactpy-2.0.0b6.dist-info → reactpy-2.0.0b8.dist-info}/entry_points.txt +0 -0
- {reactpy-2.0.0b6.dist-info → reactpy-2.0.0b8.dist-info}/licenses/LICENSE +0 -0
reactpy/__init__.py
CHANGED
|
@@ -19,11 +19,11 @@ from reactpy.core.hooks import (
|
|
|
19
19
|
use_state,
|
|
20
20
|
)
|
|
21
21
|
from reactpy.core.vdom import Vdom
|
|
22
|
-
from reactpy.pyscript.components import pyscript_component
|
|
22
|
+
from reactpy.executors.pyscript.components import pyscript_component
|
|
23
23
|
from reactpy.utils import Ref, reactpy_to_string, string_to_reactpy
|
|
24
24
|
|
|
25
25
|
__author__ = "The Reactive Python Team"
|
|
26
|
-
__version__ = "2.0.
|
|
26
|
+
__version__ = "2.0.0b8"
|
|
27
27
|
|
|
28
28
|
__all__ = [
|
|
29
29
|
"Ref",
|
reactpy/config.py
CHANGED
reactpy/core/_thread_local.py
CHANGED
|
@@ -8,7 +8,7 @@ _StateType = TypeVar("_StateType")
|
|
|
8
8
|
|
|
9
9
|
class ThreadLocal(Generic[_StateType]): # nocov
|
|
10
10
|
"""Utility for managing per-thread state information. This is only used in
|
|
11
|
-
environments where ContextVars are not available, such as the `
|
|
11
|
+
environments where ContextVars are not available, such as the `pyscript`
|
|
12
12
|
executor."""
|
|
13
13
|
|
|
14
14
|
def __init__(self, default: Callable[[], _StateType]):
|
reactpy/core/hooks.py
CHANGED
|
@@ -84,11 +84,7 @@ class _CurrentState(Generic[_Type]):
|
|
|
84
84
|
self,
|
|
85
85
|
initial_value: _Type | Callable[[], _Type],
|
|
86
86
|
) -> 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
|
|
92
88
|
hook = HOOK_STACK.current_hook()
|
|
93
89
|
|
|
94
90
|
def dispatch(new: _Type | Callable[[_Type], _Type]) -> None:
|
|
@@ -186,7 +182,6 @@ def use_effect(
|
|
|
186
182
|
def use_async_effect(
|
|
187
183
|
function: None = None,
|
|
188
184
|
dependencies: Sequence[Any] | ellipsis | None = ...,
|
|
189
|
-
shutdown_timeout: float = 0.1,
|
|
190
185
|
) -> Callable[[_EffectApplyFunc], None]: ...
|
|
191
186
|
|
|
192
187
|
|
|
@@ -194,14 +189,12 @@ def use_async_effect(
|
|
|
194
189
|
def use_async_effect(
|
|
195
190
|
function: _AsyncEffectFunc,
|
|
196
191
|
dependencies: Sequence[Any] | ellipsis | None = ...,
|
|
197
|
-
shutdown_timeout: float = 0.1,
|
|
198
192
|
) -> None: ...
|
|
199
193
|
|
|
200
194
|
|
|
201
195
|
def use_async_effect(
|
|
202
196
|
function: _AsyncEffectFunc | None = None,
|
|
203
197
|
dependencies: Sequence[Any] | ellipsis | None = ...,
|
|
204
|
-
shutdown_timeout: float = 0.1,
|
|
205
198
|
) -> Callable[[_AsyncEffectFunc], None] | None:
|
|
206
199
|
"""
|
|
207
200
|
A hook that manages an asynchronous side effect in a React-like component.
|
|
@@ -218,9 +211,6 @@ def use_async_effect(
|
|
|
218
211
|
of any value in the given sequence changes (i.e. their :func:`id` is
|
|
219
212
|
different). By default these are inferred based on local variables that are
|
|
220
213
|
referenced by the given function.
|
|
221
|
-
shutdown_timeout:
|
|
222
|
-
The amount of time (in seconds) to wait for the effect to complete before
|
|
223
|
-
forcing a shutdown.
|
|
224
214
|
|
|
225
215
|
Returns:
|
|
226
216
|
If not function is provided, a decorator. Otherwise ``None``.
|
|
@@ -236,26 +226,37 @@ def use_async_effect(
|
|
|
236
226
|
# always clean up the previous effect's resources
|
|
237
227
|
run_effect_cleanup(cleanup_func)
|
|
238
228
|
|
|
239
|
-
# Execute the effect
|
|
229
|
+
# Execute the effect and store the clean-up function.
|
|
230
|
+
# We run this in a task so it can be cancelled if the stop signal
|
|
231
|
+
# is set before the effect completes.
|
|
240
232
|
task = asyncio.create_task(func())
|
|
241
233
|
|
|
242
|
-
# Wait
|
|
243
|
-
|
|
234
|
+
# Wait for either the effect to complete or the stop signal
|
|
235
|
+
stop_task = asyncio.create_task(stop.wait())
|
|
236
|
+
done, _ = await asyncio.wait(
|
|
237
|
+
[task, stop_task],
|
|
238
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
239
|
+
)
|
|
244
240
|
|
|
245
|
-
# If
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
241
|
+
# If the effect completed first, store the cleanup function
|
|
242
|
+
if task in done:
|
|
243
|
+
cleanup_func.current = task.result()
|
|
244
|
+
# Cancel the stop task since we don't need it anymore
|
|
245
|
+
stop_task.cancel()
|
|
246
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
247
|
+
await stop_task
|
|
248
|
+
# Now wait for the stop signal to run cleanup
|
|
249
|
+
await stop.wait()
|
|
250
|
+
else:
|
|
251
|
+
# Stop signal came first - cancel the effect task
|
|
252
|
+
task.cancel()
|
|
253
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
254
|
+
await task
|
|
251
255
|
|
|
252
256
|
# Run the clean-up function when the effect is stopped,
|
|
253
257
|
# if it hasn't been run already by a new effect
|
|
254
258
|
run_effect_cleanup(cleanup_func)
|
|
255
259
|
|
|
256
|
-
# Cancel the task if it's still running
|
|
257
|
-
task.cancel()
|
|
258
|
-
|
|
259
260
|
return memoize(lambda: hook.add_effect(effect))
|
|
260
261
|
|
|
261
262
|
# Handle decorator usage
|
|
@@ -434,10 +435,7 @@ def use_callback(
|
|
|
434
435
|
def setup(function: _CallbackFunc) -> _CallbackFunc:
|
|
435
436
|
return memoize(lambda: function)
|
|
436
437
|
|
|
437
|
-
if function is not None
|
|
438
|
-
return setup(function)
|
|
439
|
-
else:
|
|
440
|
-
return setup
|
|
438
|
+
return setup(function) if function is not None else setup
|
|
441
439
|
|
|
442
440
|
|
|
443
441
|
class _LambdaCaller(Protocol):
|
|
@@ -553,17 +551,16 @@ def _try_to_infer_closure_values(
|
|
|
553
551
|
func: Callable[..., Any] | None,
|
|
554
552
|
values: Sequence[Any] | ellipsis | None,
|
|
555
553
|
) -> 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:
|
|
554
|
+
if values is not ...:
|
|
566
555
|
return values
|
|
556
|
+
if isinstance(func, FunctionType):
|
|
557
|
+
return (
|
|
558
|
+
[cell.cell_contents for cell in func.__closure__]
|
|
559
|
+
if func.__closure__
|
|
560
|
+
else []
|
|
561
|
+
)
|
|
562
|
+
else:
|
|
563
|
+
return None
|
|
567
564
|
|
|
568
565
|
|
|
569
566
|
def strictly_equal(x: Any, y: Any) -> bool:
|
|
@@ -13,8 +13,11 @@ from reactpy import html
|
|
|
13
13
|
from reactpy.executors.asgi.middleware import ReactPyMiddleware
|
|
14
14
|
from reactpy.executors.asgi.standalone import ReactPy, ReactPyApp
|
|
15
15
|
from reactpy.executors.asgi.types import AsgiWebsocketScope
|
|
16
|
+
from reactpy.executors.pyscript.utils import (
|
|
17
|
+
pyscript_component_html,
|
|
18
|
+
pyscript_setup_html,
|
|
19
|
+
)
|
|
16
20
|
from reactpy.executors.utils import vdom_head_to_html
|
|
17
|
-
from reactpy.pyscript.utils import pyscript_component_html, pyscript_setup_html
|
|
18
21
|
from reactpy.types import ReactPyConfig, VdomDict
|
|
19
22
|
|
|
20
23
|
|
|
@@ -23,8 +23,8 @@ from reactpy.executors.asgi.types import (
|
|
|
23
23
|
AsgiV3WebsocketApp,
|
|
24
24
|
AsgiWebsocketScope,
|
|
25
25
|
)
|
|
26
|
+
from reactpy.executors.pyscript.utils import pyscript_setup_html
|
|
26
27
|
from reactpy.executors.utils import server_side_component_html, vdom_head_to_html
|
|
27
|
-
from reactpy.pyscript.utils import pyscript_setup_html
|
|
28
28
|
from reactpy.types import (
|
|
29
29
|
PyScriptOptions,
|
|
30
30
|
ReactPyConfig,
|
|
@@ -4,7 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
from reactpy import component, hooks
|
|
7
|
-
from reactpy.pyscript.utils import pyscript_component_html
|
|
7
|
+
from reactpy.executors.pyscript.utils import pyscript_component_html
|
|
8
8
|
from reactpy.types import Component, Key
|
|
9
9
|
from reactpy.utils import string_to_reactpy
|
|
10
10
|
|
reactpy/executors/utils.py
CHANGED
|
@@ -64,16 +64,41 @@ def server_side_component_html(
|
|
|
64
64
|
) -> str:
|
|
65
65
|
return (
|
|
66
66
|
f'<div id="{element_id}" class="{class_}"></div>'
|
|
67
|
+
"<script>"
|
|
68
|
+
'if (!document.querySelector("#reactpy-importmap")) {'
|
|
69
|
+
" console.debug("
|
|
70
|
+
' "ReactPy did not detect a suitable JavaScript import map. Falling back to ReactPy\'s internal framework (Preact)."'
|
|
71
|
+
" );"
|
|
72
|
+
' const im = document.createElement("script");'
|
|
73
|
+
' im.type = "importmap";'
|
|
74
|
+
f" im.textContent = '{default_import_map()}';"
|
|
75
|
+
' im.id = "reactpy-importmap";'
|
|
76
|
+
" document.head.appendChild(im);"
|
|
77
|
+
" delete im;"
|
|
78
|
+
"}"
|
|
79
|
+
"</script>"
|
|
67
80
|
'<script type="module" crossorigin="anonymous">'
|
|
68
81
|
f'import {{ mountReactPy }} from "{REACTPY_PATH_PREFIX.current}static/index.js";'
|
|
69
82
|
"mountReactPy({"
|
|
70
|
-
f'
|
|
71
|
-
f'
|
|
72
|
-
f'
|
|
73
|
-
f"
|
|
74
|
-
f"
|
|
75
|
-
f"
|
|
76
|
-
f"
|
|
83
|
+
f' mountElement: document.getElementById("{element_id}"),'
|
|
84
|
+
f' pathPrefix: "{REACTPY_PATH_PREFIX.current}",'
|
|
85
|
+
f' componentPath: "{component_path}",'
|
|
86
|
+
f" reconnectInterval: {REACTPY_RECONNECT_INTERVAL.current},"
|
|
87
|
+
f" reconnectMaxInterval: {REACTPY_RECONNECT_MAX_INTERVAL.current},"
|
|
88
|
+
f" reconnectMaxRetries: {REACTPY_RECONNECT_MAX_RETRIES.current},"
|
|
89
|
+
f" reconnectBackoffMultiplier: {REACTPY_RECONNECT_BACKOFF_MULTIPLIER.current},"
|
|
77
90
|
"});"
|
|
78
91
|
"</script>"
|
|
79
92
|
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def default_import_map() -> str:
|
|
96
|
+
path_prefix = REACTPY_PATH_PREFIX.current.strip("/")
|
|
97
|
+
return f"""{{
|
|
98
|
+
"imports": {{
|
|
99
|
+
"react": "/{path_prefix}/static/preact.js",
|
|
100
|
+
"react-dom": "/{path_prefix}/static/preact-dom.js",
|
|
101
|
+
"react-dom/client": "/{path_prefix}/static/preact-dom.js",
|
|
102
|
+
"react/jsx-runtime": "/{path_prefix}/static/preact-jsx-runtime.js"
|
|
103
|
+
}}
|
|
104
|
+
}}""".replace("\n", "").replace(" ", "")
|
reactpy/reactjs/__init__.py
CHANGED
|
@@ -110,7 +110,7 @@ def component_from_npm(
|
|
|
110
110
|
resolve_imports: bool = ...,
|
|
111
111
|
resolve_imports_depth: int = ...,
|
|
112
112
|
version: str = "latest",
|
|
113
|
-
cdn: str = "https://esm.sh",
|
|
113
|
+
cdn: str = "https://esm.sh/v135",
|
|
114
114
|
fallback: Any | None = ...,
|
|
115
115
|
unmount_before_update: bool = ...,
|
|
116
116
|
allow_children: bool = ...,
|
|
@@ -124,7 +124,7 @@ def component_from_npm(
|
|
|
124
124
|
resolve_imports: bool = ...,
|
|
125
125
|
resolve_imports_depth: int = ...,
|
|
126
126
|
version: str = "latest",
|
|
127
|
-
cdn: str = "https://esm.sh",
|
|
127
|
+
cdn: str = "https://esm.sh/v135",
|
|
128
128
|
fallback: Any | None = ...,
|
|
129
129
|
unmount_before_update: bool = ...,
|
|
130
130
|
allow_children: bool = ...,
|
|
@@ -137,7 +137,7 @@ def component_from_npm(
|
|
|
137
137
|
resolve_imports: bool = False,
|
|
138
138
|
resolve_imports_depth: int = 5,
|
|
139
139
|
version: str = "latest",
|
|
140
|
-
cdn: str = "https://esm.sh",
|
|
140
|
+
cdn: str = "https://esm.sh/v135",
|
|
141
141
|
fallback: Any | None = None,
|
|
142
142
|
unmount_before_update: bool = False,
|
|
143
143
|
allow_children: bool = True,
|
|
@@ -175,10 +175,8 @@ def component_from_npm(
|
|
|
175
175
|
url = f"{cdn}/{package}@{version}"
|
|
176
176
|
|
|
177
177
|
if "esm.sh" in cdn:
|
|
178
|
-
if "?" in url
|
|
179
|
-
|
|
180
|
-
else:
|
|
181
|
-
url += "?external=react,react-dom"
|
|
178
|
+
url += "&" if "?" in url else "?"
|
|
179
|
+
url += "external=react,react-dom,react/jsx-runtime&bundle&target=es2020"
|
|
182
180
|
|
|
183
181
|
return component_from_url(
|
|
184
182
|
url,
|
reactpy/reactjs/module.py
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Any
|
|
4
|
+
from pathlib import Path, PurePosixPath
|
|
5
|
+
from typing import Any, Literal
|
|
6
6
|
|
|
7
|
-
from reactpy import
|
|
8
|
-
from reactpy.config import REACTPY_WEB_MODULES_DIR
|
|
7
|
+
from reactpy.config import REACTPY_DEBUG, REACTPY_WEB_MODULES_DIR
|
|
9
8
|
from reactpy.core.vdom import Vdom
|
|
10
9
|
from reactpy.reactjs.types import NAME_SOURCE, URL_SOURCE
|
|
11
10
|
from reactpy.reactjs.utils import (
|
|
12
11
|
are_files_identical,
|
|
13
12
|
copy_file,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
file_lock,
|
|
14
|
+
resolve_names_from_file,
|
|
15
|
+
resolve_names_from_url,
|
|
17
16
|
)
|
|
18
|
-
from reactpy.types import ImportSourceDict, JavaScriptModule, VdomConstructor
|
|
17
|
+
from reactpy.types import ImportSourceDict, JavaScriptModule, VdomConstructor, VdomDict
|
|
19
18
|
|
|
20
19
|
logger = logging.getLogger(__name__)
|
|
21
20
|
|
|
@@ -33,7 +32,7 @@ def url_to_module(
|
|
|
33
32
|
default_fallback=fallback,
|
|
34
33
|
file=None,
|
|
35
34
|
import_names=(
|
|
36
|
-
|
|
35
|
+
resolve_names_from_url(url, resolve_imports_depth)
|
|
37
36
|
if resolve_imports
|
|
38
37
|
else None
|
|
39
38
|
),
|
|
@@ -54,19 +53,20 @@ def file_to_module(
|
|
|
54
53
|
|
|
55
54
|
source_file = Path(file).resolve()
|
|
56
55
|
target_file = get_module_path(name)
|
|
57
|
-
if not source_file.exists():
|
|
58
|
-
msg = f"Source file does not exist: {source_file}"
|
|
59
|
-
raise FileNotFoundError(msg)
|
|
60
56
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
target_file
|
|
69
|
-
|
|
57
|
+
with file_lock(target_file.with_name(f"{target_file.name}.lock")):
|
|
58
|
+
if not source_file.exists():
|
|
59
|
+
msg = f"Source file does not exist: {source_file}"
|
|
60
|
+
raise FileNotFoundError(msg)
|
|
61
|
+
|
|
62
|
+
if not target_file.exists():
|
|
63
|
+
copy_file(target_file, source_file, symlink)
|
|
64
|
+
elif not are_files_identical(source_file, target_file):
|
|
65
|
+
logger.info(
|
|
66
|
+
f"Existing web module {name!r} will "
|
|
67
|
+
f"be replaced with {target_file.resolve()}"
|
|
68
|
+
)
|
|
69
|
+
copy_file(target_file, source_file, symlink)
|
|
70
70
|
|
|
71
71
|
return JavaScriptModule(
|
|
72
72
|
source=name,
|
|
@@ -74,7 +74,7 @@ def file_to_module(
|
|
|
74
74
|
default_fallback=fallback,
|
|
75
75
|
file=target_file,
|
|
76
76
|
import_names=(
|
|
77
|
-
|
|
77
|
+
resolve_names_from_file(source_file, resolve_imports_depth)
|
|
78
78
|
if resolve_imports
|
|
79
79
|
else None
|
|
80
80
|
),
|
|
@@ -110,7 +110,7 @@ def string_to_module(
|
|
|
110
110
|
default_fallback=fallback,
|
|
111
111
|
file=target_file,
|
|
112
112
|
import_names=(
|
|
113
|
-
|
|
113
|
+
resolve_names_from_file(target_file, resolve_imports_depth)
|
|
114
114
|
if resolve_imports
|
|
115
115
|
else None
|
|
116
116
|
),
|
|
@@ -178,26 +178,90 @@ def make_module(
|
|
|
178
178
|
)
|
|
179
179
|
|
|
180
180
|
|
|
181
|
+
def import_reactjs(
|
|
182
|
+
framework: Literal["preact", "react"] | None = None,
|
|
183
|
+
version: str | None = None,
|
|
184
|
+
use_local: bool = False,
|
|
185
|
+
) -> VdomDict:
|
|
186
|
+
"""
|
|
187
|
+
Return an import map script tag for ReactJS or Preact.
|
|
188
|
+
Parameters:
|
|
189
|
+
framework:
|
|
190
|
+
The framework to use, either "preact" or "react". Defaults to "preact" for
|
|
191
|
+
performance reasons. Set this to `react` if you are experiencing compatibility
|
|
192
|
+
issues with your component library.
|
|
193
|
+
version:
|
|
194
|
+
The version of the framework to use. Example values include "18", "10.2.4",
|
|
195
|
+
or "latest". If left as `None`, a default version will be used depending on the
|
|
196
|
+
selected framework.
|
|
197
|
+
use_local:
|
|
198
|
+
Whether to use the local framework ReactPy is bundled with (Preact).
|
|
199
|
+
Raises:
|
|
200
|
+
ValueError:
|
|
201
|
+
If both `framework` and `react_url_prefix` are provided, or if
|
|
202
|
+
`framework` is not one of "preact" or "react".
|
|
203
|
+
Returns:
|
|
204
|
+
A VDOM script tag containing the import map.
|
|
205
|
+
"""
|
|
206
|
+
from reactpy import html
|
|
207
|
+
from reactpy.executors.utils import default_import_map
|
|
208
|
+
|
|
209
|
+
if use_local and (framework or version): # nocov
|
|
210
|
+
raise ValueError("use_local cannot be used with framework or version")
|
|
211
|
+
|
|
212
|
+
framework = framework or "preact"
|
|
213
|
+
if framework and framework not in {"preact", "react"}: # nocov
|
|
214
|
+
raise ValueError("framework must be 'preact' or 'react'")
|
|
215
|
+
|
|
216
|
+
# Import map for ReactPy's local framework (re-exported/bundled/minified version of Preact)
|
|
217
|
+
if use_local:
|
|
218
|
+
return html.script(
|
|
219
|
+
{"type": "importmap", "id": "reactpy-importmap"},
|
|
220
|
+
default_import_map(),
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Import map for ReactJS from esm.sh
|
|
224
|
+
if framework == "react":
|
|
225
|
+
version = version or "19"
|
|
226
|
+
postfix = "?dev" if REACTPY_DEBUG.current else ""
|
|
227
|
+
return html.script(
|
|
228
|
+
{"type": "importmap", "id": "reactpy-importmap"},
|
|
229
|
+
f"""{{
|
|
230
|
+
"imports": {{
|
|
231
|
+
"react": "https://esm.sh/v135/react@{version}{postfix}",
|
|
232
|
+
"react-dom": "https://esm.sh/v135/react-dom@{version}{postfix}",
|
|
233
|
+
"react-dom/client": "https://esm.sh/v135/react-dom@{version}/client{postfix}",
|
|
234
|
+
"react/jsx-runtime": "https://esm.sh/v135/react@{version}/jsx-runtime{postfix}"
|
|
235
|
+
}}
|
|
236
|
+
}}""".replace("\n", "").replace(" ", ""),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# Import map for Preact from esm.sh
|
|
240
|
+
if framework == "preact":
|
|
241
|
+
version = version or "10"
|
|
242
|
+
postfix = "?dev" if REACTPY_DEBUG.current else ""
|
|
243
|
+
return html.script(
|
|
244
|
+
{"type": "importmap", "id": "reactpy-importmap"},
|
|
245
|
+
f"""{{
|
|
246
|
+
"imports": {{
|
|
247
|
+
"react": "https://esm.sh/v135/preact@{version}/compat{postfix}",
|
|
248
|
+
"react-dom": "https://esm.sh/v135/preact@{version}/compat{postfix}",
|
|
249
|
+
"react-dom/client": "https://esm.sh/v135/preact@{version}/compat/client{postfix}",
|
|
250
|
+
"react/jsx-runtime": "https://esm.sh/v135/preact@{version}/compat/jsx-runtime{postfix}"
|
|
251
|
+
}}
|
|
252
|
+
}}""".replace("\n", "").replace(" ", ""),
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def module_name_suffix(name: str) -> str:
|
|
257
|
+
if name.startswith("@"):
|
|
258
|
+
name = name[1:]
|
|
259
|
+
head, _, tail = name.partition("@") # handle version identifier
|
|
260
|
+
_, _, tail = tail.partition("/") # get section after version
|
|
261
|
+
return PurePosixPath(tail or head).suffix or ".js"
|
|
262
|
+
|
|
263
|
+
|
|
181
264
|
def get_module_path(name: str) -> Path:
|
|
182
265
|
directory = REACTPY_WEB_MODULES_DIR.current
|
|
183
266
|
path = directory.joinpath(*name.split("/"))
|
|
184
267
|
return path.with_suffix(path.suffix)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
def import_reactjs():
|
|
188
|
-
from reactpy import html
|
|
189
|
-
|
|
190
|
-
base_url = config.REACTPY_PATH_PREFIX.current.strip("/")
|
|
191
|
-
return html.script(
|
|
192
|
-
{"type": "importmap"},
|
|
193
|
-
f"""
|
|
194
|
-
{{
|
|
195
|
-
"imports": {{
|
|
196
|
-
"react": "/{base_url}/static/react.js",
|
|
197
|
-
"react-dom": "/{base_url}/static/react-dom.js",
|
|
198
|
-
"react-dom/client": "/{base_url}/static/react-dom.js",
|
|
199
|
-
"react/jsx-runtime": "/{base_url}/static/react-jsx-runtime.js"
|
|
200
|
-
}}
|
|
201
|
-
}}
|
|
202
|
-
""",
|
|
203
|
-
)
|
reactpy/reactjs/utils.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import filecmp
|
|
2
2
|
import logging
|
|
3
|
+
import os
|
|
3
4
|
import re
|
|
4
5
|
import shutil
|
|
5
|
-
|
|
6
|
+
import time
|
|
7
|
+
from contextlib import contextmanager, suppress
|
|
8
|
+
from pathlib import Path
|
|
6
9
|
from urllib.parse import urlparse, urlunparse
|
|
7
10
|
|
|
8
11
|
import requests
|
|
@@ -10,15 +13,7 @@ import requests
|
|
|
10
13
|
logger = logging.getLogger(__name__)
|
|
11
14
|
|
|
12
15
|
|
|
13
|
-
def
|
|
14
|
-
if name.startswith("@"):
|
|
15
|
-
name = name[1:]
|
|
16
|
-
head, _, tail = name.partition("@") # handle version identifier
|
|
17
|
-
_, _, tail = tail.partition("/") # get section after version
|
|
18
|
-
return PurePosixPath(tail or head).suffix or ".js"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def resolve_from_module_file(
|
|
16
|
+
def resolve_names_from_file(
|
|
22
17
|
file: Path,
|
|
23
18
|
max_depth: int,
|
|
24
19
|
is_regex_import: bool = False,
|
|
@@ -30,25 +25,25 @@ def resolve_from_module_file(
|
|
|
30
25
|
logger.warning(f"Did not resolve imports for unknown file {file}")
|
|
31
26
|
return set()
|
|
32
27
|
|
|
33
|
-
names, references =
|
|
28
|
+
names, references = resolve_names_from_source(
|
|
34
29
|
file.read_text(encoding="utf-8"), exclude_default=is_regex_import
|
|
35
30
|
)
|
|
36
31
|
|
|
37
32
|
for ref in references:
|
|
38
33
|
if urlparse(ref).scheme: # is an absolute URL
|
|
39
34
|
names.update(
|
|
40
|
-
|
|
35
|
+
resolve_names_from_url(ref, max_depth - 1, is_regex_import=True)
|
|
41
36
|
)
|
|
42
37
|
else:
|
|
43
38
|
path = file.parent.joinpath(*ref.split("/"))
|
|
44
39
|
names.update(
|
|
45
|
-
|
|
40
|
+
resolve_names_from_file(path, max_depth - 1, is_regex_import=True)
|
|
46
41
|
)
|
|
47
42
|
|
|
48
43
|
return names
|
|
49
44
|
|
|
50
45
|
|
|
51
|
-
def
|
|
46
|
+
def resolve_names_from_url(
|
|
52
47
|
url: str,
|
|
53
48
|
max_depth: int,
|
|
54
49
|
is_regex_import: bool = False,
|
|
@@ -64,18 +59,16 @@ def resolve_from_module_url(
|
|
|
64
59
|
logger.warning(f"Did not resolve imports for url {url} {reason}")
|
|
65
60
|
return set()
|
|
66
61
|
|
|
67
|
-
names, references =
|
|
68
|
-
text, exclude_default=is_regex_import
|
|
69
|
-
)
|
|
62
|
+
names, references = resolve_names_from_source(text, exclude_default=is_regex_import)
|
|
70
63
|
|
|
71
64
|
for ref in references:
|
|
72
65
|
url = normalize_url_path(url, ref)
|
|
73
|
-
names.update(
|
|
66
|
+
names.update(resolve_names_from_url(url, max_depth - 1, is_regex_import=True))
|
|
74
67
|
|
|
75
68
|
return names
|
|
76
69
|
|
|
77
70
|
|
|
78
|
-
def
|
|
71
|
+
def resolve_names_from_source(
|
|
79
72
|
content: str, exclude_default: bool
|
|
80
73
|
) -> tuple[set[str], set[str]]:
|
|
81
74
|
"""Find names exported by the given JavaScript module content to assist with ReactPy import resolution.
|
|
@@ -167,9 +160,26 @@ def are_files_identical(f1: Path, f2: Path) -> bool:
|
|
|
167
160
|
def copy_file(target: Path, source: Path, symlink: bool) -> None:
|
|
168
161
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
169
162
|
if symlink:
|
|
163
|
+
if target.exists():
|
|
164
|
+
target.unlink()
|
|
170
165
|
target.symlink_to(source)
|
|
171
166
|
else:
|
|
172
|
-
|
|
167
|
+
temp_target = target.with_suffix(f"{target.suffix}.tmp")
|
|
168
|
+
shutil.copy(source, temp_target)
|
|
169
|
+
try:
|
|
170
|
+
temp_target.replace(target)
|
|
171
|
+
except OSError:
|
|
172
|
+
# On Windows, replace might fail if the file is open
|
|
173
|
+
# Retry once after a short delay
|
|
174
|
+
time.sleep(0.1)
|
|
175
|
+
try:
|
|
176
|
+
temp_target.replace(target)
|
|
177
|
+
except OSError:
|
|
178
|
+
# If it still fails, try to unlink and rename
|
|
179
|
+
# This is not atomic, but it's a fallback
|
|
180
|
+
if target.exists():
|
|
181
|
+
target.unlink()
|
|
182
|
+
temp_target.rename(target)
|
|
173
183
|
|
|
174
184
|
|
|
175
185
|
_JS_DEFAULT_EXPORT_PATTERN = re.compile(
|
|
@@ -181,3 +191,22 @@ _JS_FUNC_OR_CLS_EXPORT_PATTERN = re.compile(
|
|
|
181
191
|
_JS_GENERAL_EXPORT_PATTERN = re.compile(
|
|
182
192
|
r"(?:^|;|})\s*export(?=\s+|{)(.*?)(?=;|$)", re.MULTILINE
|
|
183
193
|
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@contextmanager
|
|
197
|
+
def file_lock(lock_file: Path, timeout: float = 10.0):
|
|
198
|
+
start_time = time.time()
|
|
199
|
+
while True:
|
|
200
|
+
try:
|
|
201
|
+
fd = os.open(lock_file, os.O_CREAT | os.O_EXCL | os.O_RDWR)
|
|
202
|
+
os.close(fd)
|
|
203
|
+
break
|
|
204
|
+
except OSError as e:
|
|
205
|
+
if time.time() - start_time > timeout:
|
|
206
|
+
raise TimeoutError(f"Could not acquire lock {lock_file}") from e
|
|
207
|
+
time.sleep(0.1)
|
|
208
|
+
try:
|
|
209
|
+
yield
|
|
210
|
+
finally:
|
|
211
|
+
with suppress(OSError):
|
|
212
|
+
os.unlink(lock_file)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{
|
|
2
|
-
export{K as
|
|
1
|
+
import{h as B,k as D}from"./index-beq660xy.js";var G=/["&<]/;function C(b){if(b.length===0||G.test(b)===!1)return b;for(var h=0,k=0,y="",q="";k<b.length;k++){switch(b.charCodeAt(k)){case 34:q=""";break;case 38:q="&";break;case 60:q="<";break;default:continue}k!==h&&(y+=b.slice(h,k)),y+=q,h=k+1}return k!==h&&(y+=b.slice(h,k)),y}var H=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,I=0,J=Array.isArray;function K(b,h,k,y,q,A){h||(h={});var z,v,w=h;if("ref"in w)for(v in w={},h)v=="ref"?z=h[v]:w[v]=h[v];var E={type:b,props:w,key:k,ref:z,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:--I,__i:-1,__u:0,__source:q,__self:A};if(typeof b=="function"&&(z=b.defaultProps))for(v in z)w[v]===void 0&&(w[v]=z[v]);return B.vnode&&B.vnode(E),E}function Q(b){var h=K(D,{tpl:b,exprs:[].slice.call(arguments,1)});return h.key=h.__v,h}var F={},N=/[A-Z]/g;function S(b,h){if(B.attr){var k=B.attr(b,h);if(typeof k=="string")return k}if(h=function(w){return w!==null&&typeof w=="object"&&typeof w.valueOf=="function"?w.valueOf():w}(h),b==="ref"||b==="key")return"";if(b==="style"&&typeof h=="object"){var y="";for(var q in h){var A=h[q];if(A!=null&&A!==""){var z=q[0]=="-"?q:F[q]||(F[q]=q.replace(N,"-$&").toLowerCase()),v=";";typeof A!="number"||z.startsWith("--")||H.test(z)||(v="px;"),y=y+z+":"+A+v}}return b+'="'+C(y)+'"'}return h==null||h===!1||typeof h=="function"||typeof h=="object"?"":h===!0?b:b+'="'+C(""+h)+'"'}function O(b){if(b==null||typeof b=="boolean"||typeof b=="function")return null;if(typeof b=="object"){if(b.constructor===void 0)return b;if(J(b)){for(var h=0;h<b.length;h++)b[h]=O(b[h]);return b}}return C(""+b)}
|
|
2
|
+
export{K as a,Q as b,S as c,O as d};
|
|
3
3
|
|
|
4
|
-
//# debugId=
|
|
5
|
-
//# sourceMappingURL=index-
|
|
4
|
+
//# debugId=87FF749D2D1F353A64756E2164756E21
|
|
5
|
+
//# sourceMappingURL=index-64wy0fss.js.map
|
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
"import{options as r,Fragment as e}from\"preact\";export{Fragment}from\"preact\";var t=/[\"&<]/;function n(r){if(0===r.length||!1===t.test(r))return r;for(var e=0,n=0,o=\"\",f=\"\";n<r.length;n++){switch(r.charCodeAt(n)){case 34:f=\""\";break;case 38:f=\"&\";break;case 60:f=\"<\";break;default:continue}n!==e&&(o+=r.slice(e,n)),o+=f,e=n+1}return n!==e&&(o+=r.slice(e,n)),o}var o=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,f=0,i=Array.isArray;function u(e,t,n,o,i,u){t||(t={});var a,c,p=t;if(\"ref\"in p)for(c in p={},t)\"ref\"==c?a=t[c]:p[c]=t[c];var l={type:e,props:p,key:n,ref:a,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:--f,__i:-1,__u:0,__source:i,__self:u};if(\"function\"==typeof e&&(a=e.defaultProps))for(c in a)void 0===p[c]&&(p[c]=a[c]);return r.vnode&&r.vnode(l),l}function a(r){var t=u(e,{tpl:r,exprs:[].slice.call(arguments,1)});return t.key=t.__v,t}var c={},p=/[A-Z]/g;function l(e,t){if(r.attr){var f=r.attr(e,t);if(\"string\"==typeof f)return f}if(t=function(r){return null!==r&&\"object\"==typeof r&&\"function\"==typeof r.valueOf?r.valueOf():r}(t),\"ref\"===e||\"key\"===e)return\"\";if(\"style\"===e&&\"object\"==typeof t){var i=\"\";for(var u in t){var a=t[u];if(null!=a&&\"\"!==a){var l=\"-\"==u[0]?u:c[u]||(c[u]=u.replace(p,\"-$&\").toLowerCase()),s=\";\";\"number\"!=typeof a||l.startsWith(\"--\")||o.test(l)||(s=\"px;\"),i=i+l+\":\"+a+s}}return e+'=\"'+n(i)+'\"'}return null==t||!1===t||\"function\"==typeof t||\"object\"==typeof t?\"\":!0===t?e:e+'=\"'+n(\"\"+t)+'\"'}function s(r){if(null==r||\"boolean\"==typeof r||\"function\"==typeof r)return null;if(\"object\"==typeof r){if(void 0===r.constructor)return r;if(i(r)){for(var e=0;e<r.length;e++)r[e]=s(r[e]);return r}}return n(\"\"+r)}export{u as jsx,l as jsxAttr,u as jsxDEV,s as jsxEscape,a as jsxTemplate,u as jsxs};\n//# sourceMappingURL=jsxRuntime.module.js.map\n"
|
|
6
6
|
],
|
|
7
7
|
"mappings": "+CAA4E,IAAI,EAAE,QAAQ,SAAS,CAAC,CAAC,EAAE,CAAC,GAAO,EAAE,SAAN,GAAmB,EAAE,KAAK,CAAC,IAAb,GAAe,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,OAAO,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,OAAQ,IAAG,EAAE,SAAS,UAAW,IAAG,EAAE,QAAQ,UAAW,IAAG,EAAE,OAAO,cAAc,SAAS,IAAI,IAAI,GAAG,EAAE,MAAM,EAAE,CAAC,GAAG,GAAG,EAAE,EAAE,EAAE,EAAE,OAAO,IAAI,IAAI,GAAG,EAAE,MAAM,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,oEAAoE,EAAE,EAAE,EAAE,MAAM,QAAQ,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,EAAE,EAAE,EAAE,GAAG,QAAQ,EAAE,IAAI,KAAK,EAAE,CAAC,EAAE,EAAS,GAAP,MAAS,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,KAAK,GAAG,KAAK,IAAI,EAAE,IAAI,KAAK,IAAI,KAAK,YAAiB,OAAE,IAAI,EAAE,EAAE,IAAI,GAAG,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,GAAe,OAAO,GAAnB,aAAuB,EAAE,EAAE,cAAc,IAAI,KAAK,EAAW,EAAE,KAAN,SAAW,EAAE,GAAG,EAAE,IAAI,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,SAAS,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAa,OAAO,GAAjB,SAAmB,OAAO,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC,OAAc,IAAP,MAAoB,OAAO,GAAjB,UAAgC,OAAO,EAAE,SAArB,WAA6B,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAU,IAAR,OAAmB,IAAR,MAAU,MAAM,GAAG,GAAa,IAAV,SAAuB,OAAO,GAAjB,SAAmB,CAAC,IAAI,EAAE,GAAG,QAAQ,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,GAAG,GAAS,GAAN,MAAc,IAAL,GAAO,CAAC,IAAI,EAAO,EAAE,IAAP,IAAU,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,GAAG,EAAE,IAAc,OAAO,GAAjB,UAAoB,EAAE,WAAW,IAAI,GAAG,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,OAAa,GAAN,MAAc,IAAL,IAAoB,OAAO,GAAnB,YAAgC,OAAO,GAAjB,SAAmB,GAAQ,IAAL,GAAO,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE,IAAI,SAAS,CAAC,CAAC,EAAE,CAAC,GAAS,GAAN,MAAoB,OAAO,GAAlB,WAAiC,OAAO,GAAnB,WAAqB,OAAO,KAAK,GAAa,OAAO,GAAjB,SAAmB,CAAC,GAAY,EAAE,cAAN,OAAkB,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,OAAO,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,OAAO,GAAG,OAAO,EAAE,GAAG,CAAC",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "87FF749D2D1F353A64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|