reflex 0.8.6a0__py3-none-any.whl → 0.8.7a1__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 (41) hide show
  1. reflex/.templates/jinja/web/vite.config.js.jinja2 +4 -1
  2. reflex/.templates/web/app/routes.js +0 -1
  3. reflex/.templates/web/utils/state.js +1 -11
  4. reflex/app.py +27 -23
  5. reflex/components/base/error_boundary.py +2 -0
  6. reflex/components/component.py +6 -4
  7. reflex/components/lucide/icon.py +4 -1
  8. reflex/components/lucide/icon.pyi +4 -1
  9. reflex/components/plotly/plotly.py +9 -9
  10. reflex/components/recharts/recharts.py +2 -2
  11. reflex/components/sonner/toast.py +7 -7
  12. reflex/components/sonner/toast.pyi +8 -8
  13. reflex/config.py +9 -2
  14. reflex/constants/base.py +2 -0
  15. reflex/constants/installer.py +6 -6
  16. reflex/constants/state.py +1 -0
  17. reflex/custom_components/custom_components.py +3 -3
  18. reflex/istate/manager.py +2 -1
  19. reflex/plugins/__init__.py +2 -0
  20. reflex/plugins/_screenshot.py +144 -0
  21. reflex/plugins/base.py +14 -1
  22. reflex/reflex.py +7 -6
  23. reflex/route.py +4 -0
  24. reflex/state.py +2 -2
  25. reflex/testing.py +3 -5
  26. reflex/utils/build.py +21 -3
  27. reflex/utils/exec.py +11 -11
  28. reflex/utils/frontend_skeleton.py +254 -0
  29. reflex/utils/js_runtimes.py +411 -0
  30. reflex/utils/prerequisites.py +17 -1383
  31. reflex/utils/rename.py +170 -0
  32. reflex/utils/telemetry.py +101 -10
  33. reflex/utils/templates.py +443 -0
  34. reflex/vars/base.py +3 -3
  35. {reflex-0.8.6a0.dist-info → reflex-0.8.7a1.dist-info}/METADATA +2 -2
  36. {reflex-0.8.6a0.dist-info → reflex-0.8.7a1.dist-info}/RECORD +39 -36
  37. reflex/.templates/web/utils/client_side_routing.js +0 -45
  38. reflex/components/core/client_side_routing.py +0 -70
  39. {reflex-0.8.6a0.dist-info → reflex-0.8.7a1.dist-info}/WHEEL +0 -0
  40. {reflex-0.8.6a0.dist-info → reflex-0.8.7a1.dist-info}/entry_points.txt +0 -0
  41. {reflex-0.8.6a0.dist-info → reflex-0.8.7a1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,144 @@
1
+ """Plugin to enable screenshot functionality."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from reflex.plugins.base import Plugin as BasePlugin
6
+
7
+ if TYPE_CHECKING:
8
+ from starlette.requests import Request
9
+ from starlette.responses import Response
10
+ from typing_extensions import Unpack
11
+
12
+ from reflex.app import App
13
+ from reflex.plugins.base import PostCompileContext
14
+ from reflex.state import BaseState
15
+
16
+ ACTIVE_CONNECTIONS = "/_active_connections"
17
+ CLONE_STATE = "/_clone_state"
18
+
19
+
20
+ def _deep_copy(state: "BaseState") -> "BaseState":
21
+ """Create a deep copy of the state.
22
+
23
+ Args:
24
+ state: The state to copy.
25
+
26
+ Returns:
27
+ A deep copy of the state.
28
+ """
29
+ import copy
30
+
31
+ copy_of_state = copy.deepcopy(state)
32
+
33
+ def copy_substate(substate: "BaseState") -> "BaseState":
34
+ substate_copy = _deep_copy(substate)
35
+
36
+ substate_copy.parent_state = copy_of_state
37
+
38
+ return substate_copy
39
+
40
+ copy_of_state.substates = {
41
+ substate_name: copy_substate(substate)
42
+ for substate_name, substate in state.substates.items()
43
+ }
44
+
45
+ return copy_of_state
46
+
47
+
48
+ class ScreenshotPlugin(BasePlugin):
49
+ """Plugin to handle screenshot functionality."""
50
+
51
+ def post_compile(self, **context: "Unpack[PostCompileContext]") -> None:
52
+ """Called after the compilation of the plugin.
53
+
54
+ Args:
55
+ context: The context for the plugin.
56
+ """
57
+ app = context["app"]
58
+ self._add_active_connections_endpoint(app)
59
+ self._add_clone_state_endpoint(app)
60
+
61
+ @staticmethod
62
+ def _add_active_connections_endpoint(app: "App") -> None:
63
+ """Add an endpoint to the app that returns the active connections.
64
+
65
+ Args:
66
+ app: The application instance to which the endpoint will be added.
67
+ """
68
+ if not app._api:
69
+ return
70
+
71
+ async def active_connections(_request: "Request") -> "Response":
72
+ from starlette.responses import JSONResponse
73
+
74
+ if not app.event_namespace:
75
+ return JSONResponse({})
76
+
77
+ return JSONResponse(app.event_namespace.token_to_sid)
78
+
79
+ app._api.add_route(
80
+ ACTIVE_CONNECTIONS,
81
+ active_connections,
82
+ methods=["GET"],
83
+ )
84
+
85
+ @staticmethod
86
+ def _add_clone_state_endpoint(app: "App") -> None:
87
+ """Add an endpoint to the app that clones the current state.
88
+
89
+ Args:
90
+ app: The application instance to which the endpoint will be added.
91
+ """
92
+ if not app._api:
93
+ return
94
+
95
+ async def clone_state(request: "Request") -> "Response":
96
+ import uuid
97
+
98
+ from starlette.responses import JSONResponse
99
+
100
+ from reflex.state import _substate_key
101
+
102
+ if not app.event_namespace:
103
+ return JSONResponse({})
104
+
105
+ token_to_clone = await request.json()
106
+
107
+ if not isinstance(token_to_clone, str):
108
+ return JSONResponse(
109
+ {"error": "Token to clone must be a string."}, status_code=400
110
+ )
111
+
112
+ old_state = await app.state_manager.get_state(token_to_clone)
113
+
114
+ new_state = _deep_copy(old_state)
115
+
116
+ new_token = uuid.uuid4().hex
117
+
118
+ all_states = [new_state]
119
+
120
+ found_new = True
121
+
122
+ while found_new:
123
+ found_new = False
124
+
125
+ for state in all_states:
126
+ for substate in state.substates.values():
127
+ substate._was_touched = True
128
+
129
+ if substate not in all_states:
130
+ all_states.append(substate)
131
+
132
+ found_new = True
133
+
134
+ await app.state_manager.set_state(
135
+ _substate_key(new_token, new_state), new_state
136
+ )
137
+
138
+ return JSONResponse(new_token)
139
+
140
+ app._api.add_route(
141
+ CLONE_STATE,
142
+ clone_state,
143
+ methods=["POST"],
144
+ )
reflex/plugins/base.py CHANGED
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, ParamSpec, Protocol, TypedDict
7
7
  from typing_extensions import Unpack
8
8
 
9
9
  if TYPE_CHECKING:
10
- from reflex.app import UnevaluatedPage
10
+ from reflex.app import App, UnevaluatedPage
11
11
 
12
12
 
13
13
  class CommonContext(TypedDict):
@@ -44,6 +44,12 @@ class PreCompileContext(CommonContext):
44
44
  unevaluated_pages: Sequence["UnevaluatedPage"]
45
45
 
46
46
 
47
+ class PostCompileContext(CommonContext):
48
+ """Context for post-compile hooks."""
49
+
50
+ app: "App"
51
+
52
+
47
53
  class Plugin:
48
54
  """Base class for all plugins."""
49
55
 
@@ -104,6 +110,13 @@ class Plugin:
104
110
  context: The context for the plugin.
105
111
  """
106
112
 
113
+ def post_compile(self, **context: Unpack[PostCompileContext]) -> None:
114
+ """Called after the compilation of the plugin.
115
+
116
+ Args:
117
+ context: The context for the plugin.
118
+ """
119
+
107
120
  def __repr__(self):
108
121
  """Return a string representation of the plugin.
109
122
 
reflex/reflex.py CHANGED
@@ -58,7 +58,7 @@ def _init(
58
58
  ai: bool = False,
59
59
  ):
60
60
  """Initialize a new Reflex app in the given directory."""
61
- from reflex.utils import exec, prerequisites
61
+ from reflex.utils import exec, frontend_skeleton, prerequisites, templates
62
62
 
63
63
  # Show system info
64
64
  exec.output_system_info()
@@ -80,13 +80,13 @@ def _init(
80
80
  prerequisites.initialize_frontend_dependencies()
81
81
 
82
82
  # Initialize the app.
83
- template = prerequisites.initialize_app(app_name, template)
83
+ template = templates.initialize_app(app_name, template)
84
84
 
85
85
  # Initialize the .gitignore.
86
- prerequisites.initialize_gitignore()
86
+ frontend_skeleton.initialize_gitignore()
87
87
 
88
88
  # Initialize the requirements.txt.
89
- needs_user_manual_update = prerequisites.initialize_requirements_txt()
89
+ needs_user_manual_update = frontend_skeleton.initialize_requirements_txt()
90
90
 
91
91
  template_msg = f" using the {template} template" if template else ""
92
92
  # Finish initializing the app.
@@ -371,7 +371,7 @@ def compile(dry: bool):
371
371
  _init(name=get_config().app_name)
372
372
  get_config(reload=True)
373
373
  starting_time = time.monotonic()
374
- prerequisites.compile_app(dry_run=dry)
374
+ prerequisites.get_compiled_app(dry_run=dry)
375
375
  elapsed_time = time.monotonic() - starting_time
376
376
  console.success(f"App compiled successfully in {elapsed_time:.3f} seconds.")
377
377
 
@@ -737,9 +737,10 @@ def deploy(
737
737
  def rename(new_name: str):
738
738
  """Rename the app in the current directory."""
739
739
  from reflex.utils import prerequisites
740
+ from reflex.utils.rename import rename_app
740
741
 
741
742
  prerequisites.validate_app_name(new_name)
742
- prerequisites.rename_app(new_name, get_config().loglevel)
743
+ rename_app(new_name, get_config().loglevel)
743
744
 
744
745
 
745
746
  if TYPE_CHECKING:
reflex/route.py CHANGED
@@ -6,6 +6,7 @@ import re
6
6
  from collections.abc import Callable
7
7
 
8
8
  from reflex import constants
9
+ from reflex.config import get_config
9
10
 
10
11
 
11
12
  def verify_route_validity(route: str) -> None:
@@ -211,6 +212,9 @@ def get_router(routes: list[str]) -> Callable[[str], str | None]:
211
212
  Returns:
212
213
  The first matching route, or None if no match is found.
213
214
  """
215
+ config = get_config()
216
+ if config.frontend_path:
217
+ path = path.removeprefix(config.frontend_path)
214
218
  path = "/" + path.removeprefix("/").removesuffix("/")
215
219
  if path == "/index":
216
220
  path = "/"
reflex/state.py CHANGED
@@ -509,7 +509,7 @@ class BaseState(EvenMoreBasicBaseState):
509
509
 
510
510
  new_backend_vars = {
511
511
  name: value
512
- for name, value in cls.__dict__.items()
512
+ for name, value in list(cls.__dict__.items())
513
513
  if types.is_backend_base_variable(name, cls)
514
514
  }
515
515
  # Add annotated backend vars that may not have a default value.
@@ -2491,7 +2491,7 @@ class OnLoadInternalState(State):
2491
2491
  # Cache the app reference for subsequent calls.
2492
2492
  if type(self)._app_ref is None:
2493
2493
  type(self)._app_ref = app
2494
- load_events = app.get_load_events(self.router._page.path)
2494
+ load_events = app.get_load_events(self.router.url.path)
2495
2495
  if not load_events:
2496
2496
  self.is_hydrated = True
2497
2497
  return None # Fast path for navigation with no on_load events defined.
reflex/testing.py CHANGED
@@ -45,7 +45,7 @@ from reflex.state import (
45
45
  StateManagerRedis,
46
46
  reload_state_module,
47
47
  )
48
- from reflex.utils import console
48
+ from reflex.utils import console, js_runtimes
49
49
  from reflex.utils.export import export
50
50
  from reflex.utils.types import ASGIApp
51
51
 
@@ -403,9 +403,7 @@ class AppHarness:
403
403
  # Start the frontend.
404
404
  self.frontend_process = reflex.utils.processes.new_process(
405
405
  [
406
- *reflex.utils.prerequisites.get_js_package_executor(raise_on_none=True)[
407
- 0
408
- ],
406
+ *js_runtimes.get_js_package_executor(raise_on_none=True)[0],
409
407
  "run",
410
408
  "dev",
411
409
  ],
@@ -1010,7 +1008,7 @@ class AppHarnessProd(AppHarness):
1010
1008
  / reflex.constants.Dirs.STATIC
1011
1009
  )
1012
1010
  error_page_map = {
1013
- 404: web_root / "404" / "index.html",
1011
+ 404: web_root / "404.html",
1014
1012
  }
1015
1013
  with Subdir404TCPServer(
1016
1014
  ("", 0),
reflex/utils/build.py CHANGED
@@ -4,12 +4,13 @@ from __future__ import annotations
4
4
 
5
5
  import os
6
6
  import zipfile
7
- from pathlib import Path
7
+ from pathlib import Path, PosixPath
8
8
 
9
9
  from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
10
10
 
11
11
  from reflex import constants
12
- from reflex.utils import console, path_ops, prerequisites, processes
12
+ from reflex.config import get_config
13
+ from reflex.utils import console, js_runtimes, path_ops, prerequisites, processes
13
14
  from reflex.utils.exec import is_in_app_harness
14
15
 
15
16
 
@@ -187,7 +188,7 @@ def build():
187
188
  # Start the subprocess with the progress bar.
188
189
  process = processes.new_process(
189
190
  [
190
- *prerequisites.get_js_package_executor(raise_on_none=True)[0],
191
+ *js_runtimes.get_js_package_executor(raise_on_none=True)[0],
191
192
  "run",
192
193
  "export",
193
194
  ],
@@ -200,6 +201,23 @@ def build():
200
201
  )
201
202
  processes.show_progress("Creating Production Build", process, checkpoints)
202
203
  _duplicate_index_html_to_parent_dir(wdir / constants.Dirs.STATIC)
204
+ path_ops.cp(
205
+ wdir / constants.Dirs.STATIC / constants.ReactRouter.SPA_FALLBACK,
206
+ wdir / constants.Dirs.STATIC / "404.html",
207
+ )
208
+
209
+ config = get_config()
210
+
211
+ if frontend_path := config.frontend_path.strip("/"):
212
+ frontend_path = PosixPath(frontend_path)
213
+ first_part = frontend_path.parts[0]
214
+ for child in list((wdir / constants.Dirs.STATIC).iterdir()):
215
+ if child.is_dir() and child.name == first_part:
216
+ continue
217
+ path_ops.mv(
218
+ child,
219
+ wdir / constants.Dirs.STATIC / frontend_path / child.name,
220
+ )
203
221
 
204
222
 
205
223
  def setup_frontend(
reflex/utils/exec.py CHANGED
@@ -231,17 +231,17 @@ def run_frontend(root: Path, port: str, backend_present: bool = True):
231
231
  port: The port to run the frontend on.
232
232
  backend_present: Whether the backend is present.
233
233
  """
234
- from reflex.utils import prerequisites
234
+ from reflex.utils import js_runtimes
235
235
 
236
236
  # validate dependencies before run
237
- prerequisites.validate_frontend_dependencies(init=False)
237
+ js_runtimes.validate_frontend_dependencies(init=False)
238
238
 
239
239
  # Run the frontend in development mode.
240
240
  console.rule("[bold green]App Running")
241
241
  os.environ["PORT"] = str(get_config().frontend_port if port is None else port)
242
242
  run_process_and_launch_url(
243
243
  [
244
- *prerequisites.get_js_package_executor(raise_on_none=True)[0],
244
+ *js_runtimes.get_js_package_executor(raise_on_none=True)[0],
245
245
  "run",
246
246
  "dev",
247
247
  ],
@@ -257,16 +257,16 @@ def run_frontend_prod(root: Path, port: str, backend_present: bool = True):
257
257
  port: The port to run the frontend on.
258
258
  backend_present: Whether the backend is present.
259
259
  """
260
- from reflex.utils import prerequisites
260
+ from reflex.utils import js_runtimes
261
261
 
262
262
  # Set the port.
263
263
  os.environ["PORT"] = str(get_config().frontend_port if port is None else port)
264
264
  # validate dependencies before run
265
- prerequisites.validate_frontend_dependencies(init=False)
265
+ js_runtimes.validate_frontend_dependencies(init=False)
266
266
  # Run the frontend in production mode.
267
267
  console.rule("[bold green]App Running")
268
268
  run_process_and_launch_url(
269
- [*prerequisites.get_js_package_executor(raise_on_none=True)[0], "run", "prod"],
269
+ [*js_runtimes.get_js_package_executor(raise_on_none=True)[0], "run", "prod"],
270
270
  backend_present,
271
271
  )
272
272
 
@@ -670,7 +670,7 @@ def output_system_info():
670
670
  if console._LOG_LEVEL > constants.LogLevel.DEBUG:
671
671
  return
672
672
 
673
- from reflex.utils import prerequisites
673
+ from reflex.utils import js_runtimes
674
674
 
675
675
  config = get_config()
676
676
  try:
@@ -684,13 +684,13 @@ def output_system_info():
684
684
 
685
685
  dependencies = [
686
686
  f"[Reflex {constants.Reflex.VERSION} with Python {platform.python_version()} (PATH: {sys.executable})]",
687
- f"[Node {prerequisites.get_node_version()} (Minimum: {constants.Node.MIN_VERSION}) (PATH:{path_ops.get_node_path()})]",
687
+ f"[Node {js_runtimes.get_node_version()} (Minimum: {constants.Node.MIN_VERSION}) (PATH:{path_ops.get_node_path()})]",
688
688
  ]
689
689
 
690
690
  system = platform.system()
691
691
 
692
692
  dependencies.append(
693
- f"[Bun {prerequisites.get_bun_version()} (Minimum: {constants.Bun.MIN_VERSION}) (PATH: {path_ops.get_bun_path()})]"
693
+ f"[Bun {js_runtimes.get_bun_version()} (Minimum: {constants.Bun.MIN_VERSION}) (PATH: {path_ops.get_bun_path()})]"
694
694
  )
695
695
 
696
696
  if system == "Linux":
@@ -704,10 +704,10 @@ def output_system_info():
704
704
  console.debug(f"{dep}")
705
705
 
706
706
  console.debug(
707
- f"Using package installer at: {prerequisites.get_nodejs_compatible_package_managers(raise_on_none=False)}"
707
+ f"Using package installer at: {js_runtimes.get_nodejs_compatible_package_managers(raise_on_none=False)}"
708
708
  )
709
709
  console.debug(
710
- f"Using package executer at: {prerequisites.get_js_package_executor(raise_on_none=False)}"
710
+ f"Using package executer at: {js_runtimes.get_js_package_executor(raise_on_none=False)}"
711
711
  )
712
712
  if system != "Windows":
713
713
  console.debug(f"Unzip path: {path_ops.which('unzip')}")
@@ -0,0 +1,254 @@
1
+ """This module provides utility functions to initialize the frontend skeleton."""
2
+
3
+ import json
4
+ import random
5
+ import re
6
+ from pathlib import Path
7
+
8
+ import click
9
+
10
+ from reflex import constants
11
+ from reflex.compiler import templates
12
+ from reflex.config import Config, get_config
13
+ from reflex.utils import console, path_ops
14
+ from reflex.utils.prerequisites import get_project_hash, get_web_dir
15
+ from reflex.utils.registry import get_npm_registry
16
+
17
+
18
+ def initialize_gitignore(
19
+ gitignore_file: Path = constants.GitIgnore.FILE,
20
+ files_to_ignore: set[str] | list[str] = constants.GitIgnore.DEFAULTS,
21
+ ):
22
+ """Initialize the template .gitignore file.
23
+
24
+ Args:
25
+ gitignore_file: The .gitignore file to create.
26
+ files_to_ignore: The files to add to the .gitignore file.
27
+ """
28
+ # Combine with the current ignored files.
29
+ current_ignore: list[str] = []
30
+ if gitignore_file.exists():
31
+ current_ignore = [ln.strip() for ln in gitignore_file.read_text().splitlines()]
32
+
33
+ if files_to_ignore == current_ignore:
34
+ console.debug(f"{gitignore_file} already up to date.")
35
+ return
36
+ files_to_ignore = [ln for ln in files_to_ignore if ln not in current_ignore]
37
+ files_to_ignore += current_ignore
38
+
39
+ # Write files to the .gitignore file.
40
+ gitignore_file.touch(exist_ok=True)
41
+ console.debug(f"Creating {gitignore_file}")
42
+ gitignore_file.write_text("\n".join(files_to_ignore) + "\n")
43
+
44
+
45
+ def initialize_requirements_txt() -> bool:
46
+ """Initialize the requirements.txt file.
47
+ If absent and no pyproject.toml file exists, generate one for the user.
48
+ If the requirements.txt does not have reflex as dependency,
49
+ generate a requirement pinning current version and append to
50
+ the requirements.txt file.
51
+
52
+ Returns:
53
+ True if the user has to update the requirements.txt file.
54
+
55
+ Raises:
56
+ Exit: If the requirements.txt file cannot be read or written to.
57
+ """
58
+ requirements_file_path = Path(constants.RequirementsTxt.FILE)
59
+ if (
60
+ not requirements_file_path.exists()
61
+ and Path(constants.PyprojectToml.FILE).exists()
62
+ ):
63
+ return True
64
+
65
+ requirements_file_path.touch(exist_ok=True)
66
+
67
+ for encoding in [None, "utf-8"]:
68
+ try:
69
+ content = requirements_file_path.read_text(encoding)
70
+ break
71
+ except UnicodeDecodeError:
72
+ continue
73
+ except Exception as e:
74
+ console.error(f"Failed to read {requirements_file_path}.")
75
+ raise click.exceptions.Exit(1) from e
76
+ else:
77
+ return True
78
+
79
+ for line in content.splitlines():
80
+ if re.match(r"^reflex[^a-zA-Z0-9]", line):
81
+ console.debug(f"{requirements_file_path} already has reflex as dependency.")
82
+ return False
83
+
84
+ console.debug(
85
+ f"Appending {constants.RequirementsTxt.DEFAULTS_STUB} to {requirements_file_path}"
86
+ )
87
+ with requirements_file_path.open("a", encoding=encoding) as f:
88
+ f.write(
89
+ "\n" + constants.RequirementsTxt.DEFAULTS_STUB + constants.Reflex.VERSION
90
+ )
91
+
92
+ return False
93
+
94
+
95
+ def initialize_web_directory():
96
+ """Initialize the web directory on reflex init."""
97
+ console.log("Initializing the web directory.")
98
+
99
+ # Reuse the hash if one is already created, so we don't over-write it when running reflex init
100
+ project_hash = get_project_hash()
101
+
102
+ console.debug(f"Copying {constants.Templates.Dirs.WEB_TEMPLATE} to {get_web_dir()}")
103
+ path_ops.copy_tree(constants.Templates.Dirs.WEB_TEMPLATE, str(get_web_dir()))
104
+
105
+ console.debug("Initializing the web directory.")
106
+ initialize_package_json()
107
+
108
+ console.debug("Initializing the bun config file.")
109
+ initialize_bun_config()
110
+
111
+ console.debug("Initializing the .npmrc file.")
112
+ initialize_npmrc()
113
+
114
+ console.debug("Initializing the public directory.")
115
+ path_ops.mkdir(get_web_dir() / constants.Dirs.PUBLIC)
116
+
117
+ console.debug("Initializing the react-router.config.js file.")
118
+ update_react_router_config()
119
+
120
+ console.debug("Initializing the vite.config.js file.")
121
+ initialize_vite_config()
122
+
123
+ console.debug("Initializing the reflex.json file.")
124
+ # Initialize the reflex json file.
125
+ init_reflex_json(project_hash=project_hash)
126
+
127
+
128
+ def update_react_router_config(prerender_routes: bool = False):
129
+ """Update react-router.config.js config from Reflex config.
130
+
131
+ Args:
132
+ prerender_routes: Whether to enable prerendering of routes.
133
+ """
134
+ react_router_config_file_path = get_web_dir() / constants.ReactRouter.CONFIG_FILE
135
+
136
+ new_react_router_config = _update_react_router_config(
137
+ get_config(), prerender_routes=prerender_routes
138
+ )
139
+
140
+ # Overwriting the config file triggers a full server reload, so make sure
141
+ # there is actually a diff.
142
+ old_react_router_config = (
143
+ react_router_config_file_path.read_text()
144
+ if react_router_config_file_path.exists()
145
+ else ""
146
+ )
147
+ if old_react_router_config != new_react_router_config:
148
+ react_router_config_file_path.write_text(new_react_router_config)
149
+
150
+
151
+ def _update_react_router_config(config: Config, prerender_routes: bool = False):
152
+ basename = "/" + (config.frontend_path or "").strip("/")
153
+ if not basename.endswith("/"):
154
+ basename += "/"
155
+
156
+ react_router_config = {
157
+ "basename": basename,
158
+ "future": {
159
+ "unstable_optimizeDeps": True,
160
+ },
161
+ "ssr": False,
162
+ }
163
+
164
+ if prerender_routes:
165
+ react_router_config["prerender"] = True
166
+ react_router_config["build"] = constants.Dirs.BUILD_DIR
167
+
168
+ return f"export default {json.dumps(react_router_config)};"
169
+
170
+
171
+ def _compile_package_json():
172
+ return templates.PACKAGE_JSON.render(
173
+ scripts={
174
+ "dev": constants.PackageJson.Commands.DEV,
175
+ "export": constants.PackageJson.Commands.EXPORT,
176
+ "prod": constants.PackageJson.Commands.PROD,
177
+ },
178
+ dependencies=constants.PackageJson.DEPENDENCIES,
179
+ dev_dependencies=constants.PackageJson.DEV_DEPENDENCIES,
180
+ overrides=constants.PackageJson.OVERRIDES,
181
+ )
182
+
183
+
184
+ def initialize_package_json():
185
+ """Render and write in .web the package.json file."""
186
+ output_path = get_web_dir() / constants.PackageJson.PATH
187
+ output_path.write_text(_compile_package_json())
188
+
189
+
190
+ def _compile_vite_config(config: Config):
191
+ # base must have exactly one trailing slash
192
+ base = "/"
193
+ if frontend_path := config.frontend_path.strip("/"):
194
+ base += frontend_path + "/"
195
+ return templates.VITE_CONFIG.render(base=base)
196
+
197
+
198
+ def initialize_vite_config():
199
+ """Render and write in .web the vite.config.js file using Reflex config."""
200
+ vite_config_file_path = get_web_dir() / constants.ReactRouter.VITE_CONFIG_FILE
201
+ vite_config_file_path.write_text(_compile_vite_config(get_config()))
202
+
203
+
204
+ def initialize_bun_config():
205
+ """Initialize the bun config file."""
206
+ bun_config_path = get_web_dir() / constants.Bun.CONFIG_PATH
207
+
208
+ if (custom_bunfig := Path(constants.Bun.CONFIG_PATH)).exists():
209
+ bunfig_content = custom_bunfig.read_text()
210
+ console.info(f"Copying custom bunfig.toml inside {get_web_dir()} folder")
211
+ else:
212
+ best_registry = get_npm_registry()
213
+ bunfig_content = constants.Bun.DEFAULT_CONFIG.format(registry=best_registry)
214
+
215
+ bun_config_path.write_text(bunfig_content)
216
+
217
+
218
+ def initialize_npmrc():
219
+ """Initialize the .npmrc file."""
220
+ npmrc_path = get_web_dir() / constants.Node.CONFIG_PATH
221
+
222
+ if (custom_npmrc := Path(constants.Node.CONFIG_PATH)).exists():
223
+ npmrc_content = custom_npmrc.read_text()
224
+ console.info(f"Copying custom .npmrc inside {get_web_dir()} folder")
225
+ else:
226
+ best_registry = get_npm_registry()
227
+ npmrc_content = constants.Node.DEFAULT_CONFIG.format(registry=best_registry)
228
+
229
+ npmrc_path.write_text(npmrc_content)
230
+
231
+
232
+ def init_reflex_json(project_hash: int | None):
233
+ """Write the hash of the Reflex project to a REFLEX_JSON.
234
+
235
+ Reuse the hash if one is already created, therefore do not
236
+ overwrite it every time we run the reflex init command
237
+ .
238
+
239
+ Args:
240
+ project_hash: The app hash.
241
+ """
242
+ if project_hash is not None:
243
+ console.debug(f"Project hash is already set to {project_hash}.")
244
+ else:
245
+ # Get a random project hash.
246
+ project_hash = random.getrandbits(128)
247
+ console.debug(f"Setting project hash to {project_hash}.")
248
+
249
+ # Write the hash and version to the reflex json file.
250
+ reflex_json = {
251
+ "version": constants.Reflex.VERSION,
252
+ "project_hash": project_hash,
253
+ }
254
+ path_ops.update_json_file(get_web_dir() / constants.Reflex.JSON, reflex_json)