Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b556abc
perf(start): avoid parsing original source in Rsbuild import protection
SyMind Aug 25, 2026
0afe53d
perf(start): avoid filesystem reads in Rsbuild import protection repo…
SyMind Aug 25, 2026
44d4b50
perf(start): remove redundant module survival checks
SyMind Aug 25, 2026
5f04bc2
perf(start): ignore inactive Rspack import connections
SyMind Aug 25, 2026
e2dc01e
perf(start): lazily build Rsbuild import protection diagnostics
SyMind Aug 26, 2026
fa92944
chore: remove test case
SyMind Aug 26, 2026
85b8228
add changeset
SyMind Aug 26, 2026
8349037
fix(start): persist Rsbuild import protection markers in buildInfo
SyMind Aug 26, 2026
f44361c
fix: normalize file path in getModuleResource
SyMind Aug 26, 2026
eeff6b2
fix(start): restore Rsbuild diagnostics for inactive imports
SyMind Aug 26, 2026
16964a0
fix(start): improve Rsbuild import protection diagnostics
SyMind Aug 26, 2026
cbc89b0
refactor(start): clarify Rsbuild violation scanner state
SyMind Aug 26, 2026
fd583b7
update readme
SyMind Aug 27, 2026
073afc3
fix(start-plugin-core): avoid dependency locations in Rsbuild diagnos…
SyMind Aug 27, 2026
b55ce55
fix(start): respect importer scope for Rsbuild markers
SyMind Aug 27, 2026
2ec986e
refactor(start): move Rsbuild import protection transform to loader
SyMind Aug 27, 2026
22bd841
test(start): type Rsbuild import protection mocks
SyMind Aug 27, 2026
20065a8
fix(start): close Rsbuild import protection reporting gaps
SyMind Aug 27, 2026
fb379da
fix(start): skip errored Rsbuild import protection modules
SyMind Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/lazy-rspack-guards.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/start-plugin-core': patch
---

Improve Rsbuild import protection performance by scanning the compilation graph once and deferring diagnostic work until a violation is found.
103 changes: 65 additions & 38 deletions packages/start-plugin-core/src/rsbuild/INTERNALS-import-protection.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ Rsbuild owns:
- virtual-module transport through `VirtualModulesPlugin`
- compilation-truth reporting in `processAssets`
- final graph reconstruction from Rspack compilation data
- the small build-only deferred queue for file violations that can disappear
from the compiled graph

Shared AST analysis, rewrite logic, source extraction, usage lookup, source
locations, trace formatting, and mock code generation are described in the
shared internals doc.
Shared transform-time AST analysis, rewrite logic, source extraction, usage
lookup, source locations, trace formatting, and mock code generation are
described in the shared internals doc.

## Mental Model

Expand Down Expand Up @@ -45,17 +43,18 @@ Per environment, Rsbuild keeps a smaller runtime state than Vite:

- `resolveCache`
- `seenViolations`
- `buildTransformResults`
- `deferredFileViolations`
- `deferredFileViolationKeys`

Shared state is for virtual module transport and compiler fs access:
Shared adapter state contains:

- `virtualModules`
- `vmPlugins`
- `readyVmPlugins`
- `inputFileSystems`
- `pendingWrites`
- `moduleByResource`

`moduleByResource` associates each loader resource with its Rspack module. The
loader hook populates it, the matching post transform consumes it, and durable
marker metadata lives on `module.buildInfo`.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

Notably absent compared to Vite:

Expand All @@ -76,10 +75,13 @@ The transform phase is responsible for:

- self-denial for forbidden files
- self-denial for marker-protected files in the wrong environment
- persisting detected marker metadata in Rspack `module.buildInfo`
- direct specifier rewrites to mock-edge modules
- build-time transformed/original source preloading for later diagnostics
- recording build-only deferred file violations when original unsafe usage may
outlive a direct compiled graph edge

The transform treats the code it receives as authoritative. It does not read,
parse, or analyze original source. Imports removed by the Start compiler are no
longer part of this phase; imports with unsafe client/server usage remain in the
transformed code and are checked normally.

## Virtual Module Transport

Expand All @@ -106,27 +108,45 @@ adapter queues them and flushes during compilation setup.

