@@ -7,16 +7,14 @@ import-protection core in `src/import-protection/INTERNALS.md`.
77
88Rsbuild owns:
99
10- - post-transform enforcement through ` api.transform({ order: ' post' }) `
10+ - post-transform enforcement through a Rspack post-loader
1111- virtual-module transport through ` VirtualModulesPlugin `
1212- compilation-truth reporting in ` processAssets `
1313- final graph reconstruction from Rspack compilation data
14- - the small build-only deferred queue for file violations that can disappear
15- from the compiled graph
1614
17- Shared AST analysis, rewrite logic, source extraction, usage lookup, source
18- locations, trace formatting, and mock code generation are described in the
19- shared internals doc.
15+ Shared transform-time AST analysis, rewrite logic, source extraction, usage
16+ lookup, source locations, trace formatting, and mock code generation are
17+ described in the shared internals doc.
2018
2119## Mental Model
2220
@@ -36,25 +34,23 @@ object:
36341 . ` onBeforeBuild `
37352 . ` onBeforeDevCompile `
38363 . ` modifyRspackConfig `
39- 4 . ` transform(..., { order: 'post' }) `
40- 5 . ` processAssets(..., { stage: 'report' }) `
37+ 4 . ` processAssets(..., { stage: 'report' }) `
38+
39+ ` modifyRspackConfig ` installs both the virtual-modules plugin and the
40+ environment-scoped import-protection post-loader.
4141
4242## State Model
4343
4444Per environment, Rsbuild keeps a smaller runtime state than Vite:
4545
4646- ` resolveCache `
4747- ` seenViolations `
48- - ` buildTransformResults `
49- - ` deferredFileViolations `
50- - ` deferredFileViolationKeys `
5148
52- Shared state is for virtual module transport and compiler fs access :
49+ Shared adapter state contains :
5350
5451- ` virtualModules `
5552- ` vmPlugins `
5653- ` readyVmPlugins `
57- - ` inputFileSystems `
5854- ` pendingWrites `
5955
6056Notably absent compared to Vite:
@@ -66,7 +62,10 @@ Notably absent compared to Vite:
6662
6763## Transform Phase
6864
69- Rsbuild enforcement runs after the Start compiler in a ` post ` transform.
65+ Rsbuild enforcement runs after the Start compiler in a Rspack loader with
66+ ` enforce: 'post' ` . The complete transform pipeline lives in
67+ ` import-protection-loader.ts ` ; mutable configuration and per-environment state
68+ are passed through loader options.
7069
7170That matters because many compiler-safe imports are already stripped by the time
7271import protection runs. This naturally suppresses a large class of false
@@ -76,10 +75,13 @@ The transform phase is responsible for:
7675
7776- self-denial for forbidden files
7877- self-denial for marker-protected files in the wrong environment
78+ - persisting detected marker metadata in Rspack ` module.buildInfo `
7979- direct specifier rewrites to mock-edge modules
80- - build-time transformed/original source preloading for later diagnostics
81- - recording build-only deferred file violations when original unsafe usage may
82- outlive a direct compiled graph edge
80+
81+ The transform treats the code it receives as authoritative. It does not read,
82+ parse, or analyze original source. Imports removed by the Start compiler are no
83+ longer part of this phase; imports with unsafe client/server usage remain in the
84+ transformed code and are checked normally.
8385
8486## Virtual Module Transport
8587
@@ -106,57 +108,89 @@ adapter queues them and flushes during compilation setup.
106108
107109It reconstructs the final view of the compilation from Rspack data by:
108110
109- 1 . building a ` TransformResultProvider ` from ` compilation.modules `
110- 2 . rebuilding the active compilation graph from outgoing connections
111- 3 . reconstructing surviving specifier violations from compiled mock-edge files
112- 4 . reporting live file violations from active edges
113- 5 . reporting live marker violations from active edges plus original source
114- 6 . reporting deferred file violations only when both importer and target truly
115- survived compilation
111+ 1 . snapshotting each module and its outgoing connections
112+ 2 . collecting specifier and file violations plus possible marker modules
113+ 3 . deduplicating marker modules and validating their persisted metadata
114+ 4 . returning early when no violations remain
115+ 5 . building the ` ImportGraph ` and diagnostic indexes only when needed
116116
117- This is the core Rsbuild-native replacement for Vite's ` generateBundle `
118- verification plus dev pending-violation flow.
117+ Each ` RspackModuleGraphNode ` stores a module and its imported modules. Missing and
118+ errored target modules are skipped.
119+ Connections are not filtered by ` getActiveState() ` because inactive connections
120+ can still carry diagnostic evidence. Duplicate connections to the same target
121+ module collapse to one.
122+
123+ Snapshotting does not apply source-file eligibility. Intermediate modules remain
124+ available for entry-to-violation traces, while the scanner applies importer and
125+ rule checks. Normalized resource ids are used for rules, traces, and diagnostics;
126+ ` resourceResolveData.path ` is preferred for original-source lookup.
127+
128+ When violations exist, the adapter replays the snapshot to build ` ImportGraph ` ;
129+ it does not query outgoing connections again. A clean compilation avoids entry
130+ traversal, graph indexes, and module-source loading.
119131
120- ## Why The Deferred Queue Is Narrow
132+ Diagnostic enrichment is lazy. The transform-result provider reads
133+ ` module.originalSource().sourceAndMap() ` when available. It gets original code
134+ from sourcemap ` sourcesContent ` , then falls back to
135+ ` compilation.inputFileSystem.readFile() ` . Results and in-flight reads are cached
136+ per module.
121137
122- Rsbuild only needs explicit build deferral for file violations whose direct edge
123- may disappear after compilation.
138+ Importer locations use this order:
124139
125- Specifier violations are rediscovered from surviving mock-edge virtual files.
126- Marker violations are rediscovered from live compiled edges.
140+ 1 . find unsafe usage in original code
141+ 2 . find unsafe usage in compiled code
142+ 3 . find the import statement in compiled, then original code
127143
128- Only file violations need extra bookkeeping when the final compiled graph can no
129- longer show the original denied edge directly.
144+ Trace edges search compiled import statements. The adapter does not use
145+ ` dependency.loc ` , which may identify a transformed declaration rather than the
146+ actual import usage.
147+
148+ This is the core Rsbuild-native replacement for Vite's ` generateBundle `
149+ verification plus dev pending-violation flow.
130150
131151## Source And Compilation APIs
132152
133153The Rsbuild adapter intentionally prefers native Rspack APIs where possible.
134154
135155Transform-time:
136156
137- - ` ctx.resource `
138- - ` ctx.context `
139- - ` ctx.resolve(...) `
140- - captured ` compiler.inputFileSystem.readFile(...) `
157+ - ` loaderContext.resource `
158+ - ` loaderContext.resourcePath `
159+ - ` loaderContext.context `
160+ - ` loaderContext.resolve(...) `
161+ - ` loaderContext._module.buildInfo `
162+
163+ ` _module ` is a deprecated Rspack loader-context API. It is used deliberately
164+ because a loader invocation is bound to one exact module instance, including
165+ its layer. Keying modules by resource would collapse distinct modules that use
166+ the same resource in different layers. Do not add a resource-map fallback.
141167
142168Compilation-time:
143169
144- - ` module.nameForCondition?.() `
145170- ` module.resourceResolveData?.resource `
146- - ` module.originalSource().sourceAndMap() `
171+ - ` module.resourceResolveData?.path `
172+ - ` module.identifier() ` (normalized fallback)
173+ - ` module.buildInfo `
174+ - ` module.originalSource().sourceAndMap() ` (confirmed diagnostics only)
147175- sourcemap ` sourcesContent `
148- - ` compilation.inputFileSystem.readFile(...) `
149-
150- This keeps the adapter closer to Rsbuild/Rspack truth and avoids falling back to
151- Node fs when the compilation already has the needed data.
176+ - ` compilation.inputFileSystem.readFile() ` (original-source fallback)
177+ - ` moduleGraph.getOutgoingConnectionsInOrder(module) `
178+ - ` connection.dependency.request `
152179
153180## Marker Handling
154181
155182Unlike Vite, Rsbuild does not introduce plugin-owned virtual marker modules for
156183normal operation.
157184
158- The real package marker files are used as source-level markers, and the adapter
159- later infers marker kind from original source while reporting compiled edges.
185+ The real package marker files are source-level markers. The post-loader writes
186+ ` { kind, source } ` directly to its current ` _module.buildInfo ` before replacing
187+ a wrong-environment module. The metadata therefore stays attached to the exact
188+ resource-and-layer module and survives self-denial mocking and persistent-cache
189+ restores.
190+
191+ ` processAssets ` treats non-excluded, non-file-denied imports as possible marker
192+ modules, then checks their ` buildInfo ` . It does not infer marker kind from final
193+ dependency requests.
160194
161195## Practical Maintainer Rule
162196
0 commit comments