reorder page evaluation

This commit is contained in:
Khaleel Al-Adhami 2024-08-21 19:24:55 -07:00
parent 7dda611364
commit c9f7ddd4d0
2 changed files with 60 additions and 79 deletions

View File

@ -9,6 +9,7 @@ import dataclasses
import functools import functools
import inspect import inspect
import io import io
import dill
import multiprocess import multiprocess
from pathos import multiprocessing, pools from pathos import multiprocessing, pools
import os import os
@ -47,7 +48,10 @@ from reflex.app_mixins import AppMixin, LifespanMixin, MiddlewareMixin
from reflex.base import Base from reflex.base import Base
from reflex.compiler import compiler from reflex.compiler import compiler
from reflex.compiler import utils as compiler_utils from reflex.compiler import utils as compiler_utils
from reflex.compiler.compiler import ExecutorSafeFunctions from reflex.compiler.compiler import (
ExecutorSafeFunctions,
compile_uncompiled_page_helper,
)
from reflex.components.base.app_wrap import AppWrap from reflex.components.base.app_wrap import AppWrap
from reflex.components.base.error_boundary import ErrorBoundary from reflex.components.base.error_boundary import ErrorBoundary
from reflex.components.base.fragment import Fragment from reflex.components.base.fragment import Fragment
@ -554,49 +558,8 @@ class App(MiddlewareMixin, LifespanMixin, Base):
Args: Args:
route: The route of the page to compile. route: The route of the page to compile.
""" """
uncompiled_page = self.uncompiled_pages[route] component = compiler.compile_uncompiled_page_helper(
route, self.uncompiled_pages[route]
on_load = uncompiled_page.on_load
# Generate the component if it is a callable.
component = self._generate_component(uncompiled_page.component)
# unpack components that return tuples in an rx.fragment.
if isinstance(component, tuple):
component = Fragment.create(*component)
# Ensure state is enabled if this page uses state.
if self.state is None:
if on_load or component._has_stateful_event_triggers():
self._enable_state()
else:
for var in component._get_vars(include_children=True):
if not var._var_data:
continue
if not var._var_data.state:
continue
self._enable_state()
break
component = OverlayFragment.create(component)
meta_args = {
"title": (
uncompiled_page.title
if uncompiled_page.title is not None
else format.make_default_page_title(get_config().app_name, route)
),
"image": uncompiled_page.image,
"meta": uncompiled_page.meta,
}
if uncompiled_page.description is not None:
meta_args["description"] = uncompiled_page.description
# Add meta information to the component.
compiler_utils.add_meta(
component,
**meta_args,
) )
# Add the page. # Add the page.
@ -892,6 +855,10 @@ class App(MiddlewareMixin, LifespanMixin, Base):
self._add_optional_endpoints() self._add_optional_endpoints()
if not self._should_compile(): if not self._should_compile():
for route in self.uncompiled_pages:
if route in self.pages:
continue
self._compile_page(route)
return return
self._validate_var_dependencies() self._validate_var_dependencies()
@ -957,11 +924,6 @@ class App(MiddlewareMixin, LifespanMixin, Base):
progress.advance(task) progress.advance(task)
for route, uncompiled_page in self.uncompiled_pages.items():
ExecutorSafeFunctions.UNCOMPILED_PAGES[route] = uncompiled_page
ExecutorSafeFunctions.STYLE = self.style
# 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
@ -969,6 +931,9 @@ class App(MiddlewareMixin, LifespanMixin, Base):
platform.system() in ("Linux", "Darwin") platform.system() in ("Linux", "Darwin")
and os.environ.get("REFLEX_COMPILE_PROCESSES") is not None and os.environ.get("REFLEX_COMPILE_PROCESSES") is not None
): ):
for route in self.uncompiled_pages:
self._compile_page(route)
executor = pools.ProcessPool() executor = pools.ProcessPool()
else: else:
executor = pools.ThreadPool() executor = pools.ThreadPool()
@ -979,27 +944,40 @@ class App(MiddlewareMixin, LifespanMixin, Base):
result_futures = [] result_futures = []
pages_futures = [] pages_futures = []
# def _mark_complete(_=None):
# progress.advance(task)
def _submit_work(fn, *args, **kwargs): def _submit_work(fn, *args, **kwargs):
f = executor.apipe(fn, *args, **kwargs) f = executor.apipe(fn, *args, **kwargs)
# f.add_done_callback(_mark_complete)
result_futures.append(f) result_futures.append(f)
# Compile all page components. # Compile all page components.
for route in self.uncompiled_pages: for route, page in self.uncompiled_pages.items():
if route in self.pages:
continue
f = executor.apipe( f = executor.apipe(
ExecutorSafeFunctions.compile_uncompiled_page, route ExecutorSafeFunctions.compile_uncompiled_page,
route,
page,
self.state,
self.style,
self.theme,
) )
# f.add_done_callback(_mark_complete)
pages_futures.append((route, f)) pages_futures.append((route, f))
# Compile the pre-compiled pages.
for route, component in self.pages.items():
component._add_style_recursive(self.style, self.theme)
_submit_work(
ExecutorSafeFunctions.compile_page,
route,
component,
self.state,
)
# Compile the root stylesheet with base styles. # Compile the root stylesheet with base styles.
_submit_work(compiler.compile_root_stylesheet, self.stylesheets) _submit_work(compiler.compile_root_stylesheet, self.stylesheets)
# Compile the theme. # Compile the theme.
_submit_work(ExecutorSafeFunctions.compile_theme) _submit_work(ExecutorSafeFunctions.compile_theme, self.style)
# Compile the Tailwind config. # Compile the Tailwind config.
if config.tailwind is not None: if config.tailwind is not None:
@ -1013,18 +991,18 @@ class App(MiddlewareMixin, LifespanMixin, Base):
# Wait for all compilation tasks to complete. # Wait for all compilation tasks to complete.
for future in result_futures: for future in result_futures:
compile_results.append(future.get()) compile_results.append(future.get())
progress.advance(task)
for route, future in pages_futures: for route, future in pages_futures:
print(f"Compiled {route}")
pages_results.append(future.get()) pages_results.append(future.get())
progress.advance(task)
for route, component, compiled_page in pages_results: for route, component, compiled_page in pages_results:
self.pages[compiled_page] = component self._check_routes_conflict(route)
self.pages[route] = component
compile_results.append(compiled_page) compile_results.append(compiled_page)
# Merge the component style with the app style. for route, component in self.pages.items():
component._add_style_recursive(self.style, self.theme)
# Add component._get_all_imports() to all_imports. # Add component._get_all_imports() to all_imports.
all_imports.update(component._get_all_imports()) all_imports.update(component._get_all_imports())

