Skip to content

Commit 37877da

Browse files
authored
perf(start): optimize Rsbuild import protection reporting (#8164)
* perf(start): avoid parsing original source in Rsbuild import protection * perf(start): avoid filesystem reads in Rsbuild import protection reporting * perf(start): remove redundant module survival checks * perf(start): ignore inactive Rspack import connections * perf(start): lazily build Rsbuild import protection diagnostics * chore: remove test case * add changeset * fix(start): persist Rsbuild import protection markers in buildInfo * fix: normalize file path in getModuleResource * fix(start): restore Rsbuild diagnostics for inactive imports * fix(start): improve Rsbuild import protection diagnostics * refactor(start): clarify Rsbuild violation scanner state * update readme * fix(start-plugin-core): avoid dependency locations in Rsbuild diagnostics * fix(start): respect importer scope for Rsbuild markers * refactor(start): move Rsbuild import protection transform to loader * test(start): type Rsbuild import protection mocks * fix(start): close Rsbuild import protection reporting gaps * fix(start): skip errored Rsbuild import protection modules
1 parent 65d565c commit 37877da

8 files changed

Lines changed: 1567 additions & 1066 deletions

File tree

.changeset/lazy-rspack-guards.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/start-plugin-core': patch
3+
---
4+
5+
Improve Rsbuild import protection performance by scanning the compilation graph once and deferring diagnostic work until a violation is found.

packages/start-plugin-core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
"@babel/code-frame": "7.27.1",
8989
"@babel/core": "^7.28.5",
9090
"@babel/types": "^7.28.5",
91+
"@jridgewell/remapping": "^2.3.5",
9192
"@tanstack/router-core": "workspace:*",
9293
"@tanstack/router-generator": "workspace:*",
9394
"@tanstack/router-plugin": "workspace:*",

packages/start-plugin-core/src/rsbuild/INTERNALS-import-protection.md

Lines changed: 79 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,14 @@ import-protection core in `src/import-protection/INTERNALS.md`.
77

88
Rsbuild 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:
3634
1. `onBeforeBuild`
3735
2. `onBeforeDevCompile`
3836
3. `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

4444
Per 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

6056
Notably 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

7170
That matters because many compiler-safe imports are already stripped by the time
7271
import 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

107109
It 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

133153
The Rsbuild adapter intentionally prefers native Rspack APIs where possible.
134154

135155
Transform-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

142168
Compilation-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

155182
Unlike Vite, Rsbuild does not introduce plugin-owned virtual marker modules for
156183
normal 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

Comments
 (0)