Summary
Add a module_code field to VarData so that Vars can declare custom JavaScript code that needs to be present at the module level of the page (or component file) where the Var is rendered. The compiler will pick up module_code from Vars via the components those Vars are attached to.
This is the Var-level equivalent of Component._get_custom_code(), but with a clearer name. The long-term goal is to standardize on module_code throughout the codebase and eventually deprecate the custom_code naming.
Background
Current state: custom_code on Component
Today, custom JavaScript snippets that need to be at the module level are handled via Component._get_custom_code():
class MyComponent(rx.Component):
def _get_custom_code(self) -> str | None:
return "console.log('This runs at module level');"
These are collected recursively via _get_all_custom_code() and passed to the page template as custom_codes=....
What's missing: Vars can't contribute module code
VarData currently has these fields:
@dataclasses.dataclass(eq=True, frozen=True)
class VarData:
state: str = ""
field_name: str = ""
imports: ParsedImportTuple = ()
hooks: tuple[str, ...] = ()
deps: tuple[Var, ...] = ()
position: Hooks.HookPosition | None = None
components: tuple[BaseComponent, ...] = ()
Vars can declare imports and hooks, but they cannot declare module-level code. This means if a Var needs a module-level helper function or constant, it must be handled indirectly through a Component — even when the Var itself is the thing that needs it.
Proposed Change
Add a module_code field to VarData:
@dataclasses.dataclass(eq=True, frozen=True)
class VarData:
state: str = ""
field_name: str = ""
imports: ParsedImportTuple = ()
hooks: tuple[str, ...] = ()
deps: tuple[Var, ...] = ()
position: Hooks.HookPosition | None = None
components: tuple[BaseComponent, ...] = ()
module_code: tuple[str, ...] = () # NEW
Compiler integration
The compiler (or ConsolidateModuleCodePlugin in the new single-pass architecture) should collect module_code from Vars during compilation. Since Vars are attached to components (via props, event handlers, children), the component tree walk already visits them. The collection path is:
- For each component, examine its Var-containing fields (props, event handlers, style, children that are Vars)
- For each Var, check
_var_data.module_code
- Add any non-empty module code to the page's module code set
This naturally integrates with the existing _get_all_custom_code() flow (or its plugin replacement). The module code from Vars and from Components should be merged into the same output set — they both end up as top-level JS in the page file.
VarData.merge() update
The VarData.merge() static method needs to handle the new field:
@staticmethod
def merge(*others: VarData | None) -> VarData | None:
...
module_code = tuple({mc for od in others if od for mc in od.module_code})
...
Naming: module_code vs custom_code
The existing codebase uses custom_code for the same concept on Components. This name is confusing because "custom code" could mean many things. module_code is more precise — it's code that goes at the JS module level (top of the file, outside any component function).
This issue introduces module_code on VarData. A follow-up task (not in scope here) would be to rename _get_custom_code() / _get_all_custom_code() on Component to _get_module_code() / _get_all_module_code() and standardize the naming throughout.
Acceptance Criteria
Key Files
reflex/vars/base.py:
VarData dataclass (~line 99) — add the field
VarData.merge() — update merging logic
reflex/components/component.py:
_get_all_custom_code() (~line 1590) — should also collect from Vars (or a new method should)
_get_vars() / _get_vars_from_props() — how Vars are currently extracted from components
reflex/compiler/compiler.py:
_compile_page() — where custom_codes are passed to the template
reflex/compiler/templates.py:
page_template() — receives custom_codes parameter
Notes
- The
module_code tuple uses strings (like hooks), deduplicated by set membership. Order shouldn't matter for module-level code.
- This is a prerequisite for the app_wraps-on-VarData work, where Vars need to declare providers and context code at the app level.
- The frozen dataclass pattern is maintained —
module_code is a tuple, not a list.
Summary
Add a
module_codefield toVarDataso that Vars can declare custom JavaScript code that needs to be present at the module level of the page (or component file) where the Var is rendered. The compiler will pick upmodule_codefrom Vars via the components those Vars are attached to.This is the Var-level equivalent of
Component._get_custom_code(), but with a clearer name. The long-term goal is to standardize onmodule_codethroughout the codebase and eventually deprecate thecustom_codenaming.Background
Current state:
custom_codeon ComponentToday, custom JavaScript snippets that need to be at the module level are handled via
Component._get_custom_code():These are collected recursively via
_get_all_custom_code()and passed to the page template ascustom_codes=....What's missing: Vars can't contribute module code
VarDatacurrently has these fields:Vars can declare
importsandhooks, but they cannot declare module-level code. This means if a Var needs a module-level helper function or constant, it must be handled indirectly through a Component — even when the Var itself is the thing that needs it.Proposed Change
Add a
module_codefield toVarData:Compiler integration
The compiler (or
ConsolidateModuleCodePluginin the new single-pass architecture) should collectmodule_codefrom Vars during compilation. Since Vars are attached to components (via props, event handlers, children), the component tree walk already visits them. The collection path is:_var_data.module_codeThis naturally integrates with the existing
_get_all_custom_code()flow (or its plugin replacement). The module code from Vars and from Components should be merged into the same output set — they both end up as top-level JS in the page file.VarData.merge() update
The
VarData.merge()static method needs to handle the new field:Naming:
module_codevscustom_codeThe existing codebase uses
custom_codefor the same concept on Components. This name is confusing because "custom code" could mean many things.module_codeis more precise — it's code that goes at the JS module level (top of the file, outside any component function).This issue introduces
module_codeonVarData. A follow-up task (not in scope here) would be to rename_get_custom_code()/_get_all_custom_code()on Component to_get_module_code()/_get_all_module_code()and standardize the naming throughout.Acceptance Criteria
VarDatahas amodule_code: tuple[str, ...]field (default empty tuple)VarData.merge()correctly combinesmodule_codefrom multiple VarData instances (deduplicating)VarData.__bool__/ truthiness accounts for the new fieldmodule_codefrom Vars encountered during page compilationmodule_codeattached to a component results in that code appearing in the compiled pageKey Files
reflex/vars/base.py:VarDatadataclass (~line 99) — add the fieldVarData.merge()— update merging logicreflex/components/component.py:_get_all_custom_code()(~line 1590) — should also collect from Vars (or a new method should)_get_vars()/_get_vars_from_props()— how Vars are currently extracted from componentsreflex/compiler/compiler.py:_compile_page()— wherecustom_codesare passed to the templatereflex/compiler/templates.py:page_template()— receivescustom_codesparameterNotes
module_codetuple uses strings (likehooks), deduplicated by set membership. Order shouldn't matter for module-level code.module_codeis a tuple, not a list.