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.

Files changed (67) hide show
  1. reflex/app.py +13 -5
  2. reflex/app_mixins/lifespan.py +8 -2
  3. reflex/compiler/compiler.py +12 -12
  4. reflex/compiler/templates.py +629 -102
  5. reflex/compiler/utils.py +29 -20
  6. reflex/components/base/bare.py +17 -0
  7. reflex/components/component.py +37 -33
  8. reflex/components/core/cond.py +6 -12
  9. reflex/components/core/foreach.py +1 -1
  10. reflex/components/core/match.py +83 -60
  11. reflex/components/dynamic.py +3 -3
  12. reflex/components/el/elements/forms.py +31 -14
  13. reflex/components/el/elements/forms.pyi +0 -5
  14. reflex/components/lucide/icon.py +2 -1
  15. reflex/components/lucide/icon.pyi +2 -1
  16. reflex/components/markdown/markdown.py +2 -2
  17. reflex/components/radix/primitives/accordion.py +1 -1
  18. reflex/components/radix/primitives/drawer.py +1 -1
  19. reflex/components/radix/primitives/form.py +1 -1
  20. reflex/components/radix/primitives/slider.py +1 -1
  21. reflex/components/tags/cond_tag.py +14 -5
  22. reflex/components/tags/iter_tag.py +0 -26
  23. reflex/components/tags/match_tag.py +15 -6
  24. reflex/components/tags/tag.py +3 -6
  25. reflex/components/tags/tagless.py +14 -0
  26. reflex/constants/base.py +0 -2
  27. reflex/constants/installer.py +4 -4
  28. reflex/custom_components/custom_components.py +202 -15
  29. reflex/experimental/client_state.py +1 -1
  30. reflex/istate/manager.py +2 -1
  31. reflex/plugins/shared_tailwind.py +87 -62
  32. reflex/plugins/tailwind_v3.py +2 -2
  33. reflex/plugins/tailwind_v4.py +4 -4
  34. reflex/state.py +5 -1
  35. reflex/utils/format.py +2 -3
  36. reflex/utils/frontend_skeleton.py +2 -2
  37. reflex/utils/imports.py +18 -0
  38. reflex/utils/pyi_generator.py +10 -2
  39. reflex/utils/telemetry.py +4 -1
  40. reflex/utils/templates.py +1 -6
  41. {reflex-0.8.7a1.dist-info → reflex-0.8.8a1.dist-info}/METADATA +3 -4
  42. {reflex-0.8.7a1.dist-info → reflex-0.8.8a1.dist-info}/RECORD +45 -67
  43. reflex/.templates/jinja/app/rxconfig.py.jinja2 +0 -9
  44. reflex/.templates/jinja/custom_components/README.md.jinja2 +0 -9
  45. reflex/.templates/jinja/custom_components/__init__.py.jinja2 +0 -1
  46. reflex/.templates/jinja/custom_components/demo_app.py.jinja2 +0 -39
  47. reflex/.templates/jinja/custom_components/pyproject.toml.jinja2 +0 -25
  48. reflex/.templates/jinja/custom_components/src.py.jinja2 +0 -57
  49. reflex/.templates/jinja/web/package.json.jinja2 +0 -27
  50. reflex/.templates/jinja/web/pages/_app.js.jinja2 +0 -62
  51. reflex/.templates/jinja/web/pages/_document.js.jinja2 +0 -9
  52. reflex/.templates/jinja/web/pages/base_page.js.jinja2 +0 -21
  53. reflex/.templates/jinja/web/pages/component.js.jinja2 +0 -2
  54. reflex/.templates/jinja/web/pages/custom_component.js.jinja2 +0 -22
  55. reflex/.templates/jinja/web/pages/index.js.jinja2 +0 -18
  56. reflex/.templates/jinja/web/pages/macros.js.jinja2 +0 -38
  57. reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 +0 -15
  58. reflex/.templates/jinja/web/pages/stateful_components.js.jinja2 +0 -5
  59. reflex/.templates/jinja/web/pages/utils.js.jinja2 +0 -93
  60. reflex/.templates/jinja/web/styles/styles.css.jinja2 +0 -6
  61. reflex/.templates/jinja/web/utils/context.js.jinja2 +0 -129
  62. reflex/.templates/jinja/web/utils/theme.js.jinja2 +0 -1
  63. reflex/.templates/jinja/web/vite.config.js.jinja2 +0 -74
  64. reflex/components/core/client_side_routing.pyi +0 -68
  65. {reflex-0.8.7a1.dist-info → reflex-0.8.8a1.dist-info}/WHEEL +0 -0
  66. {reflex-0.8.7a1.dist-info → reflex-0.8.8a1.dist-info}/entry_points.txt +0 -0
  67. {reflex-0.8.7a1.dist-info → reflex-0.8.8a1.dist-info}/licenses/LICENSE +0 -0
