You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Introduce the foundational types for the new single-pass compiler: CompilerPlugin (protocol), CompilerHooks (dispatcher), BaseContext, PageContext, and CompileContext. These are the building blocks that all subsequent compiler work depends on.
This is part of the 2a. Single Pass Compiler roadmap item. The goal is to replace the current multi-pass compilation approach (where the component tree is walked multiple times to extract imports, hooks, custom code, dynamic imports, refs, and app wrap components separately) with a single tree walk using a plugin architecture.
Background & Motivation
Today, compiling a single page involves at least 6 separate recursive tree walks over the same component tree:
_get_all_imports() — collects imports from every component
Each of these methods in reflex/components/component.py follows the same pattern: call the non-recursive version on self, then recursively call on all children, merging results. This is wasteful — a single walk could collect all information in one pass.
Additionally, compilation logic is currently spread across reflex/app.py (the _compile() method, ~400 lines), reflex/compiler/compiler.py, and reflex/compiler/utils.py, making it hard to extend or adapt for different targets.
Design
A working demo of the proposed architecture is available as a starting point (see the CompilerPlugin protocol, CompilerHooks, BaseContext, PageContext, CompileContext classes in the demo code shared in the project brief). The key ideas:
eval_page: Evaluate a page function into a PageContext. Returns None if the plugin doesn't handle this page.
compile_page: Perform a transformation on the PageContext during page compilation.
compile_component: Two-phase async generator for component transformation. Pre-yield (descending): sees component before children are visited. Post-yield (ascending): runs after children are visited, receives (component, children) tuple.
CompilerHooks Dispatcher
Holds a tuple[CompilerPlugin, ...] and dispatches hook calls to all registered plugins in order. Plugin ordering controls priority.
Context Objects
BaseContext: Async context manager that sets itself as a ContextVar, allowing any code in the call stack to access the current context via ContextClass.get().
PageContext: Holds per-page compilation state — name, root_component, imports, module_code, etc.
CompileContext: Holds the full compilation state — list of pages, hooks, compiled results.
Acceptance Criteria
CompilerPlugin protocol is defined in a new module (e.g. reflex/compiler/plugins.py or reflex/compiler/plugin.py)
CompilerHooks dataclass is implemented with _dispatch, eval_page, compile_page, and compile_component methods
BaseContext is implemented with ContextVar-based async context management, get() classmethod, and ensure_context_attached() guard
PageContext is implemented with fields for: name, route, root_component, imports (list of ParsedImportDict), module_code (set of str), hooks (dict), dynamic_imports (set), refs (dict), app_wrap_components (dict)
CompileContext is implemented with fields for: pages, hooks (CompilerHooks), compiled_pages dict, and a compile() method that orchestrates page compilation
All new types have docstrings and type annotations
Unit tests cover: plugin dispatch ordering, context var lifecycle (enter/exit/get), PageContext field accumulation
Key Files to Understand
reflex/components/component.py — BaseComponent._get_all_* methods (lines ~1590-2002) show the current tree-walking pattern
reflex/app.py — App._compile() method (~lines 1151-1549) is the main compilation orchestrator
Notes
The context objects use ContextVar so that plugins running during compilation can access PageContext.get() or CompileContext.get() from anywhere in the call stack without explicit parameter passing.
Plugin ordering matters: e.g., a FooPlugin that transforms components must run before ConsolidateImportsPlugin so the transformed components' imports are captured.
This issue only introduces the types and basic tests. Wiring them into the actual compilation pipeline is a separate issue.
Summary
Introduce the foundational types for the new single-pass compiler:
CompilerPlugin(protocol),CompilerHooks(dispatcher),BaseContext,PageContext, andCompileContext. These are the building blocks that all subsequent compiler work depends on.This is part of the 2a. Single Pass Compiler roadmap item. The goal is to replace the current multi-pass compilation approach (where the component tree is walked multiple times to extract imports, hooks, custom code, dynamic imports, refs, and app wrap components separately) with a single tree walk using a plugin architecture.
Background & Motivation
Today, compiling a single page involves at least 6 separate recursive tree walks over the same component tree:
_get_all_imports()— collects imports from every component_get_all_hooks()— collects React hooks_get_all_custom_code()— collects custom JS code snippets_get_all_dynamic_imports()— collects dynamic import statements_get_all_refs()— collects refs_get_all_app_wrap_components()— collects app wrapper componentsEach of these methods in
reflex/components/component.pyfollows the same pattern: call the non-recursive version on self, then recursively call on all children, merging results. This is wasteful — a single walk could collect all information in one pass.Additionally, compilation logic is currently spread across
reflex/app.py(the_compile()method, ~400 lines),reflex/compiler/compiler.py, andreflex/compiler/utils.py, making it hard to extend or adapt for different targets.Design
A working demo of the proposed architecture is available as a starting point (see the
CompilerPluginprotocol,CompilerHooks,BaseContext,PageContext,CompileContextclasses in the demo code shared in the project brief). The key ideas:CompilerPluginProtocoleval_page: Evaluate a page function into aPageContext. ReturnsNoneif the plugin doesn't handle this page.compile_page: Perform a transformation on thePageContextduring page compilation.compile_component: Two-phase async generator for component transformation. Pre-yield (descending): sees component before children are visited. Post-yield (ascending): runs after children are visited, receives(component, children)tuple.CompilerHooksDispatcherHolds a
tuple[CompilerPlugin, ...]and dispatches hook calls to all registered plugins in order. Plugin ordering controls priority.Context Objects
BaseContext: Async context manager that sets itself as aContextVar, allowing any code in the call stack to access the current context viaContextClass.get().PageContext: Holds per-page compilation state —name,root_component,imports,module_code, etc.CompileContext: Holds the full compilation state — list of pages, hooks, compiled results.Acceptance Criteria
CompilerPluginprotocol is defined in a new module (e.g.reflex/compiler/plugins.pyorreflex/compiler/plugin.py)CompilerHooksdataclass is implemented with_dispatch,eval_page,compile_page, andcompile_componentmethodsBaseContextis implemented withContextVar-based async context management,get()classmethod, andensure_context_attached()guardPageContextis implemented with fields for:name,route,root_component,imports(list ofParsedImportDict),module_code(set of str),hooks(dict),dynamic_imports(set),refs(dict),app_wrap_components(dict)CompileContextis implemented with fields for:pages,hooks(CompilerHooks),compiled_pagesdict, and acompile()method that orchestrates page compilationKey Files to Understand
reflex/components/component.py—BaseComponent._get_all_*methods (lines ~1590-2002) show the current tree-walking patternreflex/compiler/compiler.py—_compile_page(),compile_stateful_components(),_get_shared_components_recursive()reflex/app.py—App._compile()method (~lines 1151-1549) is the main compilation orchestratorNotes
ContextVarso that plugins running during compilation can accessPageContext.get()orCompileContext.get()from anywhere in the call stack without explicit parameter passing.FooPluginthat transforms components must run beforeConsolidateImportsPluginso the transformed components' imports are captured.