packages/start-plugin-core/src/import-protection/INTERNALS.md
src/import-protection/ contains the adapter-independent core for TanStack
Start import protection.
Its current structure is:
TransformResultImportAnalysis object from that ASTThe shared layer is responsible for correctness-sensitive logic that must stay aligned between Vite and Rsbuild.
analysis.tsThe central shared analysis module.
It builds and caches ImportAnalysis on TransformResult.analysis.
ImportAnalysis contains:
It exposes both result-based and code-based helpers:
getOrCreateImportAnalysis(result)getImportSourcesFromResult(result)getImportSources(code)getImportSpecifierLocationFromResult(result, source)getMockExportNamesBySourceFromResult(result)getMockExportNamesBySource(code)getNamedExportsFromResult(result)getNamedExports(code)findPostCompileUsagePosFromResult(result, source)findPostCompileUsagePos(code, source)findOriginalUnsafeUsagePosFromResult(result, source, envType)findOriginalUnsafeUsagePos(code, source, envType)isValidExportName(name)adapterUtils.tsContains small adapter-facing shared helpers for decisions that are identical in Vite and Rsbuild.
It currently owns:
envTypeMapcompiledRulesThis keeps the adapters from duplicating the same targeting logic while still leaving lifecycle-specific behavior in the adapter implementations.
rewrite.tsContains the actual AST mutation pass for denied static imports and re-exports.
It is intentionally separate from analysis.ts.
Why:
rewriteDeniedImports() rewrites denied static import/re-export edges to mock
module imports while preserving explicit bindings expected by the importer.
sourceLocation.tsBuilds source locations and snippets from transformed code plus sourcemaps.
It reuses shared analysis for two important lookups:
It also owns:
sourcesContent original-source selectionvirtualModules.tsOwns the shared mock module generators and payload encoding.
This includes:
The adapter-specific transport of these modules is not handled here.
defaults.ts, matchers.ts, utils.ts, trace.tsThese remain shared support layers:
defaults.ts: default deny/marker rulesmatchers.ts: compiled glob/regex matcher utilitiesutils.ts: path normalization, candidate generation, messages, defer logic,
generic helperstrace.ts: import graph, traces, snippet-aware message formattingThe key cache decision is: analysis is cached per TransformResult, not
globally by source text.
That means:
The analysis object is read-only after creation, except for the internal
usageByKey memoization map.
getImportSources() is now AST-driven and returns sources in encountered order.
It includes:
import ... from 'x'export ... from 'x'export * from 'x'import('x')Adapter code that needs source extraction should call into analysis.ts
directly.
Mock-edge modules need to know which named exports the importer expects.
analysis.ts derives that from the importer AST by recording:
This produces mockExportNamesBySource, which is a safe over-approximation of
the named exports the adapter should expose on a mock-edge module.
Self-denial needs the denied file's export surface.
getNamedExports() and getNamedExportsFromResult() collect valid named
exports from:
default is excluded from the named export list because the mock generators
handle default export separately.
Usage lookup is shared across adapters through analysis.ts.
The walker:
catch paramsSafe boundary recognition is environment-specific:
createServerFn().handler(...)createMiddleware(...).server(...)createIsomorphicFn().server(...)createServerOnlyFn(...)createClientOnlyFn(...)createIsomorphicFn().client(...)This is the core shared logic that keeps both adapters aligned on cases like
boundary-safe, factory-safe, and compiler-leak.
Analysis and rewrite are intentionally separate.
Reasons:
The intended flow is:
ImportAnalysisrewriteDeniedImports()The shared layer deliberately does not decide:
Those are adapter responsibilities.
The shared layer does provide the primitives both adapters rely on:
When making future import-protection changes:
src/import-protection/.ImportAnalysis over adding new one-off parsers or wrapper
helpers.TransformResult
instead.