@@ -2,15 +2,23 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from jinja2 import Environment, FileSystemLoader, Template
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
- def _sort_hooks(hooks: dict[str, VarData | None]):
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
- sorted_hooks = {
23
- Hooks.HookPosition.INTERNAL: [],
24
- Hooks.HookPosition.PRE_TRIGGER: [],
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
- sorted_hooks[Hooks.HookPosition.INTERNAL].append((hook, data))
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
- sorted_hooks[Hooks.HookPosition.PRE_TRIGGER].append((hook, data))
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
- sorted_hooks[Hooks.HookPosition.POST_TRIGGER].append((hook, data))
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
- return sorted_hooks
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
- class ReflexJinjaEnvironment(Environment):
47
- """The template class for jinja environment."""
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
- def __init__(self) -> None:
50
- """Set default environment."""
51
- super().__init__(
52
- trim_blocks=True,
53
- lstrip_blocks=True,
54
- auto_reload=False,
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
- self.filters["json_dumps"] = json_dumps
57
- self.filters["react_setter"] = lambda state: f"set{state.capitalize()}"
58
- self.filters["var_name"] = format_state_name
59
- self.loader = FileSystemLoader(constants.Templates.Dirs.JINJA_TEMPLATE)
60
- self.globals["const"] = {
61
- "socket": constants.CompileVars.SOCKET,
62
- "result": constants.CompileVars.RESULT,
63
- "router": constants.CompileVars.ROUTER,
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
- def get_template(name: str) -> Template:
90
- """Get render function that work with a template.
250
+
251
+ def theme_template(theme: str):
252
+ """Template for the theme file.
91
253
 
92
254
  Args:
93
- name: The template name. "/" is used as the path separator.
255
+ theme: The theme to render.
94
256
 
95
257
  Returns:
96
- A render function.
258
+ Rendered theme file content as string.
97
259
  """
98
- return ReflexJinjaEnvironment().get_template(name=name)
260
+ return f"""export default {theme}"""
99
261
 
100
262
 
101
- def from_string(source: str) -> Template:
102
- """Get render function that work with a template.
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
- source: The template source.
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
- A render function.
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
- return ReflexJinjaEnvironment().from_string(source=source)
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
- # Template for the Reflex config file.
114
- RXCONFIG = get_template("app/rxconfig.py.jinja2")
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
- # Code to render App root.
120
- APP_ROOT = get_template("web/pages/_app.js.jinja2")
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
- # Template for the theme file.
123
- THEME = get_template("web/utils/theme.js.jinja2")
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
- # Template for the context file.
126
- CONTEXT = get_template("web/utils/context.js.jinja2")
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
- # Template to render a component tag.
129
- COMPONENT = get_template("web/pages/component.js.jinja2")
460
+ hooks_str = _render_hooks(hooks)
461
+ return f"""{imports_str}
130
462
 
131
- # Code to render a single react page.
132
- PAGE = get_template("web/pages/index.js.jinja2")
463
+ {dynamic_imports_str}
133
464
 
134
- # Code to render the custom components page.
135
- COMPONENTS = get_template("web/pages/custom_component.js.jinja2")
465
+ {custom_code_str}
136
466
 
137
- # Code to render Component instances as part of StatefulComponent
138
- STATEFUL_COMPONENT = get_template("web/pages/stateful_component.js.jinja2")
467
+ export default function Component() {{
468
+ {hooks_str}
139
469
 
140
- # Code to render StatefulComponent to an external file to be shared
141
- STATEFUL_COMPONENTS = get_template("web/pages/stateful_components.js.jinja2")
470
+ return (
471
+ {_RenderUtils.render(render)}
472
+ )
473
+ }}"""
142
474
 
143
- # Sitemap config file.
144
- SITEMAP_CONFIG = "module.exports = {config}".format
145
475
 
146
- # Code to render the root stylesheet.
147
- STYLE = get_template("web/styles/styles.css.jinja2")
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
- # Code that generate the package json file
150
- PACKAGE_JSON = get_template("web/package.json.jinja2")
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
- # Code that generate the vite.config.js file
153
- VITE_CONFIG = get_template("web/vite.config.js.jinja2")
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
- # Code that generate the pyproject.toml file for custom components.
159
- CUSTOM_COMPONENTS_PYPROJECT_TOML = get_template(
160
- "custom_components/pyproject.toml.jinja2"
161
- )
505
+ def vite_config_template(base: str):
506
+ """Template for vite.config.js.
162
507
 
163
- # Code that generates the README file for custom components.
164
- CUSTOM_COMPONENTS_README = get_template("custom_components/README.md.jinja2")
508
+ Args:
509
+ base: The base path for the Vite config.
165
510
 
166
- # Code that generates the source file for custom components.
167
- CUSTOM_COMPONENTS_SOURCE = get_template("custom_components/src.py.jinja2")
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
- # Code that generates the init file for custom components.
170
- CUSTOM_COMPONENTS_INIT_FILE = get_template("custom_components/__init__.py.jinja2")
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
- # Code that generates the demo app main py file for testing custom components.
173
- CUSTOM_COMPONENTS_DEMO_APP = get_template("custom_components/demo_app.py.jinja2")
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}"