fix(javascript): resolve Vite build-output preload dependencies (#7745)

* fix(javascript): resolve Vite build-output preload dependencies

With an absolute base, Vite writes __vite__mapDeps entries relative to the
build output directory (for example assets/chunk.js), and its preload helper
prefixes the configured base at runtime. REA emitted these entries as
dynamic-import module specifiers, so a scheme-less path without a leading
dot resolved as a bare package and every entry was reported as an
unresolved "external" module, even when the build output directory was
selected.

The static analyzer now marks such entries with a vite-output: specifier,
as Webpack chunk loads are marked with chunk:. Entries starting with ./,
../ or /, and scheme-qualified entries, keep their previous handling. The
graph resolves a marked entry as a URL path under each ancestor directory
of the importing file; exactly one ancestor holding the file establishes
the build output directory. Otherwise the entry is not-found (or
unavailable when several ancestors match) with a limitation that names
the build output directory.

On a synthetic 7-file Vite build, the 12 preload entries that were
"external" now resolve when the build output directory is selected, and
are not-found with that guidance when only assets/ is selected.

* fix(javascript): keep unanalyzed Vite output candidates

resolveViteOutputDependency considered only resolved ancestors. A file
that exists under an ancestor but was not analyzed (for example
dist/assets/lazy.css for the entry assets/lazy.css) resolves there as
unavailable, and that result was replaced by the root's not-found with a
limitation saying that no ancestor holds the file.

Keep resolved and unavailable ancestor candidates together. One candidate
is returned with its own path and limitations; several are reported as
unavailable with every candidate path and limitation. Only when no
ancestor can hold the file does the root's not-found result stand.
This commit is contained in:
JG Tromp authored and GitHub committed 2026-10-28 19:46:45 +08:00
1 parent 33698e6b31
commit 7aa943c14b
6 files changed
+275 -35

No files matched your search

@@ -7,7 +7,11 @@ import { isIntrinsicMapLookup } from "./javascriptSemanticMapOperations.js";
import { traverseJavaScriptValueAst } from "./javascriptSemanticTraversal.js";
import { semanticIdentifierRole } from "./javascriptSemanticProjection.js";
import { htmlUrlText, stripQueryAndFragment } from "../artifactPathSyntax.js";
import {
hasScheme,
htmlUrlText,
stripQueryAndFragment,
} from "../artifactPathSyntax.js";
import { sanitizeEndpointCandidate } from "../browserObservation.js";
import {
addFindingOnce,
@@ -36,6 +40,7 @@ import type {
JavaScriptFindingContext as FindingContext,
JavaScriptReferenceInput as ReferenceInput,
} from "./javascriptStaticAnalysisState.js";
import { VITE_OUTPUT_DEPENDENCY_PREFIX } from "./javascriptStaticAnalysisTypes.js";
import {
resolveSemanticBindingState,
semanticStableInitializer,
@@ -392,10 +397,26 @@ const vitePreloadDependencySpecifiers = (
return indexes.map((element) => {
if (element === null || !t.isNumericLiteral(element)) return undefined;
const index = element.value;
return Number.isSafeInteger(index) && index >= 0 ? table[index] : undefined;
return Number.isSafeInteger(index) && index >= 0
? viteDependencySpecifier(table[index])
: undefined;
});
};
// A relative base writes importer-relative `./` paths, which remain module
// URLs. Without a scheme or leading dot or slash, the entry names a file under
// the build output directory.
const viteDependencySpecifier = (
dependency: string | undefined,
): string | undefined =>
dependency === undefined ||
dependency === "" ||
dependency.startsWith(".") ||
dependency.startsWith("/") ||
hasScheme(dependency)
? dependency
: `${VITE_OUTPUT_DEPENDENCY_PREFIX}${dependency}`;
const isExplicitDefaultArgument = (
argument: t.CallExpression["arguments"][number],
bindings: JavaScriptSemanticAnalysisState,