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,
|
_substate_key,
|
||||||
code_uses_state_contexts,
|
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.exec import is_prod_mode, is_testing_env
|
||||||
from reflex.utils.imports import ImportVar
|
from reflex.utils.imports import ImportVar
|
||||||
|
|
||||||
@ -991,9 +999,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
should_compile = self._should_compile()
|
should_compile = self._should_compile()
|
||||||
|
|
||||||
if not should_compile:
|
if not should_compile:
|
||||||
for route in self._unevaluated_pages:
|
with console.timing("Evaluate Pages (Backend)"):
|
||||||
console.debug(f"Evaluating page: {route}")
|
for route in self._unevaluated_pages:
|
||||||
self._compile_page(route, save_page=should_compile)
|
console.debug(f"Evaluating page: {route}")
|
||||||
|
self._compile_page(route, save_page=should_compile)
|
||||||
|
|
||||||
# Add the optional endpoints (_upload)
|
# Add the optional endpoints (_upload)
|
||||||
self._add_optional_endpoints()
|
self._add_optional_endpoints()
|
||||||
@ -1019,10 +1028,11 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
+ adhoc_steps_without_executor,
|
+ adhoc_steps_without_executor,
|
||||||
)
|
)
|
||||||
|
|
||||||
for route in self._unevaluated_pages:
|
with console.timing("Evaluate Pages (Frontend)"):
|
||||||
console.debug(f"Evaluating page: {route}")
|
for route in self._unevaluated_pages:
|
||||||
self._compile_page(route, save_page=should_compile)
|
console.debug(f"Evaluating page: {route}")
|
||||||
progress.advance(task)
|
self._compile_page(route, save_page=should_compile)
|
||||||
|
progress.advance(task)
|
||||||
|
|
||||||
# Add the optional endpoints (_upload)
|
# Add the optional endpoints (_upload)
|
||||||
self._add_optional_endpoints()
|
self._add_optional_endpoints()
|
||||||
@ -1057,13 +1067,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
custom_components |= component._get_all_custom_components()
|
custom_components |= component._get_all_custom_components()
|
||||||
|
|
||||||
# Perform auto-memoization of stateful components.
|
# Perform auto-memoization of stateful components.
|
||||||
(
|
with console.timing("Auto-memoize StatefulComponents"):
|
||||||
stateful_components_path,
|
(
|
||||||
stateful_components_code,
|
stateful_components_path,
|
||||||
page_components,
|
stateful_components_code,
|
||||||
) = compiler.compile_stateful_components(self._pages.values())
|
page_components,
|
||||||
|
) = compiler.compile_stateful_components(self._pages.values())
|
||||||
progress.advance(task)
|
progress.advance(task)
|
||||||
|
|
||||||
# Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State.
|
# 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:
|
if code_uses_state_contexts(stateful_components_code) and self._state is None:
|
||||||
@ -1086,6 +1096,17 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
progress.advance(task)
|
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.
|
# Use a forking process pool, if possible. Much faster, especially for large sites.
|
||||||
# Fallback to ThreadPoolExecutor as something that will always work.
|
# Fallback to ThreadPoolExecutor as something that will always work.
|
||||||
executor = None
|
executor = None
|
||||||
@ -1138,9 +1159,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
_submit_work(compiler.remove_tailwind_from_postcss)
|
_submit_work(compiler.remove_tailwind_from_postcss)
|
||||||
|
|
||||||
# Wait for all compilation tasks to complete.
|
# Wait for all compilation tasks to complete.
|
||||||
for future in concurrent.futures.as_completed(result_futures):
|
with console.timing("Compile to Javascript"):
|
||||||
compile_results.append(future.result())
|
for future in concurrent.futures.as_completed(result_futures):
|
||||||
progress.advance(task)
|
compile_results.append(future.result())
|
||||||
|
progress.advance(task)
|
||||||
|
|
||||||
app_root = self._app_root(app_wrappers=app_wrappers)
|
app_root = self._app_root(app_wrappers=app_wrappers)
|
||||||
|
|
||||||
@ -1175,7 +1197,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
progress.stop()
|
progress.stop()
|
||||||
|
|
||||||
# Install frontend packages.
|
# 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
|
# Setup the next.config.js
|
||||||
transpile_packages = [
|
transpile_packages = [
|
||||||
@ -1201,8 +1224,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
# Remove pages that are no longer in the app.
|
# Remove pages that are no longer in the app.
|
||||||
p.unlink()
|
p.unlink()
|
||||||
|
|
||||||
for output_path, code in compile_results:
|
with console.timing("Write to Disk"):
|
||||||
compiler_utils.write_page(output_path, code)
|
for output_path, code in compile_results:
|
||||||
|
compiler_utils.write_page(output_path, code)
|
||||||
|
|
||||||
@contextlib.asynccontextmanager
|
@contextlib.asynccontextmanager
|
||||||
async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
|
async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
|
||||||
|
@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import contextlib
|
||||||
import inspect
|
import inspect
|
||||||
import shutil
|
import shutil
|
||||||
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import FrameType
|
from types import FrameType
|
||||||
|
|
||||||
@ -317,3 +319,20 @@ def status(*args, **kwargs):
|
|||||||
A new status.
|
A new status.
|
||||||
"""
|
"""
|
||||||
return _console.status(*args, **kwargs)
|
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 = filepath.read_text(encoding="utf-8")
|
||||||
text = re.sub(find, replace, text)
|
text = re.sub(find, replace, text)
|
||||||
filepath.write_text(text, encoding="utf-8")
|
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