Summary
Implement the default set of CompilerPlugins that replicate the current multi-pass tree walking behavior as single-pass plugins. Each plugin collects one category of information during the single tree walk and stashes it in the PageContext.
Depends on ENG-9142 (compiler plugin protocol and context objects).
Background
Today, reflex/compiler/compiler.py::_compile_page() calls these tree-walking methods sequentially:
def _compile_page(component: BaseComponent) -> str:
imports = component._get_all_imports() # walk 1
_apply_common_imports(imports)
imports = utils.compile_imports(imports)
return templates.page_template(
imports=imports,
dynamic_imports=sorted(component._get_all_dynamic_imports()), # walk 2
custom_codes=component._get_all_custom_code(), # walk 3
hooks=component._get_all_hooks(), # walk 4
render=component.render(), # walk 5
)
And App._compile() does additional walks for:
component._get_all_imports() (again, for global import collection)
component._get_all_app_wrap_components() (walk 6)
Each walk traverses the entire component tree. With the plugin architecture, all of these become compile_component hooks that run during a single recursive walk.
Plugins to Implement
1. DefaultPagePlugin
eval_page: Evaluates the page function into a PageContext (equivalent to compile_unevaluated_page() in reflex/compiler/compiler.py)
compile_page: Orchestrates the single recursive tree walk by calling _compile_component_recursive()
_compile_component_recursive(): The core single-walk method that dispatches compile_component hooks for each component
Reference: See DefaultPagePlugin in the demo code. The key insight is the two-phase generator pattern — pre-yield sees the component going down, post-yield (via gen.asend((comp, children))) sees the result going back up.
2. ConsolidateImportsPlugin
Replaces Component._get_all_imports() and _apply_common_imports().
async def compile_component(self, comp):
comp, _ = yield # post-yield: get the (potentially transformed) component
if isinstance(comp, Component) and (imports := comp._get_imports()):
PageContext.get().imports.append(imports)
async def compile_page(self, page_ctx):
# After tree walk, collapse and merge all collected imports
page_ctx.imports = [collapse_imports(merge_imports(*page_ctx.imports))]
Current code: Component._get_all_imports() in component.py (~line 1739)
3. ConsolidateHooksPlugin
Replaces Component._get_all_hooks() and _get_all_hooks_internal().
async def compile_component(self, comp):
comp, _ = yield
if isinstance(comp, Component):
hooks = {}
hooks.update(comp._get_hooks_internal())
if (h := comp._get_hooks()) is not None:
hooks[h] = None
hooks.update(comp._get_added_hooks())
PageContext.get().hooks.update(hooks)
Current code: Component._get_all_hooks() in component.py (~line 1899)
4. ConsolidateCustomCodePlugin
Replaces Component._get_all_custom_code().
Current code: Component._get_all_custom_code() in component.py (~line 1590)
5. ConsolidateDynamicImportsPlugin
Replaces Component._get_all_dynamic_imports().
Current code: Component._get_all_dynamic_imports() in component.py (~line 1627)
6. ConsolidateRefsPlugin
Replaces Component._get_all_refs().
Current code: Component._get_all_refs() in component.py (~line 1933)
7. ConsolidateAppWrapPlugin
Replaces Component._get_all_app_wrap_components().
Current code: Component._get_all_app_wrap_components() in component.py (~line 1972)
8. ApplyStylePlugin
Replaces Component._add_style_recursive(). Applies styles during the descending phase (pre-yield) of the tree walk.
Current code: Component._add_style_recursive() in component.py (~line 1251)
Acceptance Criteria
Key Files
reflex/components/component.py — All _get_all_* methods and _add_style_recursive
reflex/compiler/compiler.py — _compile_page(), _apply_common_imports()
reflex/compiler/utils.py — merge_imports(), compile_imports(), collapse_imports()
Notes
- The individual
_get_imports(), _get_hooks(), _get_custom_code(), etc. (non-recursive, single-component versions) remain on the Component class. Only the _get_all_* (recursive tree-walking) versions are replaced by plugins.
- The
_get_components_in_props() call in some _get_all_* methods means those plugins need to also walk components embedded in props, not just children.
- Plugin ordering is critical. For example,
ApplyStylePlugin must run before any consolidation plugin. The ConsolidateImportsPlugin.compile_page phase must run after all compile_component hooks have finished collecting imports.
Summary
Implement the default set of
CompilerPlugins that replicate the current multi-pass tree walking behavior as single-pass plugins. Each plugin collects one category of information during the single tree walk and stashes it in thePageContext.Depends on ENG-9142 (compiler plugin protocol and context objects).
Background
Today,
reflex/compiler/compiler.py::_compile_page()calls these tree-walking methods sequentially:And
App._compile()does additional walks for:component._get_all_imports()(again, for global import collection)component._get_all_app_wrap_components()(walk 6)Each walk traverses the entire component tree. With the plugin architecture, all of these become
compile_componenthooks that run during a single recursive walk.Plugins to Implement
1.
DefaultPagePlugineval_page: Evaluates the page function into aPageContext(equivalent tocompile_unevaluated_page()inreflex/compiler/compiler.py)compile_page: Orchestrates the single recursive tree walk by calling_compile_component_recursive()_compile_component_recursive(): The core single-walk method that dispatchescompile_componenthooks for each componentReference: See
DefaultPagePluginin the demo code. The key insight is the two-phase generator pattern — pre-yield sees the component going down, post-yield (viagen.asend((comp, children))) sees the result going back up.2.
ConsolidateImportsPluginReplaces
Component._get_all_imports()and_apply_common_imports().Current code:
Component._get_all_imports()incomponent.py(~line 1739)3.
ConsolidateHooksPluginReplaces
Component._get_all_hooks()and_get_all_hooks_internal().Current code:
Component._get_all_hooks()incomponent.py(~line 1899)4.
ConsolidateCustomCodePluginReplaces
Component._get_all_custom_code().Current code:
Component._get_all_custom_code()incomponent.py(~line 1590)5.
ConsolidateDynamicImportsPluginReplaces
Component._get_all_dynamic_imports().Current code:
Component._get_all_dynamic_imports()incomponent.py(~line 1627)6.
ConsolidateRefsPluginReplaces
Component._get_all_refs().Current code:
Component._get_all_refs()incomponent.py(~line 1933)7.
ConsolidateAppWrapPluginReplaces
Component._get_all_app_wrap_components().Current code:
Component._get_all_app_wrap_components()incomponent.py(~line 1972)8.
ApplyStylePluginReplaces
Component._add_style_recursive(). Applies styles during the descending phase (pre-yield) of the tree walk.Current code:
Component._add_style_recursive()incomponent.py(~line 1251)Acceptance Criteria
CompilerPlugin_get_all_*method for the same component treePageContextdata as the current_compile_page()functionKey Files
reflex/components/component.py— All_get_all_*methods and_add_style_recursivereflex/compiler/compiler.py—_compile_page(),_apply_common_imports()reflex/compiler/utils.py—merge_imports(),compile_imports(),collapse_imports()Notes
_get_imports(),_get_hooks(),_get_custom_code(), etc. (non-recursive, single-component versions) remain on theComponentclass. Only the_get_all_*(recursive tree-walking) versions are replaced by plugins._get_components_in_props()call in some_get_all_*methods means those plugins need to also walk components embedded in props, not justchildren.ApplyStylePluginmust run before any consolidation plugin. TheConsolidateImportsPlugin.compile_pagephase must run after allcompile_componenthooks have finished collecting imports.