It reconstructs the final view of the compilation from Rspack data by:

1. building a `TransformResultProvider` from `compilation.modules`
2. rebuilding the active compilation graph from outgoing connections
3. reconstructing surviving specifier violations from compiled mock-edge files
4. reporting live file violations from active edges
5. reporting live marker violations from active edges plus original source
6. reporting deferred file violations only when both importer and target truly
survived compilation
1. snapshotting each module and its outgoing connections
2. collecting specifier and file violations plus possible marker modules
3. deduplicating marker modules and validating their persisted metadata
4. returning early when no violations remain
5. building the `ImportGraph` and diagnostic indexes only when needed

This is the core Rsbuild-native replacement for Vite's `generateBundle`
verification plus dev pending-violation flow.
Each `RspackModuleGraphNode` stores a module and its imported modules. Missing and
errored target modules are skipped.
Connections are not filtered by `getActiveState()` because inactive connections
can still carry diagnostic evidence. Duplicate connections to the same target
module collapse to one.

Snapshotting does not apply source-file eligibility. Intermediate modules remain
available for entry-to-violation traces, while the scanner applies importer and
rule checks. Normalized resource ids are used for rules, traces, and diagnostics;
`resourceResolveData.path` is preferred for original-source lookup.

When violations exist, the adapter replays the snapshot to build `ImportGraph`;
it does not query outgoing connections again. A clean compilation avoids entry
traversal, graph indexes, and module-source loading.

## Why The Deferred Queue Is Narrow
Diagnostic enrichment is lazy. The transform-result provider reads
`module.originalSource().sourceAndMap()` when available. It gets original code
from sourcemap `sourcesContent`, then falls back to
`compilation.inputFileSystem.readFile()`. Results and in-flight reads are cached
per module.

Rsbuild only needs explicit build deferral for file violations whose direct edge
may disappear after compilation.
Importer locations use this order:

Specifier violations are rediscovered from surviving mock-edge virtual files.
Marker violations are rediscovered from live compiled edges.
1. find unsafe usage in original code
2. find unsafe usage in compiled code
3. find the import statement in compiled, then original code

Only file violations need extra bookkeeping when the final compiled graph can no
longer show the original denied edge directly.
Trace edges search compiled import statements. The adapter does not use
`dependency.loc`, which may identify a transformed declaration rather than the
actual import usage.

This is the core Rsbuild-native replacement for Vite's `generateBundle`
verification plus dev pending-violation flow.

## Source And Compilation APIs

Expand All @@ -137,26 +157,33 @@ Transform-time:
- `ctx.resource`
- `ctx.context`
- `ctx.resolve(...)`
- captured `compiler.inputFileSystem.readFile(...)`

Compilation-time:

- `module.nameForCondition?.()`
- `module.resourceResolveData?.resource`
- `module.originalSource().sourceAndMap()`
- `module.resourceResolveData?.path`
- `module.identifier()` (normalized fallback)
- `module.buildInfo`
- `module.originalSource().sourceAndMap()` (confirmed diagnostics only)
- sourcemap `sourcesContent`
- `compilation.inputFileSystem.readFile(...)`

This keeps the adapter closer to Rsbuild/Rspack truth and avoids falling back to
Node fs when the compilation already has the needed data.
- `compilation.inputFileSystem.readFile()` (original-source fallback)
- `moduleGraph.getOutgoingConnectionsInOrder(module)`
- `connection.dependency.request`

## Marker Handling

Unlike Vite, Rsbuild does not introduce plugin-owned virtual marker modules for
normal operation.

The real package marker files are used as source-level markers, and the adapter
later infers marker kind from original source while reporting compiled edges.
The real package marker files are source-level markers. Rspack's loader hook
records the module under the exact loader resource. The matching post transform
writes `{ kind, source }` to `module.buildInfo` before replacing a
wrong-environment module. The metadata survives self-denial mocking and
persistent-cache restores.

`processAssets` treats non-excluded, non-file-denied imports as possible marker
modules, then checks their `buildInfo`. It does not infer marker kind from final
dependency requests.

## Practical Maintainer Rule

Expand Down
Loading