fix(javascript): preserve graphs with async chunk self-references (#1152)

This commit is contained in:
Ysopwnable
2026-10-09 09:37:37 +08:00
committed by GitHub
parent 84094af54a
commit 72201668ed
3 changed files with 113 additions and 10 deletions
@@ -15,10 +15,11 @@ import {
staticInferenceEvidence,
} from "./JavaScriptArtifactGraphEvidence.js";
/** Project Webpack/Rspack chunk and factory literals recovered from AST. */
/** Project Webpack/Rspack AST literals and disclose unrepresentable relationships. */
export const addJavaScriptBundlerNodes = (
context: JavaScriptArtifactGraphContext,
): void => {
): readonly string[] => {
let selfAsyncChunks = 0;
for (const analyzed of context.analysis.files) {
const { file, javascript } = analyzed;
const asset = context.assetNodes.get(file.path);
@@ -49,8 +50,14 @@ export const addJavaScriptBundlerNodes = (
records.push({ file, registration, chunk, coverage });
addBundlerModuleNodes(context, { ...input, chunk });
}
for (const record of records) addBundlerRuntimeEdges(context, record);
for (const record of records)
selfAsyncChunks += addBundlerRuntimeEdges(context, record);
}
return selfAsyncChunks === 0
? []
: [
`${String(selfAsyncChunks)} bundler async-chunk ${selfAsyncChunks === 1 ? "reference" : "references"} resolved back to the requesting chunk itself and ${selfAsyncChunks === 1 ? "was" : "were"} omitted; application graph edges cannot be self-referential.`,
];
};
interface BundlerProjectionInput {
@@ -177,7 +184,8 @@ interface BundlerGraphRecord {
const addBundlerRuntimeEdges = (
context: JavaScriptArtifactGraphContext,
record: BundlerGraphRecord,
): void => {
): number => {
let selfAsyncChunks = 0;
const { file, registration, chunk, coverage } = record;
for (const moduleKey of registration.entry_module_keys) {
const resolved = context.moduleNodes.get(
@@ -209,6 +217,10 @@ const addBundlerRuntimeEdges = (
for (const chunkKey of registration.async_chunk_keys) {
const lookup = chunkLookupKey(file.path, registration.runtime, chunkKey);
const resolved = context.chunkNodes.get(lookup);
if (resolved?.node_id === chunk.node_id) {
selfAsyncChunks++;
continue;
}
const target =
resolved ?? unresolvedBundlerChunkNode(context, record, chunkKey);
context.accumulator.addEdge({
@@ -232,6 +244,7 @@ const addBundlerRuntimeEdges = (
}),
});
}
return selfAsyncChunks;
};
const unresolvedBundlerModuleNode = (