-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Expand file tree
/
Copy pathfrontend_skeleton.py
More file actions
309 lines (237 loc) ยท 9.96 KB
/
frontend_skeleton.py
File metadata and controls
309 lines (237 loc) ยท 9.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
"""This module provides utility functions to initialize the frontend skeleton."""
import json
import random
from pathlib import Path
from reflex_base import constants
from reflex_base.config import Config, get_config
from reflex_base.environment import environment
from reflex.compiler import templates
from reflex.utils import console, path_ops
from reflex.utils.prerequisites import get_project_hash, get_web_dir
from reflex.utils.registry import get_npm_registry
def initialize_gitignore(
gitignore_file: Path = constants.GitIgnore.FILE,
files_to_ignore: set[str] | list[str] = constants.GitIgnore.DEFAULTS,
):
"""Initialize the template .gitignore file.
Args:
gitignore_file: The .gitignore file to create.
files_to_ignore: The files to add to the .gitignore file.
"""
# Combine with the current ignored files.
current_ignore: list[str] = []
if gitignore_file.exists():
current_ignore = [ln.strip() for ln in gitignore_file.read_text().splitlines()]
if files_to_ignore == current_ignore:
console.debug(f"{gitignore_file} already up to date.")
return
files_to_ignore = [ln for ln in files_to_ignore if ln not in current_ignore]
files_to_ignore += current_ignore
# Write files to the .gitignore file.
gitignore_file.touch(exist_ok=True)
console.debug(f"Creating {gitignore_file}")
gitignore_file.write_text("\n".join(files_to_ignore) + "\n")
def _read_dependency_file(file_path: Path) -> tuple[str | None, str | None]:
"""Read a dependency file with a forgiving encoding strategy.
Args:
file_path: The file to read.
Returns:
A tuple of file content and the encoding used to read it.
"""
try:
return file_path.read_text(), None
except UnicodeDecodeError:
pass
except Exception as e:
console.error(f"Failed to read {file_path} due to {e}.")
raise SystemExit(1) from None
try:
return file_path.read_text(encoding="utf-8"), "utf-8"
except UnicodeDecodeError:
return None, None
except Exception as e:
console.error(f"Failed to read {file_path} due to {e}.")
raise SystemExit(1) from None
def _has_reflex_requirement_line(requirements_text: str) -> bool:
"""Check whether requirements.txt already contains reflex.
Returns:
Whether reflex is already present in the requirements text.
"""
return any(
_is_reflex_dependency_spec(line) for line in requirements_text.splitlines()
)
def _is_reflex_dependency_spec(requirement: str) -> bool:
"""Check whether a dependency specification refers to the reflex package.
Args:
requirement: The dependency specification to check.
Returns:
Whether the specification refers to the reflex package.
"""
requirement = requirement.strip()
if not requirement.lower().startswith("reflex"):
return False
suffix = requirement[len("reflex") :]
if suffix.startswith("["):
extras_end = suffix.find("]")
if extras_end == -1:
return False
suffix = suffix[extras_end + 1 :]
return not suffix or suffix.lstrip().startswith((
"==",
"!=",
">=",
"<=",
"~=",
">",
"<",
";",
"@",
))
def initialize_requirements_txt(
requirements_file_path: Path = Path(constants.RequirementsTxt.FILE),
pyproject_file_path: Path = Path(constants.PyprojectToml.FILE),
) -> bool:
"""Initialize the requirements.txt file.
If a project already uses pyproject.toml, leave dependency management to the
package manager. Otherwise ensure requirements.txt pins the current Reflex
version for legacy workflows.
Returns:
True if the user has to update the requirements.txt file.
"""
if not requirements_file_path.exists() and pyproject_file_path.exists():
return False
requirements_file_path.touch(exist_ok=True)
content, encoding = _read_dependency_file(requirements_file_path)
if content is None:
return True
if _has_reflex_requirement_line(content):
console.debug(f"{requirements_file_path} already has reflex as dependency.")
return False
console.debug(
f"Appending {constants.RequirementsTxt.DEFAULTS_STUB} to {requirements_file_path}"
)
with requirements_file_path.open("a", encoding=encoding) as f:
f.write(
"\n" + constants.RequirementsTxt.DEFAULTS_STUB + constants.Reflex.VERSION
)
return False
def initialize_web_directory():
"""Initialize the web directory on reflex init."""
console.log("Initializing the web directory.")
# Reuse the hash if one is already created, so we don't over-write it when running reflex init
project_hash = get_project_hash()
console.debug(f"Copying {constants.Templates.Dirs.WEB_TEMPLATE} to {get_web_dir()}")
path_ops.copy_tree(constants.Templates.Dirs.WEB_TEMPLATE, str(get_web_dir()))
console.debug("Initializing the web directory.")
initialize_package_json()
console.debug("Initializing the bun config file.")
initialize_bun_config()
console.debug("Initializing the .npmrc file.")
initialize_npmrc()
console.debug("Initializing the public directory.")
path_ops.mkdir(get_web_dir() / constants.Dirs.PUBLIC)
console.debug("Initializing the react-router.config.js file.")
update_react_router_config()
console.debug("Initializing the vite.config.js file.")
initialize_vite_config()
console.debug("Initializing the reflex.json file.")
# Initialize the reflex json file.
init_reflex_json(project_hash=project_hash)
def update_react_router_config(prerender_routes: bool = False):
"""Update react-router.config.js config from Reflex config.
Args:
prerender_routes: Whether to enable prerendering of routes.
"""
react_router_config_file_path = get_web_dir() / constants.ReactRouter.CONFIG_FILE
new_react_router_config = _update_react_router_config(
get_config(), prerender_routes=prerender_routes
)
# Overwriting the config file triggers a full server reload, so make sure
# there is actually a diff.
old_react_router_config = (
react_router_config_file_path.read_text()
if react_router_config_file_path.exists()
else ""
)
if old_react_router_config != new_react_router_config:
react_router_config_file_path.write_text(new_react_router_config)
def _update_react_router_config(config: Config, prerender_routes: bool = False):
react_router_config = {
"basename": config.prepend_frontend_path("/"),
"future": {
"unstable_optimizeDeps": True,
},
"ssr": False,
}
if prerender_routes:
react_router_config["prerender"] = True
react_router_config["build"] = constants.Dirs.BUILD_DIR
return f"export default {json.dumps(react_router_config)};"
def _compile_package_json():
return templates.package_json_template(
scripts={
"dev": constants.PackageJson.Commands.DEV,
"export": constants.PackageJson.Commands.EXPORT,
},
dependencies=constants.PackageJson.DEPENDENCIES,
dev_dependencies=constants.PackageJson.DEV_DEPENDENCIES,
overrides=constants.PackageJson.OVERRIDES,
)
def initialize_package_json():
"""Render and write in .web the package.json file."""
output_path = get_web_dir() / constants.PackageJson.PATH
output_path.write_text(_compile_package_json())
def _compile_vite_config(config: Config):
# base must have exactly one trailing slash
return templates.vite_config_template(
base=config.prepend_frontend_path("/"),
hmr=environment.VITE_HMR.get(),
force_full_reload=environment.VITE_FORCE_FULL_RELOAD.get(),
experimental_hmr=environment.VITE_EXPERIMENTAL_HMR.get(),
sourcemap=environment.VITE_SOURCEMAP.get(),
allowed_hosts=config.vite_allowed_hosts,
)
def initialize_vite_config():
"""Render and write in .web the vite.config.js file using Reflex config."""
vite_config_file_path = get_web_dir() / constants.ReactRouter.VITE_CONFIG_FILE
vite_config_file_path.write_text(_compile_vite_config(get_config()))
def initialize_bun_config():
"""Initialize the bun config file."""
bun_config_path = get_web_dir() / constants.Bun.CONFIG_PATH
if (custom_bunfig := Path(constants.Bun.CONFIG_PATH)).exists():
bunfig_content = custom_bunfig.read_text()
console.info(f"Copying custom bunfig.toml inside {get_web_dir()} folder")
else:
best_registry = get_npm_registry()
bunfig_content = constants.Bun.DEFAULT_CONFIG.format(registry=best_registry)
bun_config_path.write_text(bunfig_content)
def initialize_npmrc():
"""Initialize the .npmrc file."""
npmrc_path = get_web_dir() / constants.Node.CONFIG_PATH
if (custom_npmrc := Path(constants.Node.CONFIG_PATH)).exists():
npmrc_content = custom_npmrc.read_text()
console.info(f"Copying custom .npmrc inside {get_web_dir()} folder")
else:
best_registry = get_npm_registry()
npmrc_content = constants.Node.DEFAULT_CONFIG.format(registry=best_registry)
npmrc_path.write_text(npmrc_content)
def init_reflex_json(project_hash: int | None):
"""Write the hash of the Reflex project to a REFLEX_JSON.
Reuse the hash if one is already created, therefore do not
overwrite it every time we run the reflex init command
.
Args:
project_hash: The app hash.
"""
if project_hash is not None:
console.debug(f"Project hash is already set to {project_hash}.")
else:
# Get a random project hash.
project_hash = random.getrandbits(128)
console.debug(f"Setting project hash to {project_hash}.")
# Write the hash and version to the reflex json file.
reflex_json = {
"version": constants.Reflex.VERSION,
"project_hash": project_hash,
}
path_ops.update_json_file(get_web_dir() / constants.Reflex.JSON, reflex_json)