reactpy 2.0.0b5__py3-none-any.whl → 2.0.0b7__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.
Files changed (52) hide show
  1. reactpy/__init__.py +4 -3
  2. reactpy/_html.py +11 -9
  3. reactpy/config.py +1 -1
  4. reactpy/core/_life_cycle_hook.py +4 -1
  5. reactpy/core/_thread_local.py +1 -1
  6. reactpy/core/hooks.py +11 -19
  7. reactpy/core/layout.py +43 -38
  8. reactpy/core/serve.py +11 -16
  9. reactpy/core/vdom.py +3 -5
  10. reactpy/executors/asgi/pyscript.py +4 -1
  11. reactpy/executors/asgi/standalone.py +1 -1
  12. reactpy/{pyscript → executors/pyscript}/component_template.py +1 -1
  13. reactpy/{pyscript → executors/pyscript}/components.py +1 -1
  14. reactpy/{pyscript → executors/pyscript}/utils.py +1 -1
  15. reactpy/executors/utils.py +32 -7
  16. reactpy/reactjs/__init__.py +351 -0
  17. reactpy/reactjs/module.py +267 -0
  18. reactpy/reactjs/types.py +7 -0
  19. reactpy/reactjs/utils.py +212 -0
  20. reactpy/static/index-64wy0fss.js +5 -0
  21. reactpy/static/index-64wy0fss.js.map +10 -0
  22. reactpy/static/index-beq660xy.js +5 -0
  23. reactpy/static/index-beq660xy.js.map +12 -0
  24. reactpy/static/index.js +2 -2
  25. reactpy/static/index.js.map +7 -10
  26. reactpy/static/preact-dom.js +4 -0
  27. reactpy/static/preact-dom.js.map +11 -0
  28. reactpy/static/preact-jsx-runtime.js +4 -0
  29. reactpy/static/preact-jsx-runtime.js.map +9 -0
  30. reactpy/static/preact.js +4 -0
  31. reactpy/static/preact.js.map +10 -0
  32. reactpy/templatetags/jinja.py +4 -1
  33. reactpy/testing/__init__.py +2 -7
  34. reactpy/testing/backend.py +24 -12
  35. reactpy/testing/common.py +1 -9
  36. reactpy/testing/display.py +65 -28
  37. reactpy/testing/logs.py +1 -1
  38. reactpy/transforms.py +2 -2
  39. reactpy/types.py +17 -11
  40. reactpy/utils.py +1 -1
  41. reactpy/web/__init__.py +0 -6
  42. reactpy/web/module.py +37 -473
  43. reactpy/web/utils.py +2 -158
  44. {reactpy-2.0.0b5.dist-info → reactpy-2.0.0b7.dist-info}/METADATA +1 -1
  45. {reactpy-2.0.0b5.dist-info → reactpy-2.0.0b7.dist-info}/RECORD +50 -38
  46. reactpy/testing/utils.py +0 -27
  47. reactpy/web/templates/react.js +0 -61
  48. /reactpy/{pyscript → executors/pyscript}/__init__.py +0 -0
  49. /reactpy/{pyscript → executors/pyscript}/layout_handler.py +0 -0
  50. {reactpy-2.0.0b5.dist-info → reactpy-2.0.0b7.dist-info}/WHEEL +0 -0
  51. {reactpy-2.0.0b5.dist-info → reactpy-2.0.0b7.dist-info}/entry_points.txt +0 -0
  52. {reactpy-2.0.0b5.dist-info → reactpy-2.0.0b7.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,351 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ from pathlib import Path
5
+ from typing import Any, overload
6
+
7
+ from reactpy.reactjs.module import (
8
+ file_to_module,
9
+ import_reactjs,
10
+ module_to_vdom,
11
+ string_to_module,
12
+ url_to_module,
13
+ )
14
+ from reactpy.reactjs.types import (
15
+ NAME_SOURCE,
16
+ URL_SOURCE,
17
+ )
18
+ from reactpy.types import JavaScriptModule, VdomConstructor
19
+
20
+ __all__ = [
21
+ "NAME_SOURCE",
22
+ "URL_SOURCE",
23
+ "component_from_file",
24
+ "component_from_npm",
25
+ "component_from_string",
26
+ "component_from_url",
27
+ "import_reactjs",
28
+ ]
29
+
30
+ _URL_JS_MODULE_CACHE: dict[str, JavaScriptModule] = {}
31
+ _FILE_JS_MODULE_CACHE: dict[str, JavaScriptModule] = {}
32
+ _STRING_JS_MODULE_CACHE: dict[str, JavaScriptModule] = {}
33
+
34
+
35
+ @overload
36
+ def component_from_url(
37
+ url: str,
38
+ import_names: str,
39
+ resolve_imports: bool = ...,
40
+ resolve_imports_depth: int = ...,
41
+ fallback: Any | None = ...,
42
+ unmount_before_update: bool = ...,
43
+ allow_children: bool = ...,
44
+ ) -> VdomConstructor: ...
45
+
46
+
47
+ @overload
48
+ def component_from_url(
49
+ url: str,
50
+ import_names: list[str] | tuple[str, ...],
51
+ resolve_imports: bool = ...,
52
+ resolve_imports_depth: int = ...,
53
+ fallback: Any | None = ...,
54
+ unmount_before_update: bool = ...,
55
+ allow_children: bool = ...,
56
+ ) -> list[VdomConstructor]: ...
57
+
58
+
59
+ def component_from_url(
60
+ url: str,
61
+ import_names: str | list[str] | tuple[str, ...],
62
+ resolve_imports: bool = False,
63
+ resolve_imports_depth: int = 5,
64
+ fallback: Any | None = None,
65
+ unmount_before_update: bool = False,
66
+ allow_children: bool = True,
67
+ ) -> VdomConstructor | list[VdomConstructor]:
68
+ """Import a component from a URL.
69
+
70
+ Parameters:
71
+ url:
72
+ The URL to import the component from.
73
+ import_names:
74
+ One or more component names to import. If given as a string, a single component
75
+ will be returned. If a list is given, then a list of components will be
76
+ returned.
77
+ resolve_imports:
78
+ Whether to try and find all the named imports of this module.
79
+ resolve_imports_depth:
80
+ How deeply to search for those imports.
81
+ fallback:
82
+ What to temporarily display while the module is being loaded.
83
+ unmount_before_update:
84
+ Cause the component to be unmounted before each update. This option should
85
+ only be used if the imported package fails to re-render when props change.
86
+ Using this option has negative performance consequences since all DOM
87
+ elements must be changed on each render. See :issue:`461` for more info.
88
+ allow_children:
89
+ Whether or not these components can have children.
90
+ """
91
+ key = f"{url}{resolve_imports}{resolve_imports_depth}{unmount_before_update}"
92
+ if key in _URL_JS_MODULE_CACHE:
93
+ module = _URL_JS_MODULE_CACHE[key]
94
+ else:
95
+ module = url_to_module(
96
+ url,
97
+ fallback=fallback,
98
+ resolve_imports=resolve_imports,
99
+ resolve_imports_depth=resolve_imports_depth,
100
+ unmount_before_update=unmount_before_update,
101
+ )
102
+ _URL_JS_MODULE_CACHE[key] = module
103
+ return module_to_vdom(module, import_names, fallback, allow_children)
104
+
105
+
106
+ @overload
107
+ def component_from_npm(
108
+ package: str,
109
+ import_names: str,
110
+ resolve_imports: bool = ...,
111
+ resolve_imports_depth: int = ...,
112
+ version: str = "latest",
113
+ cdn: str = "https://esm.sh",
114
+ fallback: Any | None = ...,
115
+ unmount_before_update: bool = ...,
116
+ allow_children: bool = ...,
117
+ ) -> VdomConstructor: ...
118
+
119
+
120
+ @overload
121
+ def component_from_npm(
122
+ package: str,
123
+ import_names: list[str] | tuple[str, ...],
124
+ resolve_imports: bool = ...,
125
+ resolve_imports_depth: int = ...,
126
+ version: str = "latest",
127
+ cdn: str = "https://esm.sh",
128
+ fallback: Any | None = ...,
129
+ unmount_before_update: bool = ...,
130
+ allow_children: bool = ...,
131
+ ) -> list[VdomConstructor]: ...
132
+
133
+
134
+ def component_from_npm(
135
+ package: str,
136
+ import_names: str | list[str] | tuple[str, ...],
137
+ resolve_imports: bool = False,
138
+ resolve_imports_depth: int = 5,
139
+ version: str = "latest",
140
+ cdn: str = "https://esm.sh",
141
+ fallback: Any | None = None,
142
+ unmount_before_update: bool = False,
143
+ allow_children: bool = True,
144
+ ) -> VdomConstructor | list[VdomConstructor]:
145
+ """Import a component from an NPM package.
146
+
147
+ Is is mandatory to load `reactpy.reactjs.import_reactjs()` on your page before using this
148
+ function. It is recommended to put this within your HTML <head> content.
149
+
150
+ Parameters:
151
+ package:
152
+ The name of the NPM package.
153
+ import_names:
154
+ One or more component names to import. If given as a string, a single component
155
+ will be returned. If a list is given, then a list of components will be
156
+ returned.
157
+ resolve_imports:
158
+ Whether to try and find all the named imports of this module.
159
+ resolve_imports_depth:
160
+ How deeply to search for those imports.
161
+ version:
162
+ The version of the package to use. Defaults to "latest".
163
+ cdn:
164
+ The CDN to use. Defaults to "https://esm.sh".
165
+ fallback:
166
+ What to temporarily display while the module is being loaded.
167
+ unmount_before_update:
168
+ Cause the component to be unmounted before each update. This option should
169
+ only be used if the imported package fails to re-render when props change.
170
+ Using this option has negative performance consequences since all DOM
171
+ elements must be changed on each render. See :issue:`461` for more info.
172
+ allow_children:
173
+ Whether or not these components can have children.
174
+ """
175
+ url = f"{cdn}/{package}@{version}"
176
+
177
+ if "esm.sh" in cdn:
178
+ url += "&" if "?" in url else "?"
179
+ url += "external=react,react-dom,react/jsx-runtime&bundle&target=es2020"
180
+
181
+ return component_from_url(
182
+ url,
183
+ import_names,
184
+ fallback=fallback,
185
+ resolve_imports=resolve_imports,
186
+ resolve_imports_depth=resolve_imports_depth,
187
+ unmount_before_update=unmount_before_update,
188
+ allow_children=allow_children,
189
+ )
190
+
191
+
192
+ @overload
193
+ def component_from_file(
194
+ file: str | Path,
195
+ import_names: str,
196
+ resolve_imports: bool = ...,
197
+ resolve_imports_depth: int = ...,
198
+ name: str = "",
199
+ fallback: Any | None = ...,
200
+ unmount_before_update: bool = ...,
201
+ symlink: bool = ...,
202
+ allow_children: bool = ...,
203
+ ) -> VdomConstructor: ...
204
+
205
+
206
+ @overload
207
+ def component_from_file(
208
+ file: str | Path,
209
+ import_names: list[str] | tuple[str, ...],
210
+ resolve_imports: bool = ...,
211
+ resolve_imports_depth: int = ...,
212
+ name: str = "",
213
+ fallback: Any | None = ...,
214
+ unmount_before_update: bool = ...,
215
+ symlink: bool = ...,
216
+ allow_children: bool = ...,
217
+ ) -> list[VdomConstructor]: ...
218
+
219
+
220
+ def component_from_file(
221
+ file: str | Path,
222
+ import_names: str | list[str] | tuple[str, ...],
223
+ resolve_imports: bool = False,
224
+ resolve_imports_depth: int = 5,
225
+ name: str = "",
226
+ fallback: Any | None = None,
227
+ unmount_before_update: bool = False,
228
+ symlink: bool = False,
229
+ allow_children: bool = True,
230
+ ) -> VdomConstructor | list[VdomConstructor]:
231
+ """Import a component from a file.
232
+
233
+ Parameters:
234
+ file:
235
+ The file from which the content of the web module will be created.
236
+ import_names:
237
+ One or more component names to import. If given as a string, a single component
238
+ will be returned. If a list is given, then a list of components will be
239
+ returned.
240
+ resolve_imports:
241
+ Whether to try and find all the named imports of this module.
242
+ resolve_imports_depth:
243
+ How deeply to search for those imports.
244
+ name:
245
+ The human-readable name of the ReactJS package
246
+ fallback:
247
+ What to temporarily display while the module is being loaded.
248
+ unmount_before_update:
249
+ Cause the component to be unmounted before each update. This option should
250
+ only be used if the imported package fails to re-render when props change.
251
+ Using this option has negative performance consequences since all DOM
252
+ elements must be changed on each render. See :issue:`461` for more info.
253
+ symlink:
254
+ Whether the web module should be saved as a symlink to the given ``file``.
255
+ allow_children:
256
+ Whether or not these components can have children.
257
+ """
258
+ name = name or hashlib.sha256(str(file).encode()).hexdigest()[:10]
259
+ key = f"{name}{resolve_imports}{resolve_imports_depth}{unmount_before_update}"
260
+ if key in _FILE_JS_MODULE_CACHE:
261
+ module = _FILE_JS_MODULE_CACHE[key]
262
+ else:
263
+ module = file_to_module(
264
+ name,
265
+ file,
266
+ fallback=fallback,
267
+ resolve_imports=resolve_imports,
268
+ resolve_imports_depth=resolve_imports_depth,
269
+ unmount_before_update=unmount_before_update,
270
+ symlink=symlink,
271
+ )
272
+ _FILE_JS_MODULE_CACHE[key] = module
273
+ return module_to_vdom(module, import_names, fallback, allow_children)
274
+
275
+
276
+ @overload
277
+ def component_from_string(
278
+ content: str,
279
+ import_names: str,
280
+ resolve_imports: bool = ...,
281
+ resolve_imports_depth: int = ...,
282
+ name: str = "",
283
+ fallback: Any | None = ...,
284
+ unmount_before_update: bool = ...,
285
+ allow_children: bool = ...,
286
+ ) -> VdomConstructor: ...
287
+
288
+
289
+ @overload
290
+ def component_from_string(
291
+ content: str,
292
+ import_names: list[str] | tuple[str, ...],
293
+ resolve_imports: bool = ...,
294
+ resolve_imports_depth: int = ...,
295
+ name: str = "",
296
+ fallback: Any | None = ...,
297
+ unmount_before_update: bool = ...,
298
+ allow_children: bool = ...,
299
+ ) -> list[VdomConstructor]: ...
300
+
301
+
302
+ def component_from_string(
303
+ content: str,
304
+ import_names: str | list[str] | tuple[str, ...],
305
+ resolve_imports: bool = False,
306
+ resolve_imports_depth: int = 5,
307
+ name: str = "",
308
+ fallback: Any | None = None,
309
+ unmount_before_update: bool = False,
310
+ allow_children: bool = True,
311
+ ) -> VdomConstructor | list[VdomConstructor]:
312
+ """Import a component from a string.
313
+
314
+ Parameters:
315
+ content:
316
+ The contents of the web module
317
+ import_names:
318
+ One or more component names to import. If given as a string, a single component
319
+ will be returned. If a list is given, then a list of components will be
320
+ returned.
321
+ resolve_imports:
322
+ Whether to try and find all the named imports of this module.
323
+ resolve_imports_depth:
324
+ How deeply to search for those imports.
325
+ name:
326
+ The human-readable name of the ReactJS package
327
+ fallback:
328
+ What to temporarily display while the module is being loaded.
329
+ unmount_before_update:
330
+ Cause the component to be unmounted before each update. This option should
331
+ only be used if the imported package fails to re-render when props change.
332
+ Using this option has negative performance consequences since all DOM
333
+ elements must be changed on each render. See :issue:`461` for more info.
334
+ allow_children:
335
+ Whether or not these components can have children.
336
+ """
337
+ name = name or hashlib.sha256(content.encode()).hexdigest()[:10]
338
+ key = f"{name}{resolve_imports}{resolve_imports_depth}{unmount_before_update}"
339
+ if key in _STRING_JS_MODULE_CACHE:
340
+ module = _STRING_JS_MODULE_CACHE[key]
341
+ else:
342
+ module = string_to_module(
343
+ name,
344
+ content,
345
+ fallback=fallback,
346
+ resolve_imports=resolve_imports,
347
+ resolve_imports_depth=resolve_imports_depth,
348
+ unmount_before_update=unmount_before_update,
349
+ )
350
+ _STRING_JS_MODULE_CACHE[key] = module
351
+ return module_to_vdom(module, import_names, fallback, allow_children)
@@ -0,0 +1,267 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from pathlib import Path, PurePosixPath
5
+ from typing import Any, Literal
6
+
7
+ from reactpy.config import REACTPY_DEBUG, REACTPY_WEB_MODULES_DIR
8
+ from reactpy.core.vdom import Vdom
9
+ from reactpy.reactjs.types import NAME_SOURCE, URL_SOURCE
10
+ from reactpy.reactjs.utils import (
11
+ are_files_identical,
12
+ copy_file,
13
+ file_lock,
14
+ resolve_names_from_file,
15
+ resolve_names_from_url,
16
+ )
17
+ from reactpy.types import ImportSourceDict, JavaScriptModule, VdomConstructor, VdomDict
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ def url_to_module(
23
+ url: str,
24
+ fallback: Any | None = None,
25
+ resolve_imports: bool = True,
26
+ resolve_imports_depth: int = 5,
27
+ unmount_before_update: bool = False,
28
+ ) -> JavaScriptModule:
29
+ return JavaScriptModule(
30
+ source=url,
31
+ source_type=URL_SOURCE,
32
+ default_fallback=fallback,
33
+ file=None,
34
+ import_names=(
35
+ resolve_names_from_url(url, resolve_imports_depth)
36
+ if resolve_imports
37
+ else None
38
+ ),
39
+ unmount_before_update=unmount_before_update,
40
+ )
41
+
42
+
43
+ def file_to_module(
44
+ name: str,
45
+ file: str | Path,
46
+ fallback: Any | None = None,
47
+ resolve_imports: bool = True,
48
+ resolve_imports_depth: int = 5,
49
+ unmount_before_update: bool = False,
50
+ symlink: bool = False,
51
+ ) -> JavaScriptModule:
52
+ name += module_name_suffix(name)
53
+
54
+ source_file = Path(file).resolve()
55
+ target_file = get_module_path(name)
56
+
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
+
71
+ return JavaScriptModule(
72
+ source=name,
73
+ source_type=NAME_SOURCE,
74
+ default_fallback=fallback,
75
+ file=target_file,
76
+ import_names=(
77
+ resolve_names_from_file(source_file, resolve_imports_depth)
78
+ if resolve_imports
79
+ else None
80
+ ),
81
+ unmount_before_update=unmount_before_update,
82
+ )
83
+
84
+
85
+ def string_to_module(
86
+ name: str,
87
+ content: str,
88
+ fallback: Any | None = None,
89
+ resolve_imports: bool = True,
90
+ resolve_imports_depth: int = 5,
91
+ unmount_before_update: bool = False,
92
+ ) -> JavaScriptModule:
93
+ name += module_name_suffix(name)
94
+
95
+ target_file = get_module_path(name)
96
+
97
+ if target_file.exists() and target_file.read_text(encoding="utf-8") != content:
98
+ logger.info(
99
+ f"Existing web module {name!r} will "
100
+ f"be replaced with {target_file.resolve()}"
101
+ )
102
+ target_file.unlink()
103
+
104
+ target_file.parent.mkdir(parents=True, exist_ok=True)
105
+ target_file.write_text(content)
106
+
107
+ return JavaScriptModule(
108
+ source=name,
109
+ source_type=NAME_SOURCE,
110
+ default_fallback=fallback,
111
+ file=target_file,
112
+ import_names=(
113
+ resolve_names_from_file(target_file, resolve_imports_depth)
114
+ if resolve_imports
115
+ else None
116
+ ),
117
+ unmount_before_update=unmount_before_update,
118
+ )
119
+
120
+
121
+ def module_to_vdom(
122
+ web_module: JavaScriptModule,
123
+ import_names: str | list[str] | tuple[str, ...],
124
+ fallback: Any | None = None,
125
+ allow_children: bool = True,
126
+ ) -> VdomConstructor | list[VdomConstructor]:
127
+ """Return one or more VDOM constructors from a :class:`JavaScriptModule`
128
+
129
+ Parameters:
130
+ import_names:
131
+ One or more names to import. If given as a string, a single component
132
+ will be returned. If a list is given, then a list of components will be
133
+ returned.
134
+ fallback:
135
+ What to temporarily display while the module is being loaded.
136
+ allow_children:
137
+ Whether or not these components can have children.
138
+ """
139
+ if isinstance(import_names, str):
140
+ if (
141
+ web_module.import_names is not None
142
+ and import_names.split(".")[0] not in web_module.import_names
143
+ ):
144
+ msg = f"{web_module.source!r} does not contain {import_names!r}"
145
+ raise ValueError(msg)
146
+ return make_module(web_module, import_names, fallback, allow_children)
147
+ else:
148
+ if web_module.import_names is not None:
149
+ missing = sorted(
150
+ {e.split(".")[0] for e in import_names}.difference(
151
+ web_module.import_names
152
+ )
153
+ )
154
+ if missing:
155
+ msg = f"{web_module.source!r} does not contain {missing!r}"
156
+ raise ValueError(msg)
157
+ return [
158
+ make_module(web_module, name, fallback, allow_children)
159
+ for name in import_names
160
+ ]
161
+
162
+
163
+ def make_module(
164
+ web_module: JavaScriptModule,
165
+ name: str,
166
+ fallback: Any | None,
167
+ allow_children: bool,
168
+ ) -> VdomConstructor:
169
+ return Vdom(
170
+ name,
171
+ allow_children=allow_children,
172
+ import_source=ImportSourceDict(
173
+ source=web_module.source,
174
+ sourceType=web_module.source_type,
175
+ fallback=(fallback or web_module.default_fallback),
176
+ unmountBeforeUpdate=web_module.unmount_before_update,
177
+ ),
178
+ )
179
+
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/react@{version}{postfix}",
232
+ "react-dom": "https://esm.sh/react-dom@{version}{postfix}",
233
+ "react-dom/client": "https://esm.sh/react-dom@{version}/client{postfix}",
234
+ "react/jsx-runtime": "https://esm.sh/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/preact@{version}/compat{postfix}",
248
+ "react-dom": "https://esm.sh/preact@{version}/compat{postfix}",
249
+ "react-dom/client": "https://esm.sh/preact@{version}/compat/client{postfix}",
250
+ "react/jsx-runtime": "https://esm.sh/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
+
264
+ def get_module_path(name: str) -> Path:
265
+ directory = REACTPY_WEB_MODULES_DIR.current
266
+ path = directory.joinpath(*name.split("/"))
267
+ return path.with_suffix(path.suffix)
@@ -0,0 +1,7 @@
1
+ from reactpy.types import SourceType
2
+
3
+ NAME_SOURCE = SourceType("NAME")
4
+ """A named source - usually a Javascript package name"""
5
+
6
+ URL_SOURCE = SourceType("URL")
7
+ """A source loaded from a URL, usually a CDN"""