reflex 0.8.7a1__py3-none-any.whl → 0.8.8a1__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.
Potentially problematic release.
This version of reflex might be problematic. Click here for more details.
- reflex/app.py +13 -5
- reflex/app_mixins/lifespan.py +8 -2
- reflex/compiler/compiler.py +12 -12
- reflex/compiler/templates.py +629 -102
- reflex/compiler/utils.py +29 -20
- reflex/components/base/bare.py +17 -0
- reflex/components/component.py +37 -33
- reflex/components/core/cond.py +6 -12
- reflex/components/core/foreach.py +1 -1
- reflex/components/core/match.py +83 -60
- reflex/components/dynamic.py +3 -3
- reflex/components/el/elements/forms.py +31 -14
- reflex/components/el/elements/forms.pyi +0 -5
- reflex/components/lucide/icon.py +2 -1
- reflex/components/lucide/icon.pyi +2 -1
- reflex/components/markdown/markdown.py +2 -2
- reflex/components/radix/primitives/accordion.py +1 -1
- reflex/components/radix/primitives/drawer.py +1 -1
- reflex/components/radix/primitives/form.py +1 -1
- reflex/components/radix/primitives/slider.py +1 -1
- reflex/components/tags/cond_tag.py +14 -5
- reflex/components/tags/iter_tag.py +0 -26
- reflex/components/tags/match_tag.py +15 -6
- reflex/components/tags/tag.py +3 -6
- reflex/components/tags/tagless.py +14 -0
- reflex/constants/base.py +0 -2
- reflex/constants/installer.py +4 -4
- reflex/custom_components/custom_components.py +202 -15
- reflex/experimental/client_state.py +1 -1
- reflex/istate/manager.py +2 -1
- reflex/plugins/shared_tailwind.py +87 -62
- reflex/plugins/tailwind_v3.py +2 -2
- reflex/plugins/tailwind_v4.py +4 -4
- reflex/state.py +5 -1
- reflex/utils/format.py +2 -3
- reflex/utils/frontend_skeleton.py +2 -2
- reflex/utils/imports.py +18 -0
- reflex/utils/pyi_generator.py +10 -2
- reflex/utils/telemetry.py +4 -1
- reflex/utils/templates.py +1 -6
- {reflex-0.8.7a1.dist-info → reflex-0.8.8a1.dist-info}/METADATA +3 -4
- {reflex-0.8.7a1.dist-info → reflex-0.8.8a1.dist-info}/RECORD +45 -67
- reflex/.templates/jinja/app/rxconfig.py.jinja2 +0 -9
- reflex/.templates/jinja/custom_components/README.md.jinja2 +0 -9
- reflex/.templates/jinja/custom_components/__init__.py.jinja2 +0 -1
- reflex/.templates/jinja/custom_components/demo_app.py.jinja2 +0 -39
- reflex/.templates/jinja/custom_components/pyproject.toml.jinja2 +0 -25
- reflex/.templates/jinja/custom_components/src.py.jinja2 +0 -57
- reflex/.templates/jinja/web/package.json.jinja2 +0 -27
- reflex/.templates/jinja/web/pages/_app.js.jinja2 +0 -62
- reflex/.templates/jinja/web/pages/_document.js.jinja2 +0 -9
- reflex/.templates/jinja/web/pages/base_page.js.jinja2 +0 -21
- reflex/.templates/jinja/web/pages/component.js.jinja2 +0 -2
- reflex/.templates/jinja/web/pages/custom_component.js.jinja2 +0 -22
- reflex/.templates/jinja/web/pages/index.js.jinja2 +0 -18
- reflex/.templates/jinja/web/pages/macros.js.jinja2 +0 -38
- reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 +0 -15
- reflex/.templates/jinja/web/pages/stateful_components.js.jinja2 +0 -5
- reflex/.templates/jinja/web/pages/utils.js.jinja2 +0 -93
- reflex/.templates/jinja/web/styles/styles.css.jinja2 +0 -6
- reflex/.templates/jinja/web/utils/context.js.jinja2 +0 -129
- reflex/.templates/jinja/web/utils/theme.js.jinja2 +0 -1
- reflex/.templates/jinja/web/vite.config.js.jinja2 +0 -74
- reflex/components/core/client_side_routing.pyi +0 -68
- {reflex-0.8.7a1.dist-info → reflex-0.8.8a1.dist-info}/WHEEL +0 -0
- {reflex-0.8.7a1.dist-info → reflex-0.8.8a1.dist-info}/entry_points.txt +0 -0
- {reflex-0.8.7a1.dist-info → reflex-0.8.8a1.dist-info}/licenses/LICENSE +0 -0
reflex/compiler/templates.py
CHANGED
|
@@ -2,15 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import json
|
|
6
|
+
from collections.abc import Iterable, Mapping
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
6
8
|
|
|
7
9
|
from reflex import constants
|
|
8
10
|
from reflex.constants import Hooks
|
|
9
11
|
from reflex.utils.format import format_state_name, json_dumps
|
|
10
12
|
from reflex.vars.base import VarData
|
|
11
13
|
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from reflex.compiler.utils import _ImportDict
|
|
16
|
+
from reflex.components.component import Component, StatefulComponent
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
|
|
19
|
+
def _sort_hooks(
|
|
20
|
+
hooks: dict[str, VarData | None],
|
|
21
|
+
) -> tuple[list[str], list[str], list[str]]:
|
|
14
22
|
"""Sort the hooks by their position.
|
|
15
23
|
|
|
16
24
|
Args:
|
|
@@ -19,155 +27,674 @@ def _sort_hooks(hooks: dict[str, VarData | None]):
|
|
|
19
27
|
Returns:
|
|
20
28
|
The sorted hooks.
|
|
21
29
|
"""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Hooks.HookPosition.POST_TRIGGER: [],
|
|
26
|
-
}
|
|
30
|
+
internal_hooks = []
|
|
31
|
+
pre_trigger_hooks = []
|
|
32
|
+
post_trigger_hooks = []
|
|
27
33
|
|
|
28
34
|
for hook, data in hooks.items():
|
|
29
35
|
if data and data.position and data.position == Hooks.HookPosition.INTERNAL:
|
|
30
|
-
|
|
36
|
+
internal_hooks.append(hook)
|
|
31
37
|
elif not data or (
|
|
32
38
|
not data.position
|
|
33
39
|
or data.position == constants.Hooks.HookPosition.PRE_TRIGGER
|
|
34
40
|
):
|
|
35
|
-
|
|
41
|
+
pre_trigger_hooks.append(hook)
|
|
36
42
|
elif (
|
|
37
43
|
data
|
|
38
44
|
and data.position
|
|
39
45
|
and data.position == constants.Hooks.HookPosition.POST_TRIGGER
|
|
40
46
|
):
|
|
41
|
-
|
|
47
|
+
post_trigger_hooks.append(hook)
|
|
48
|
+
|
|
49
|
+
return internal_hooks, pre_trigger_hooks, post_trigger_hooks
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class _RenderUtils:
|
|
53
|
+
@staticmethod
|
|
54
|
+
def render(component: Mapping[str, Any] | str) -> str:
|
|
55
|
+
if isinstance(component, str):
|
|
56
|
+
return component or "null"
|
|
57
|
+
if "iterable" in component:
|
|
58
|
+
return _RenderUtils.render_iterable_tag(component)
|
|
59
|
+
if "match_cases" in component:
|
|
60
|
+
return _RenderUtils.render_match_tag(component)
|
|
61
|
+
if "cond_state" in component:
|
|
62
|
+
return _RenderUtils.render_condition_tag(component)
|
|
63
|
+
if (contents := component.get("contents")) is not None:
|
|
64
|
+
return contents or "null"
|
|
65
|
+
return _RenderUtils.render_tag(component)
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def render_tag(component: Mapping[str, Any]) -> str:
|
|
69
|
+
name = component.get("name") or "Fragment"
|
|
70
|
+
props = f"{{{','.join(component['props'])}}}"
|
|
71
|
+
rendered_children = [
|
|
72
|
+
_RenderUtils.render(child)
|
|
73
|
+
for child in component.get("children", [])
|
|
74
|
+
if child
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
return f"jsx({name},{props},{','.join(rendered_children)})"
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def render_condition_tag(component: Any) -> str:
|
|
81
|
+
return f"({component['cond_state']}?({_RenderUtils.render(component['true_value'])}):({_RenderUtils.render(component['false_value'])}))"
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def render_iterable_tag(component: Any) -> str:
|
|
85
|
+
children_rendered = "".join(
|
|
86
|
+
[_RenderUtils.render(child) for child in component.get("children", [])]
|
|
87
|
+
)
|
|
88
|
+
return f"{component['iterable_state']}.map(({component['arg_name']},{component['arg_index']})=>({children_rendered}))"
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def render_match_tag(component: Any) -> str:
|
|
92
|
+
cases_code = ""
|
|
93
|
+
for conditions, return_value in component["match_cases"]:
|
|
94
|
+
for condition in conditions:
|
|
95
|
+
cases_code += f" case JSON.stringify({condition}):\n"
|
|
96
|
+
cases_code += f""" return {_RenderUtils.render(return_value)};
|
|
97
|
+
break;
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
return f"""(() => {{
|
|
101
|
+
switch (JSON.stringify({component["cond"]})) {{
|
|
102
|
+
{cases_code} default:
|
|
103
|
+
return {_RenderUtils.render(component["default"])};
|
|
104
|
+
break;
|
|
105
|
+
}}
|
|
106
|
+
}})()"""
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def get_import(module: _ImportDict) -> str:
|
|
110
|
+
default_import = module["default"]
|
|
111
|
+
rest_imports = module["rest"]
|
|
112
|
+
|
|
113
|
+
if default_import and rest_imports:
|
|
114
|
+
rest_imports_str = ",".join(sorted(rest_imports))
|
|
115
|
+
return f'import {default_import}, {{{rest_imports_str}}} from "{module["lib"]}"'
|
|
116
|
+
if default_import:
|
|
117
|
+
return f'import {default_import} from "{module["lib"]}"'
|
|
118
|
+
if rest_imports:
|
|
119
|
+
rest_imports_str = ",".join(sorted(rest_imports))
|
|
120
|
+
return f'import {{{rest_imports_str}}} from "{module["lib"]}"'
|
|
121
|
+
return f'import "{module["lib"]}"'
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def rxconfig_template(app_name: str):
|
|
125
|
+
"""Template for the Reflex config file.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
app_name: The name of the application.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Rendered Reflex config file content as string.
|
|
132
|
+
"""
|
|
133
|
+
return f"""import reflex as rx
|
|
134
|
+
|
|
135
|
+
config = rx.Config(
|
|
136
|
+
app_name="{app_name}",
|
|
137
|
+
plugins=[
|
|
138
|
+
rx.plugins.SitemapPlugin(),
|
|
139
|
+
rx.plugins.TailwindV4Plugin(),
|
|
140
|
+
]
|
|
141
|
+
)"""
|
|
142
|
+
|
|
42
143
|
|
|
43
|
-
|
|
144
|
+
def document_root_template(*, imports: list[_ImportDict], document: dict[str, Any]):
|
|
145
|
+
"""Template for the document root.
|
|
44
146
|
|
|
147
|
+
Args:
|
|
148
|
+
imports: List of import statements.
|
|
149
|
+
document: Document root component.
|
|
45
150
|
|
|
46
|
-
|
|
47
|
-
|
|
151
|
+
Returns:
|
|
152
|
+
Rendered document root code as string.
|
|
153
|
+
"""
|
|
154
|
+
imports_rendered = "\n".join([_RenderUtils.get_import(mod) for mod in imports])
|
|
155
|
+
return f"""{imports_rendered}
|
|
156
|
+
|
|
157
|
+
export function Layout({{children}}) {{
|
|
158
|
+
return (
|
|
159
|
+
{_RenderUtils.render(document)}
|
|
160
|
+
)
|
|
161
|
+
}}"""
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def app_root_template(
|
|
165
|
+
*,
|
|
166
|
+
imports: list[_ImportDict],
|
|
167
|
+
custom_codes: set[str],
|
|
168
|
+
hooks: dict[str, VarData | None],
|
|
169
|
+
window_libraries: list[tuple[str, str]],
|
|
170
|
+
render: dict[str, Any],
|
|
171
|
+
dynamic_imports: set[str],
|
|
172
|
+
):
|
|
173
|
+
"""Template for the App root.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
imports: The list of import statements.
|
|
177
|
+
custom_codes: The set of custom code snippets.
|
|
178
|
+
hooks: The dictionary of hooks.
|
|
179
|
+
window_libraries: The list of window libraries.
|
|
180
|
+
render: The dictionary of render functions.
|
|
181
|
+
dynamic_imports: The set of dynamic imports.
|
|
48
182
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
183
|
+
Returns:
|
|
184
|
+
Rendered App root component as string.
|
|
185
|
+
"""
|
|
186
|
+
imports_str = "\n".join([_RenderUtils.get_import(mod) for mod in imports])
|
|
187
|
+
dynamic_imports_str = "\n".join(dynamic_imports)
|
|
188
|
+
|
|
189
|
+
custom_code_str = "\n".join(custom_codes)
|
|
190
|
+
|
|
191
|
+
import_window_libraries = "\n".join(
|
|
192
|
+
[
|
|
193
|
+
f'import * as {lib_alias} from "{lib_path}";'
|
|
194
|
+
for lib_alias, lib_path in window_libraries
|
|
195
|
+
]
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
window_imports_str = "\n".join(
|
|
199
|
+
[f' "{lib_path}": {lib_alias},' for lib_alias, lib_path in window_libraries]
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
return f"""
|
|
203
|
+
import reflexGlobalStyles from '$/styles/__reflex_global_styles.css?url';
|
|
204
|
+
{imports_str}
|
|
205
|
+
{dynamic_imports_str}
|
|
206
|
+
import {{ EventLoopProvider, StateProvider, defaultColorMode }} from "$/utils/context";
|
|
207
|
+
import {{ ThemeProvider }} from '$/utils/react-theme';
|
|
208
|
+
import {{ Layout as AppLayout }} from './_document';
|
|
209
|
+
import {{ Outlet }} from 'react-router';
|
|
210
|
+
{import_window_libraries}
|
|
211
|
+
|
|
212
|
+
{custom_code_str}
|
|
213
|
+
|
|
214
|
+
export const links = () => [
|
|
215
|
+
{{ rel: 'stylesheet', href: reflexGlobalStyles, type: 'text/css' }}
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
function AppWrap({{children}}) {{
|
|
219
|
+
{_render_hooks(hooks)}
|
|
220
|
+
return ({_RenderUtils.render(render)})
|
|
221
|
+
}}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
export function Layout({{children}}) {{
|
|
225
|
+
useEffect(() => {{
|
|
226
|
+
// Make contexts and state objects available globally for dynamic eval'd components
|
|
227
|
+
let windowImports = {{
|
|
228
|
+
{window_imports_str}
|
|
229
|
+
}};
|
|
230
|
+
window["__reflex"] = windowImports;
|
|
231
|
+
}}, []);
|
|
232
|
+
|
|
233
|
+
return jsx(AppLayout, {{}},
|
|
234
|
+
jsx(ThemeProvider, {{defaultTheme: defaultColorMode, attribute: "class"}},
|
|
235
|
+
jsx(StateProvider, {{}},
|
|
236
|
+
jsx(EventLoopProvider, {{}},
|
|
237
|
+
jsx(AppWrap, {{}}, children)
|
|
55
238
|
)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"event_endpoint": constants.Endpoint.EVENT.name,
|
|
65
|
-
"events": constants.CompileVars.EVENTS,
|
|
66
|
-
"state": constants.CompileVars.STATE,
|
|
67
|
-
"final": constants.CompileVars.FINAL,
|
|
68
|
-
"processing": constants.CompileVars.PROCESSING,
|
|
69
|
-
"initial_result": {
|
|
70
|
-
constants.CompileVars.STATE: None,
|
|
71
|
-
constants.CompileVars.EVENTS: [],
|
|
72
|
-
constants.CompileVars.FINAL: True,
|
|
73
|
-
constants.CompileVars.PROCESSING: False,
|
|
74
|
-
},
|
|
75
|
-
"color_mode": constants.ColorMode.NAME,
|
|
76
|
-
"resolved_color_mode": constants.ColorMode.RESOLVED_NAME,
|
|
77
|
-
"toggle_color_mode": constants.ColorMode.TOGGLE,
|
|
78
|
-
"set_color_mode": constants.ColorMode.SET,
|
|
79
|
-
"use_color_mode": constants.ColorMode.USE,
|
|
80
|
-
"hydrate": constants.CompileVars.HYDRATE,
|
|
81
|
-
"on_load_internal": constants.CompileVars.ON_LOAD_INTERNAL,
|
|
82
|
-
"update_vars_internal": constants.CompileVars.UPDATE_VARS_INTERNAL,
|
|
83
|
-
"frontend_exception_state": constants.CompileVars.FRONTEND_EXCEPTION_STATE_FULL,
|
|
84
|
-
"hook_position": constants.Hooks.HookPosition,
|
|
85
|
-
}
|
|
86
|
-
self.globals["sort_hooks"] = _sort_hooks
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
);
|
|
242
|
+
}}
|
|
243
|
+
|
|
244
|
+
export default function App() {{
|
|
245
|
+
return jsx(Outlet, {{}});
|
|
246
|
+
}}
|
|
87
247
|
|
|
248
|
+
"""
|
|
88
249
|
|
|
89
|
-
|
|
90
|
-
|
|
250
|
+
|
|
251
|
+
def theme_template(theme: str):
|
|
252
|
+
"""Template for the theme file.
|
|
91
253
|
|
|
92
254
|
Args:
|
|
93
|
-
|
|
255
|
+
theme: The theme to render.
|
|
94
256
|
|
|
95
257
|
Returns:
|
|
96
|
-
|
|
258
|
+
Rendered theme file content as string.
|
|
97
259
|
"""
|
|
98
|
-
return
|
|
260
|
+
return f"""export default {theme}"""
|
|
99
261
|
|
|
100
262
|
|
|
101
|
-
def
|
|
102
|
-
|
|
263
|
+
def context_template(
|
|
264
|
+
*,
|
|
265
|
+
is_dev_mode: bool,
|
|
266
|
+
default_color_mode: str,
|
|
267
|
+
initial_state: dict[str, Any] | None = None,
|
|
268
|
+
state_name: str | None = None,
|
|
269
|
+
client_storage: dict[str, dict[str, dict[str, Any]]] | None = None,
|
|
270
|
+
):
|
|
271
|
+
"""Template for the context file.
|
|
103
272
|
|
|
104
273
|
Args:
|
|
105
|
-
|
|
274
|
+
initial_state: The initial state for the context.
|
|
275
|
+
state_name: The name of the state.
|
|
276
|
+
client_storage: The client storage for the context.
|
|
277
|
+
is_dev_mode: Whether the app is in development mode.
|
|
278
|
+
default_color_mode: The default color mode for the context.
|
|
106
279
|
|
|
107
280
|
Returns:
|
|
108
|
-
|
|
281
|
+
Rendered context file content as string.
|
|
282
|
+
"""
|
|
283
|
+
initial_state = initial_state or {}
|
|
284
|
+
state_contexts_str = "".join(
|
|
285
|
+
[
|
|
286
|
+
f"{format_state_name(state_name)}: createContext(null),"
|
|
287
|
+
for state_name in initial_state
|
|
288
|
+
]
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
state_str = (
|
|
292
|
+
rf"""
|
|
293
|
+
export const state_name = "{state_name}"
|
|
294
|
+
|
|
295
|
+
export const exception_state_name = "{constants.CompileVars.FRONTEND_EXCEPTION_STATE_FULL}"
|
|
296
|
+
|
|
297
|
+
// These events are triggered on initial load and each page navigation.
|
|
298
|
+
export const onLoadInternalEvent = () => {{
|
|
299
|
+
const internal_events = [];
|
|
300
|
+
|
|
301
|
+
// Get tracked cookie and local storage vars to send to the backend.
|
|
302
|
+
const client_storage_vars = hydrateClientStorage(clientStorage);
|
|
303
|
+
// But only send the vars if any are actually set in the browser.
|
|
304
|
+
if (client_storage_vars && Object.keys(client_storage_vars).length !== 0) {{
|
|
305
|
+
internal_events.push(
|
|
306
|
+
Event(
|
|
307
|
+
'{state_name}.{constants.CompileVars.UPDATE_VARS_INTERNAL}',
|
|
308
|
+
{{vars: client_storage_vars}},
|
|
309
|
+
),
|
|
310
|
+
);
|
|
311
|
+
}}
|
|
312
|
+
|
|
313
|
+
// `on_load_internal` triggers the correct on_load event(s) for the current page.
|
|
314
|
+
// If the page does not define any on_load event, this will just set `is_hydrated = true`.
|
|
315
|
+
internal_events.push(Event('{state_name}.{constants.CompileVars.ON_LOAD_INTERNAL}'));
|
|
316
|
+
|
|
317
|
+
return internal_events;
|
|
318
|
+
}}
|
|
319
|
+
|
|
320
|
+
// The following events are sent when the websocket connects or reconnects.
|
|
321
|
+
export const initialEvents = () => [
|
|
322
|
+
Event('{state_name}.{constants.CompileVars.HYDRATE}'),
|
|
323
|
+
...onLoadInternalEvent()
|
|
324
|
+
]
|
|
109
325
|
"""
|
|
110
|
-
|
|
326
|
+
if state_name
|
|
327
|
+
else """
|
|
328
|
+
export const state_name = undefined
|
|
329
|
+
|
|
330
|
+
export const exception_state_name = undefined
|
|
331
|
+
|
|
332
|
+
export const onLoadInternalEvent = () => []
|
|
333
|
+
|
|
334
|
+
export const initialEvents = () => []
|
|
335
|
+
"""
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
state_reducer_str = "\n".join(
|
|
339
|
+
rf'const [{format_state_name(state_name)}, dispatch_{format_state_name(state_name)}] = useReducer(applyDelta, initialState["{state_name}"])'
|
|
340
|
+
for state_name in initial_state
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
create_state_contexts_str = "\n".join(
|
|
344
|
+
rf"createElement(StateContexts.{format_state_name(state_name)},{{value: {format_state_name(state_name)}}},"
|
|
345
|
+
for state_name in initial_state
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
dispatchers_str = "\n".join(
|
|
349
|
+
f'"{state_name}": dispatch_{format_state_name(state_name)},'
|
|
350
|
+
for state_name in initial_state
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
return rf"""import {{ createContext, useContext, useMemo, useReducer, useState, createElement, useEffect }} from "react"
|
|
354
|
+
import {{ applyDelta, Event, hydrateClientStorage, useEventLoop, refs }} from "$/utils/state"
|
|
355
|
+
import {{ jsx }} from "@emotion/react";
|
|
356
|
+
|
|
357
|
+
export const initialState = {"{}" if not initial_state else json_dumps(initial_state)}
|
|
358
|
+
|
|
359
|
+
export const defaultColorMode = {default_color_mode}
|
|
360
|
+
export const ColorModeContext = createContext(null);
|
|
361
|
+
export const UploadFilesContext = createContext(null);
|
|
362
|
+
export const DispatchContext = createContext(null);
|
|
363
|
+
export const StateContexts = {{{state_contexts_str}}};
|
|
364
|
+
export const EventLoopContext = createContext(null);
|
|
365
|
+
export const clientStorage = {"{}" if client_storage is None else json_dumps(client_storage)}
|
|
366
|
+
|
|
367
|
+
{state_str}
|
|
368
|
+
|
|
369
|
+
export const isDevMode = {json.dumps(is_dev_mode)};
|
|
370
|
+
|
|
371
|
+
export function UploadFilesProvider({{ children }}) {{
|
|
372
|
+
const [filesById, setFilesById] = useState({{}})
|
|
373
|
+
refs["__clear_selected_files"] = (id) => setFilesById(filesById => {{
|
|
374
|
+
const newFilesById = {{...filesById}}
|
|
375
|
+
delete newFilesById[id]
|
|
376
|
+
return newFilesById
|
|
377
|
+
}})
|
|
378
|
+
return createElement(
|
|
379
|
+
UploadFilesContext.Provider,
|
|
380
|
+
{{ value: [filesById, setFilesById] }},
|
|
381
|
+
children
|
|
382
|
+
);
|
|
383
|
+
}}
|
|
384
|
+
|
|
385
|
+
export function ClientSide(component) {{
|
|
386
|
+
return ({{ children, ...props }}) => {{
|
|
387
|
+
const [Component, setComponent] = useState(null);
|
|
388
|
+
useEffect(() => {{
|
|
389
|
+
setComponent(component);
|
|
390
|
+
}}, []);
|
|
391
|
+
return Component ? jsx(Component, props, children) : null;
|
|
392
|
+
}};
|
|
393
|
+
}}
|
|
394
|
+
|
|
395
|
+
export function EventLoopProvider({{ children }}) {{
|
|
396
|
+
const dispatch = useContext(DispatchContext)
|
|
397
|
+
const [addEvents, connectErrors] = useEventLoop(
|
|
398
|
+
dispatch,
|
|
399
|
+
initialEvents,
|
|
400
|
+
clientStorage,
|
|
401
|
+
)
|
|
402
|
+
return createElement(
|
|
403
|
+
EventLoopContext.Provider,
|
|
404
|
+
{{ value: [addEvents, connectErrors] }},
|
|
405
|
+
children
|
|
406
|
+
);
|
|
407
|
+
}}
|
|
408
|
+
|
|
409
|
+
export function StateProvider({{ children }}) {{
|
|
410
|
+
{state_reducer_str}
|
|
411
|
+
const dispatchers = useMemo(() => {{
|
|
412
|
+
return {{
|
|
413
|
+
{dispatchers_str}
|
|
414
|
+
}}
|
|
415
|
+
}}, [])
|
|
416
|
+
|
|
417
|
+
return (
|
|
418
|
+
{create_state_contexts_str}
|
|
419
|
+
createElement(DispatchContext, {{value: dispatchers}}, children)
|
|
420
|
+
{")" * len(initial_state)}
|
|
421
|
+
)
|
|
422
|
+
}}"""
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def component_template(component: Component | StatefulComponent):
|
|
426
|
+
"""Template to render a component tag.
|
|
111
427
|
|
|
428
|
+
Args:
|
|
429
|
+
component: The component to render.
|
|
112
430
|
|
|
113
|
-
|
|
114
|
-
|
|
431
|
+
Returns:
|
|
432
|
+
Rendered component as string.
|
|
433
|
+
"""
|
|
434
|
+
return _RenderUtils.render(component.render())
|
|
115
435
|
|
|
116
|
-
# Code to render the Document root.
|
|
117
|
-
DOCUMENT_ROOT = get_template("web/pages/_document.js.jinja2")
|
|
118
436
|
|
|
119
|
-
|
|
120
|
-
|
|
437
|
+
def page_template(
|
|
438
|
+
imports: Iterable[_ImportDict],
|
|
439
|
+
dynamic_imports: Iterable[str],
|
|
440
|
+
custom_codes: Iterable[str],
|
|
441
|
+
hooks: dict[str, VarData | None],
|
|
442
|
+
render: dict[str, Any],
|
|
443
|
+
):
|
|
444
|
+
"""Template for a single react page.
|
|
121
445
|
|
|
122
|
-
|
|
123
|
-
|
|
446
|
+
Args:
|
|
447
|
+
imports: List of import statements.
|
|
448
|
+
dynamic_imports: List of dynamic import statements.
|
|
449
|
+
custom_codes: List of custom code snippets.
|
|
450
|
+
hooks: Dictionary of hooks.
|
|
451
|
+
render: Render function for the component.
|
|
124
452
|
|
|
125
|
-
|
|
126
|
-
|
|
453
|
+
Returns:
|
|
454
|
+
Rendered React page component as string.
|
|
455
|
+
"""
|
|
456
|
+
imports_str = "\n".join([_RenderUtils.get_import(imp) for imp in imports])
|
|
457
|
+
custom_code_str = "\n".join(custom_codes)
|
|
458
|
+
dynamic_imports_str = "\n".join(dynamic_imports)
|
|
127
459
|
|
|
128
|
-
|
|
129
|
-
|
|
460
|
+
hooks_str = _render_hooks(hooks)
|
|
461
|
+
return f"""{imports_str}
|
|
130
462
|
|
|
131
|
-
|
|
132
|
-
PAGE = get_template("web/pages/index.js.jinja2")
|
|
463
|
+
{dynamic_imports_str}
|
|
133
464
|
|
|
134
|
-
|
|
135
|
-
COMPONENTS = get_template("web/pages/custom_component.js.jinja2")
|
|
465
|
+
{custom_code_str}
|
|
136
466
|
|
|
137
|
-
|
|
138
|
-
|
|
467
|
+
export default function Component() {{
|
|
468
|
+
{hooks_str}
|
|
139
469
|
|
|
140
|
-
|
|
141
|
-
|
|
470
|
+
return (
|
|
471
|
+
{_RenderUtils.render(render)}
|
|
472
|
+
)
|
|
473
|
+
}}"""
|
|
142
474
|
|
|
143
|
-
# Sitemap config file.
|
|
144
|
-
SITEMAP_CONFIG = "module.exports = {config}".format
|
|
145
475
|
|
|
146
|
-
|
|
147
|
-
|
|
476
|
+
def package_json_template(
|
|
477
|
+
scripts: dict[str, str],
|
|
478
|
+
dependencies: dict[str, str],
|
|
479
|
+
dev_dependencies: dict[str, str],
|
|
480
|
+
overrides: dict[str, str],
|
|
481
|
+
):
|
|
482
|
+
"""Template for package.json.
|
|
148
483
|
|
|
149
|
-
|
|
150
|
-
|
|
484
|
+
Args:
|
|
485
|
+
scripts: The scripts to include in the package.json file.
|
|
486
|
+
dependencies: The dependencies to include in the package.json file.
|
|
487
|
+
dev_dependencies: The devDependencies to include in the package.json file.
|
|
488
|
+
overrides: The overrides to include in the package.json file.
|
|
151
489
|
|
|
152
|
-
|
|
153
|
-
|
|
490
|
+
Returns:
|
|
491
|
+
Rendered package.json content as string.
|
|
492
|
+
"""
|
|
493
|
+
return json.dumps(
|
|
494
|
+
{
|
|
495
|
+
"name": "reflex",
|
|
496
|
+
"type": "module",
|
|
497
|
+
"scripts": scripts,
|
|
498
|
+
"dependencies": dependencies,
|
|
499
|
+
"devDependencies": dev_dependencies,
|
|
500
|
+
"overrides": overrides,
|
|
501
|
+
}
|
|
502
|
+
)
|
|
154
503
|
|
|
155
|
-
# Template containing some macros used in the web pages.
|
|
156
|
-
MACROS = get_template("web/pages/macros.js.jinja2")
|
|
157
504
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
"custom_components/pyproject.toml.jinja2"
|
|
161
|
-
)
|
|
505
|
+
def vite_config_template(base: str):
|
|
506
|
+
"""Template for vite.config.js.
|
|
162
507
|
|
|
163
|
-
|
|
164
|
-
|
|
508
|
+
Args:
|
|
509
|
+
base: The base path for the Vite config.
|
|
165
510
|
|
|
166
|
-
|
|
167
|
-
|
|
511
|
+
Returns:
|
|
512
|
+
Rendered vite.config.js content as string.
|
|
513
|
+
"""
|
|
514
|
+
return rf"""import {{ fileURLToPath, URL }} from "url";
|
|
515
|
+
import {{ reactRouter }} from "@react-router/dev/vite";
|
|
516
|
+
import {{ defineConfig }} from "vite";
|
|
517
|
+
import safariCacheBustPlugin from "./vite-plugin-safari-cachebust";
|
|
518
|
+
|
|
519
|
+
// Ensure that bun always uses the react-dom/server.node functions.
|
|
520
|
+
function alwaysUseReactDomServerNode() {{
|
|
521
|
+
return {{
|
|
522
|
+
name: "vite-plugin-always-use-react-dom-server-node",
|
|
523
|
+
enforce: "pre",
|
|
524
|
+
|
|
525
|
+
resolveId(source, importer) {{
|
|
526
|
+
if (
|
|
527
|
+
typeof importer === "string" &&
|
|
528
|
+
importer.endsWith("/entry.server.node.tsx") &&
|
|
529
|
+
source.includes("react-dom/server")
|
|
530
|
+
) {{
|
|
531
|
+
return this.resolve("react-dom/server.node", importer, {{
|
|
532
|
+
skipSelf: true,
|
|
533
|
+
}});
|
|
534
|
+
}}
|
|
535
|
+
return null;
|
|
536
|
+
}},
|
|
537
|
+
}};
|
|
538
|
+
}}
|
|
539
|
+
|
|
540
|
+
export default defineConfig((config) => ({{
|
|
541
|
+
plugins: [
|
|
542
|
+
alwaysUseReactDomServerNode(),
|
|
543
|
+
reactRouter(),
|
|
544
|
+
safariCacheBustPlugin(),
|
|
545
|
+
],
|
|
546
|
+
build: {{
|
|
547
|
+
assetsDir: "{base}assets".slice(1),
|
|
548
|
+
rollupOptions: {{
|
|
549
|
+
jsx: {{}},
|
|
550
|
+
output: {{
|
|
551
|
+
advancedChunks: {{
|
|
552
|
+
groups: [
|
|
553
|
+
{{
|
|
554
|
+
test: /env.json/,
|
|
555
|
+
name: "reflex-env",
|
|
556
|
+
}},
|
|
557
|
+
],
|
|
558
|
+
}},
|
|
559
|
+
}},
|
|
560
|
+
}},
|
|
561
|
+
}},
|
|
562
|
+
experimental: {{
|
|
563
|
+
enableNativePlugin: false,
|
|
564
|
+
}},
|
|
565
|
+
server: {{
|
|
566
|
+
port: process.env.PORT,
|
|
567
|
+
watch: {{
|
|
568
|
+
ignored: [
|
|
569
|
+
"**/.web/backend/**",
|
|
570
|
+
"**/.web/reflex.install_frontend_packages.cached",
|
|
571
|
+
],
|
|
572
|
+
}},
|
|
573
|
+
}},
|
|
574
|
+
resolve: {{
|
|
575
|
+
mainFields: ["browser", "module", "jsnext"],
|
|
576
|
+
alias: [
|
|
577
|
+
{{
|
|
578
|
+
find: "$",
|
|
579
|
+
replacement: fileURLToPath(new URL("./", import.meta.url)),
|
|
580
|
+
}},
|
|
581
|
+
{{
|
|
582
|
+
find: "@",
|
|
583
|
+
replacement: fileURLToPath(new URL("./public", import.meta.url)),
|
|
584
|
+
}},
|
|
585
|
+
],
|
|
586
|
+
}},
|
|
587
|
+
}}));"""
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def stateful_component_template(
|
|
591
|
+
tag_name: str, memo_trigger_hooks: list[str], component: Component, export: bool
|
|
592
|
+
):
|
|
593
|
+
"""Template for stateful component.
|
|
168
594
|
|
|
169
|
-
|
|
170
|
-
|
|
595
|
+
Args:
|
|
596
|
+
tag_name: The tag name for the component.
|
|
597
|
+
memo_trigger_hooks: The memo trigger hooks for the component.
|
|
598
|
+
component: The component to render.
|
|
599
|
+
export: Whether to export the component.
|
|
171
600
|
|
|
172
|
-
|
|
173
|
-
|
|
601
|
+
Returns:
|
|
602
|
+
Rendered stateful component code as string.
|
|
603
|
+
"""
|
|
604
|
+
all_hooks = component._get_all_hooks()
|
|
605
|
+
return f"""
|
|
606
|
+
{"export " if export else ""}function {tag_name} () {{
|
|
607
|
+
{_render_hooks(all_hooks, memo_trigger_hooks)}
|
|
608
|
+
return (
|
|
609
|
+
{_RenderUtils.render(component.render())}
|
|
610
|
+
)
|
|
611
|
+
}}
|
|
612
|
+
"""
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def stateful_components_template(imports: list[_ImportDict], memoized_code: str) -> str:
|
|
616
|
+
"""Template for stateful components.
|
|
617
|
+
|
|
618
|
+
Args:
|
|
619
|
+
imports: List of import statements.
|
|
620
|
+
memoized_code: Memoized code for stateful components.
|
|
621
|
+
|
|
622
|
+
Returns:
|
|
623
|
+
Rendered stateful components code as string.
|
|
624
|
+
"""
|
|
625
|
+
imports_str = "\n".join([_RenderUtils.get_import(imp) for imp in imports])
|
|
626
|
+
return f"{imports_str}\n{memoized_code}"
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def custom_component_template(
|
|
630
|
+
imports: list[_ImportDict],
|
|
631
|
+
components: list[dict[str, Any]],
|
|
632
|
+
dynamic_imports: Iterable[str],
|
|
633
|
+
custom_codes: Iterable[str],
|
|
634
|
+
) -> str:
|
|
635
|
+
"""Template for custom component.
|
|
636
|
+
|
|
637
|
+
Args:
|
|
638
|
+
imports: List of import statements.
|
|
639
|
+
components: List of component definitions.
|
|
640
|
+
dynamic_imports: List of dynamic import statements.
|
|
641
|
+
custom_codes: List of custom code snippets.
|
|
642
|
+
|
|
643
|
+
Returns:
|
|
644
|
+
Rendered custom component code as string.
|
|
645
|
+
"""
|
|
646
|
+
imports_str = "\n".join([_RenderUtils.get_import(imp) for imp in imports])
|
|
647
|
+
dynamic_imports_str = "\n".join(dynamic_imports)
|
|
648
|
+
custom_code_str = "\n".join(custom_codes)
|
|
649
|
+
|
|
650
|
+
components_code = ""
|
|
651
|
+
for component in components:
|
|
652
|
+
components_code += f"""
|
|
653
|
+
export const {component["name"]} = memo(({{ {", ".join(component.get("props", []))} }}) => {{
|
|
654
|
+
{_render_hooks(component.get("hooks", {}))}
|
|
655
|
+
return(
|
|
656
|
+
{_RenderUtils.render(component["render"])}
|
|
657
|
+
)
|
|
658
|
+
}});
|
|
659
|
+
"""
|
|
660
|
+
|
|
661
|
+
return f"""
|
|
662
|
+
{imports_str}
|
|
663
|
+
|
|
664
|
+
{dynamic_imports_str}
|
|
665
|
+
|
|
666
|
+
{custom_code_str}
|
|
667
|
+
|
|
668
|
+
{components_code}"""
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def styles_template(stylesheets: list[str]) -> str:
|
|
672
|
+
"""Template for styles.css.
|
|
673
|
+
|
|
674
|
+
Args:
|
|
675
|
+
stylesheets: List of stylesheets to include.
|
|
676
|
+
|
|
677
|
+
Returns:
|
|
678
|
+
Rendered styles.css content as string.
|
|
679
|
+
"""
|
|
680
|
+
return "@layer __reflex_base;\n" + "\n".join(
|
|
681
|
+
[f"@import url('{sheet_name}');" for sheet_name in stylesheets]
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
def _render_hooks(hooks: dict[str, VarData | None], memo: list | None = None) -> str:
|
|
686
|
+
"""Render hooks for macros.
|
|
687
|
+
|
|
688
|
+
Args:
|
|
689
|
+
hooks: Dictionary of hooks to render.
|
|
690
|
+
memo: Optional list of memo hooks.
|
|
691
|
+
|
|
692
|
+
Returns:
|
|
693
|
+
Rendered hooks code as string.
|
|
694
|
+
"""
|
|
695
|
+
internal, pre_trigger, post_trigger = _sort_hooks(hooks)
|
|
696
|
+
internal_str = "\n".join(internal)
|
|
697
|
+
pre_trigger_str = "\n".join(pre_trigger)
|
|
698
|
+
post_trigger_str = "\n".join(post_trigger)
|
|
699
|
+
memo_str = "\n".join(memo) if memo is not None else ""
|
|
700
|
+
return f"{internal_str}\n{pre_trigger_str}\n{memo_str}\n{post_trigger_str}"
|