mirror of
https://github.com/cloudflare/vinext.git
synced 2026-05-09 08:25:34 +02:00
[GH-ISSUE #863] Replace regex-based __VINEXT_CLASS stub patching in generateBundle #187
Labels
No labels
enhancement
enhancement
good first issue
help wanted
nextjs-tracking
nextjs-tracking
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/vinext#187
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @NathanDrake2406 on GitHub (Apr 20, 2026).
Original GitHub issue: https://github.com/cloudflare/vinext/issues/863
Raised by @james-elicx on #842: https://github.com/cloudflare/vinext/pull/842#discussion_r3109733377
What's in place today
packages/vinext/src/index.tsgenerateBundlepatches the generated RSC entry by:/function __VINEXT_CLASS\(routeIdx\)\s*\{\s*return null;?\s*\}/.target.codevia string replace with a switch-statement dispatch table.target.mapbecause the patched body is longer than the stub.#843 extends this same pattern by adding a second sibling stub
__VINEXT_CLASS_REASONSbehindVINEXT_DEBUG_CLASSIFICATION, gated by an independent env check. Each new build-time dispatch table doubles the regex surface.Why it's brittle
target.codebypasses Rolldown's module graph, its sourcemap machinery, and any downstream plugins that expect immutable chunk output after the final phase.Lifecycle constraint (read this before picking an option)
The classification pipeline has two stages that land at different Rolldown lifecycle points:
loadonward.this.getModuleInfo(moduleId)against the final module graph. Only complete ingenerateBundle/renderChunk.Any option that tries to return the full Layer 1 + Layer 2 dispatch table from a virtual module's
loadhook will fail silently: the graph is not populated yet, so every layout looks "no evidence" and Layer 2 degrades to the runtime probe. This constraint rules out the naive "pure virtual module fromload" design.Options to explore
Listed least to most invasive. Option 1b is the current preferred target.
1a. Virtual-module injection, pure
load(infeasible). Replace the stub in the source template with an import fromvirtual:vinext-layout-classificationand have theloadhook return structured dispatch-table code. This sounds right but breaks on the lifecycle constraint above. Noted so future readers do not re-discover it.1b. Virtual-module injection, placeholder +
renderChunkfill (recommended). Declarevirtual:vinext-layout-classificationwithresolveId+loadreturning a typed stub such asexport function getBuildTimeClassifications() { return null; } export function getBuildTimeReasons() { return null; }. InrenderChunk, locate the chunk by its stablefacadeModuleId(we assigned it, no regex scan across chunks) and replace the body withMagicStringso sourcemaps stay correct. The RSC entry just imports the named functions. Key wins:__VINEXT_CLASS_REASONSbecomes a sibling export from the same module, gated by the same build-time flag. One schema, multiple typed exports.vm.runInThisContextregex-extraction goes away.2.
renderChunkon the RSC entry with MagicString. Keep the current shape (stub lives in the RSC entry, patched by the plugin) but move fromgenerateBundlestring replace torenderChunk+ MagicString. Preserves incremental sourcemaps and works with the bundler's abstractions. Still couples to the RSC entry's emitted text; a partial improvement, not a full fix.3. Emit the dispatch table as a separate chunk via
emitFile. HavegenerateBundlecallthis.emitFile({ type: 'chunk', id: 'virtual:vinext-layout-classification', ... })and rely on the RSC entry's existing import to resolve to the emitted chunk. Removes the stub entirely but adds a chunk boundary, a runtime module import, and lifecycle ordering concerns betweenemitFileand the importer's finalization.4. AST-based patch. Parse the chunk with
@oxc/parser(already an indirect dependency through Vite+), locate the function declaration by name, replace its body node. Heavier but immune to whitespace/formatter drift. Useful only if 1b proves infeasible for some reason not yet identified.Follow-ups affected
__VINEXT_CLASS_REASONSsibling. Option 1b lets it become a second named export of the same virtual module rather than a parallel stub + regex.Non-goals
app-page-execution.tsdoes not need to change.