Perf Optimization: use the imports we already calculate during compile

Instead of augmenting _get_imports with a weird, slow, recursive crawl and
dictionary reconstruction, just use the imports that we compile into the
components.js file to install frontend packages needed by the custom
components.

Same effect, but adds essentially zero overhead to the compilation.
This commit is contained in:
Masen Furer 2024-03-14 20:18:27 -07:00
parent 67624b48df
commit e13ff71c2c
No known key found for this signature in database
GPG Key ID: 2AE2BD5531FF94F4
4 changed files with 41 additions and 40 deletions

View File

@ -818,6 +818,7 @@ class App(Base):
compile_results.append((stateful_components_path, stateful_components_code))
result_futures = []
custom_components_future = None
def submit_work(fn, *args, **kwargs):
"""Submit work to the thread pool and add a callback to mark the task as complete.
@ -847,7 +848,10 @@ class App(Base):
submit_work(compiler.compile_app, app_root)
# Compile the custom components.
submit_work(compiler.compile_components, custom_components)
custom_components_future = thread_pool.submit(
compiler.compile_components, custom_components
)
custom_components_future.add_done_callback(mark_complete)
# Compile the root stylesheet with base styles.
submit_work(compiler.compile_root_stylesheet, self.stylesheets)
@ -878,14 +882,15 @@ class App(Base):
# Get imports from AppWrap components.
all_imports.update(app_root.get_imports())
# Iterate through all the custom components and add their imports to the all_imports.
for component in custom_components:
all_imports.update(component.get_imports())
# Wait for all compilation tasks to complete.
for future in concurrent.futures.as_completed(result_futures):
compile_results.append(future.result())
# Iterate through all the custom components and add their imports to the all_imports.
custom_components_result = custom_components_future.result()
compile_results.append(custom_components_result[:2])
all_imports.update(custom_components_result[2])
# Empty the .web pages directory.
compiler.purge_web_pages_dir()

View File

@ -186,7 +186,9 @@ def _compile_component(component: Component) -> str:
return templates.COMPONENT.render(component=component)
def _compile_components(components: set[CustomComponent]) -> str:
def _compile_components(
components: set[CustomComponent],
) -> tuple[str, Dict[str, list[ImportVar]]]:
"""Compile the components.
Args:
@ -208,9 +210,12 @@ def _compile_components(components: set[CustomComponent]) -> str:
imports = utils.merge_imports(imports, component_imports)
# Compile the components page.
return templates.COMPONENTS.render(
imports=utils.compile_imports(imports),
components=component_renders,
return (
templates.COMPONENTS.render(
imports=utils.compile_imports(imports),
components=component_renders,
),
imports,
)
@ -401,7 +406,9 @@ def compile_page(
return output_path, code
def compile_components(components: set[CustomComponent]):
def compile_components(
components: set[CustomComponent],
) -> tuple[str, str, Dict[str, list[ImportVar]]]:
"""Compile the custom components.
Args:
@ -414,8 +421,8 @@ def compile_components(components: set[CustomComponent]):
output_path = utils.get_components_path()
# Compile the components.
code = _compile_components(components)
return output_path, code
code, imports = _compile_components(components)
return output_path, code, imports
def compile_stateful_components(

View File

@ -1339,31 +1339,6 @@ class CustomComponent(Component):
"""
return hash(self.tag)
def _get_imports(self) -> Dict[str, List[ImportVar]]:
"""Get the imports for the component.
This is needed because otherwise the imports for the component are not
installed during compile time, but they are rendered into the page.
Returns:
The imports for the component and any custom component props.
"""
return imports.merge_imports(
super()._get_imports(),
# Sweep up any imports from CustomComponent props for frontend installation.
{
library: [
ImportVar(
tag=None,
render=False,
install=any(imp.install for imp in imps),
),
]
for comp in self.get_custom_components()
for library, imps in comp.get_component(comp).get_imports().items()
},
)
@classmethod
def get_props(cls) -> Set[str]:
"""Get the props for the component.

View File

@ -4,6 +4,7 @@ import pytest
import reflex as rx
from reflex.base import Base
from reflex.compiler.compiler import compile_components
from reflex.components.base.bare import Bare
from reflex.components.chakra.layout.box import Box
from reflex.components.component import (
@ -1289,8 +1290,21 @@ def test_custom_component_get_imports():
return Other.create(c)
custom_comp = wrapper()
assert "inner" in custom_comp.get_imports()
# Inner is not imported directly, but it is imported by the custom component.
assert "inner" not in custom_comp.get_imports()
# The imports are only resolved during compilation.
_, _, imports_inner = compile_components(custom_comp.get_custom_components())
assert "inner" in imports_inner
outer_comp = outer(c=wrapper())
assert "inner" in outer_comp.get_imports()
assert "other" in outer_comp.get_imports()
# Libraries are not imported directly, but are imported by the custom component.
assert "inner" not in outer_comp.get_imports()
assert "other" not in outer_comp.get_imports()
# The imports are only resolved during compilation.
_, _, imports_outer = compile_components(outer_comp.get_custom_components())
assert "inner" in imports_outer
assert "other" in imports_outer