feat(compiler): mirror memo output paths to Python source modules#6457
feat(compiler): mirror memo output paths to Python source modules#6457FarhanAliRaza wants to merge 16 commits into
Conversation
Memos now compile into a single JSX file per user module at a path that mirrors the module's dotted name, instead of one file per memo under . The page-side import surface matches the source layout, which makes debugging easier and lets Vite group co-defined memos in the same chunk. Memos without a captured source module keep the legacy per-name files and index. A manifest in records emitted paths so stale files from previous compiles get pruned.
Merging this PR will not alter performance
Comparing Footnotes
|
Greptile SummaryThis PR changes how auto-memoized (
Confidence Score: 5/5Safe to merge. The mirrored-output path change is well-contained, the manifest-based stale-file cleanup handles both absolute and relative .web directories, and the hot-reload correctness fix directly addresses the scenarios previously observed in local testing. All three concrete bugs surfaced in prior review rounds are resolved: the relative-path double-prefix in prune_stale_memo_files is gone, the mkstemp fd leak is fixed by closing immediately and reopening by path, and the process-lifetime cached find_spec is replaced with a live sys.modules check. The (tag, source_module) registry key closes the silent-overwrite regression for identical subtrees across modules. Dedicated tests cover the relative-web-dir edge case and the cross-module deduplication scenario. No files require special attention. Important Files Changed
Reviews (4): Last reviewed commit: "chore: regenerate memo.pyi stub hash" | Re-trigger Greptile |
Identical memoizable subtrees on pages from different user modules produce the same wrapper tag. The auto-memo registry was keyed by tag alone, so the second registration overwrote the first — only one of the source modules got a mirrored memo file emitted, and the other page imported the tag from a JSX file that never declared it. Vite failed the prod build with MISSING_EXPORT. Key the registry by (tag, source_module) so each module's mirrored file gets its own definition, and add an integration test that builds two pages from distinct user modules sharing a memoizable subtree.
was d, so once a module had been resolved its mirrored path was frozen for the process. A user toggling a module between a regular and a package () during dev reload kept the original origin and emitted memo files to the stale path. Drop the cache and read from first, falling back to only when the module isn't loaded — is rebound on reload, while a cached spec wouldn't be. Also tighten to close the mkstemp fd up front so it can't leak if reopening raises, and re-export from for parity with the rest of the surface.
Memo components now mirror to their source module's combined file, so the self-referencing memo test can no longer find a per-name RecursiveBox.jsx path. Join all emitted code and assert on content.
Auto-memoized (rx.memo) components now compile to .web paths mirroring their defining Python module instead of a shared components.jsx, scoping the memo registry per module so same-named components no longer collide. Adds reflex_base.utils.memo_paths to translate source modules into the mirrored JSX path and $/... specifier.
This is net-new functionality, we don't need to provide compat shims
use more durable module/package names that are less likely to change in the future
1. the import was weird, why not just import directly? 2. the canonical location for _get_memo_component_class has moved to reflex_base
masenf
left a comment
There was a problem hiding this comment.
we need to put the modules under a subdir of .web that is not already used, maybe .web/app_components.
The problem arises when I have an app called app and a module called app.root which exports a memo component... this ends up getting overwritten by the app/root.jsx that the framework emits and then the page breaks when you try to load it.
The user's file tree hierarchy really shouldn't be able to break the app, hence keeping it under a subdirectory that is only used for emitting memo components would protect the rest of the app from unexpected changes.
Mirrored memos could compile to paths that overwrite framework output (a memo in module `app.root` would clobber `.web/app/root.jsx`), and un-mirrorable memos shared `.web/utils/components`. Move all memo output under a reserved `app_components/` tree — mirrored memos at `app_components/<segments>`, un-mirrorable ones at `app_components/_internal/<name>` — so user module paths can never collide with framework files. Add collision detection (reserved internal dir, case-insensitive path clashes) and reject Windows reserved device names so mirroring fails loudly instead of silently overwriting. Reset the memo wrapper class cache each compile so a module flipping to a package across hot reloads re-resolves its library. Move stale-file pruning after the dry-run return so `--dry` never mutates `.web` or the manifest.
The is-absolute guard double-prefixed emitted paths to `.web/.web/...` when get_web_dir() returns the default relative path, so emitted keys never matched the manifest and live files were wrongly pruned. Emitted paths already share the web_dir prefix, so strip it directly.
Memos now compile into a single JSX file per user module at a path that mirrors the module's dotted name, instead of one file per memo under . The page-side import surface matches the source
layout, which makes debugging easier and lets Vite group co-defined memos in the same chunk.
Memos without a captured source module keep the legacy per-name files and index. A manifest in records emitted
paths so stale files from previous compiles get pruned.
All Submissions:
Type of change
Please delete options that are not relevant.
New Feature Submission:
Changes To Core Features:
closes #6218
fixes ENG-9150