View File

@ -517,9 +517,7 @@ if TYPE_CHECKING:
from reflex.event import EventHandler, EventSpec from reflex.event import EventHandler, EventSpec
def compile_uncompiled_page( def compile_uncompiled_page_helper(route: str, page: UncompiledPage) -> Component:
route: str, page: UncompiledPage
) -> tuple[EventHandler | EventSpec | list[EventHandler | EventSpec] | None, Fragment]:
"""Compiles an uncompiled page into a component and adds meta information. """Compiles an uncompiled page into a component and adds meta information.
Args: Args:
@ -527,7 +525,7 @@ def compile_uncompiled_page(
page (UncompiledPage): The uncompiled page object. page (UncompiledPage): The uncompiled page object.
Returns: Returns:
tuple[EventHandler | EventSpec | list[EventHandler | EventSpec] | None, Fragment]: The on_load event handler or spec, and the compiled component. Component: The compiled component.
""" """
# Generate the component if it is a callable. # Generate the component if it is a callable.
component = page.component component = page.component
@ -589,13 +587,10 @@ class ExecutorSafeFunctions:
""" """
UNCOMPILED_PAGES = {}
COMPILED_COMPONENTS = {}
STATE: Type[BaseState] | None = None
STYLE: ComponentStyle | None = None
@classmethod @classmethod
def compile_page(cls, route: str): def compile_page(
cls, route: str, component: Component, state: Type[BaseState]
) -> tuple[str, str]:
"""Compile a page. """Compile a page.
Args: Args:
@ -604,10 +599,17 @@ class ExecutorSafeFunctions:
Returns: Returns:
The path and code of the compiled page. The path and code of the compiled page.
""" """
return compile_page(route, cls.COMPILED_COMPONENTS[route], cls.STATE) return compile_page(route, component, state)
@classmethod @classmethod
def compile_uncompiled_page(cls, route: str): def compile_uncompiled_page(
cls,
route: str,
page: UncompiledPage,
state: Type[BaseState],
style: ComponentStyle,
theme: Component,
) -> tuple[str, Component, tuple[str, str]]:
"""Compile an uncompiled page. """Compile an uncompiled page.
Args: Args:
@ -616,12 +618,13 @@ class ExecutorSafeFunctions:
Returns: Returns:
The path and code of the compiled page. The path and code of the compiled page.
""" """
component = compile_uncompiled_page(route, cls.UNCOMPILED_PAGES[route]) component = compile_uncompiled_page_helper(route, page)
component = component if isinstance(component, Component) else component() component = component if isinstance(component, Component) else component()
return route, component, compile_page(route, component, cls.STATE) component._add_style_recursive(style, theme)
return route, component, compile_page(route, component, state)
@classmethod @classmethod
def compile_theme(cls): def compile_theme(cls, style: ComponentStyle | None) -> tuple[str, str]:
"""Compile the theme. """Compile the theme.
Returns: Returns:
@ -630,6 +633,6 @@ class ExecutorSafeFunctions:
Raises: Raises:
ValueError: If the style is not set. ValueError: If the style is not set.
""" """
if cls.STYLE is None: if style is None:
raise ValueError("STYLE should be set") raise ValueError("STYLE should be set")
return compile_theme(cls.STYLE) return compile_theme(style)