Copy/update assets on compile (#4765)
* Add path_ops.update_directory_tree: Copy missing and newer files from src to dest * add console.timing context Log debug messages with timing for different processes. * Update assets tree as app._compile step. If the assets change between hot reload, then update them before reloading (in case a CSS file was added or something). * Add timing for other app._compile events * Only copy assets if assets exist * Fix docstring for update_directory_tree
This commit is contained in:
parent
c17cda3e95
commit
70920a64be
@ -99,7 +99,15 @@ from reflex.state import (
|
||||
_substate_key,
|
||||
code_uses_state_contexts,
|
||||
)
|
||||
from reflex.utils import codespaces, console, exceptions, format, prerequisites, types
|
||||
from reflex.utils import (
|
||||
codespaces,
|
||||
console,
|
||||
exceptions,
|
||||
format,
|
||||
path_ops,
|
||||
prerequisites,
|
||||
types,
|
||||
)
|
||||
from reflex.utils.exec import is_prod_mode, is_testing_env
|
||||
from reflex.utils.imports import ImportVar
|
||||
|
||||
@ -991,9 +999,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
should_compile = self._should_compile()
|
||||
|
||||
if not should_compile:
|
||||
for route in self._unevaluated_pages:
|
||||
console.debug(f"Evaluating page: {route}")
|
||||
self._compile_page(route, save_page=should_compile)
|
||||
with console.timing("Evaluate Pages (Backend)"):
|
||||
for route in self._unevaluated_pages:
|
||||
console.debug(f"Evaluating page: {route}")
|
||||
self._compile_page(route, save_page=should_compile)
|
||||
|
||||
# Add the optional endpoints (_upload)
|
||||
self._add_optional_endpoints()
|
||||
@ -1019,10 +1028,11 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
+ adhoc_steps_without_executor,
|
||||
)
|
||||
|
||||
for route in self._unevaluated_pages:
|
||||
console.debug(f"Evaluating page: {route}")
|
||||
self._compile_page(route, save_page=should_compile)
|
||||
progress.advance(task)
|
||||
with console.timing("Evaluate Pages (Frontend)"):
|
||||
for route in self._unevaluated_pages:
|
||||
console.debug(f"Evaluating page: {route}")
|
||||
self._compile_page(route, save_page=should_compile)
|
||||
progress.advance(task)
|
||||
|
||||
# Add the optional endpoints (_upload)
|
||||
self._add_optional_endpoints()
|
||||
@ -1057,13 +1067,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
custom_components |= component._get_all_custom_components()
|
||||
|
||||
# Perform auto-memoization of stateful components.
|
||||
(
|
||||
stateful_components_path,
|
||||
stateful_components_code,
|
||||
page_components,
|
||||
) = compiler.compile_stateful_components(self._pages.values())
|
||||
|
||||
progress.advance(task)
|
||||
with console.timing("Auto-memoize StatefulComponents"):
|
||||
(
|
||||
stateful_components_path,
|
||||
stateful_components_code,
|
||||
page_components,
|
||||
) = compiler.compile_stateful_components(self._pages.values())
|
||||
progress.advance(task)
|
||||
|
||||
# Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State.
|
||||
if code_uses_state_contexts(stateful_components_code) and self._state is None:
|
||||
@ -1086,6 +1096,17 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
progress.advance(task)
|
||||
|
||||
# Copy the assets.
|
||||
assets_src = Path.cwd() / constants.Dirs.APP_ASSETS
|
||||
if assets_src.is_dir():
|
||||
with console.timing("Copy assets"):
|
||||
path_ops.update_directory_tree(
|
||||
src=assets_src,
|
||||
dest=(
|
||||
Path.cwd() / prerequisites.get_web_dir() / constants.Dirs.PUBLIC
|
||||
),
|
||||
)
|
||||
|
||||
# Use a forking process pool, if possible. Much faster, especially for large sites.
|
||||
# Fallback to ThreadPoolExecutor as something that will always work.
|
||||
executor = None
|
||||
@ -1138,9 +1159,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
_submit_work(compiler.remove_tailwind_from_postcss)
|
||||
|
||||
# Wait for all compilation tasks to complete.
|
||||
for future in concurrent.futures.as_completed(result_futures):
|
||||
compile_results.append(future.result())
|
||||
progress.advance(task)
|
||||
with console.timing("Compile to Javascript"):
|
||||
for future in concurrent.futures.as_completed(result_futures):
|
||||
compile_results.append(future.result())
|
||||
progress.advance(task)
|
||||
|
||||
app_root = self._app_root(app_wrappers=app_wrappers)
|
||||
|
||||
@ -1175,7 +1197,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
progress.stop()
|
||||
|
||||
# Install frontend packages.
|
||||
self._get_frontend_packages(all_imports)
|
||||
with console.timing("Install Frontend Packages"):
|
||||
self._get_frontend_packages(all_imports)
|
||||
|
||||
# Setup the next.config.js
|
||||
transpile_packages = [
|
||||
@ -1201,8 +1224,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
# Remove pages that are no longer in the app.
|
||||
p.unlink()
|
||||
|
||||
for output_path, code in compile_results:
|
||||
compiler_utils.write_page(output_path, code)
|
||||
with console.timing("Write to Disk"):
|
||||
for output_path, code in compile_results:
|
||||
compiler_utils.write_page(output_path, code)
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
|
||||
|
@ -2,8 +2,10 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import inspect
|
||||
import shutil
|
||||
import time
|
||||
from pathlib import Path
|
||||
from types import FrameType
|
||||
|
||||
@ -317,3 +319,20 @@ def status(*args, **kwargs):
|
||||
A new status.
|
||||
"""
|
||||
return _console.status(*args, **kwargs)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def timing(msg: str):
|
||||
"""Create a context manager to time a block of code.
|
||||
|
||||
Args:
|
||||
msg: The message to display.
|
||||
|
||||
Yields:
|
||||
None.
|
||||
"""
|
||||
start = time.time()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
debug(f"[white]\\[timing] {msg}: {time.time() - start:.2f}s[/white]")
|
||||
|
@ -260,3 +260,33 @@ def find_replace(directory: str | Path, find: str, replace: str):
|
||||
text = filepath.read_text(encoding="utf-8")
|
||||
text = re.sub(find, replace, text)
|
||||
filepath.write_text(text, encoding="utf-8")
|
||||
|
||||
|
||||
def update_directory_tree(src: Path, dest: Path):
|
||||
"""Recursively copies a directory tree from src to dest.
|
||||
Only copies files if the destination file is missing or modified earlier than the source file.
|
||||
|
||||
Args:
|
||||
src: Source directory
|
||||
dest: Destination directory
|
||||
|
||||
Raises:
|
||||
ValueError: If the source is not a directory
|
||||
"""
|
||||
if not src.is_dir():
|
||||
raise ValueError(f"Source {src} is not a directory")
|
||||
|
||||
# Ensure the destination directory exists
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for item in src.iterdir():
|
||||
dest_item = dest / item.name
|
||||
|
||||
if item.is_dir():
|
||||
# Recursively copy subdirectories
|
||||
update_directory_tree(item, dest_item)
|
||||
elif item.is_file() and (
|
||||
not dest_item.exists() or item.stat().st_mtime > dest_item.stat().st_mtime
|
||||
):
|
||||
# Copy file if it doesn't exist in the destination or is older than the source
|
||||
shutil.copy2(item, dest_item)
|
||||
|
Loading…
Reference in New Issue
Block a user