fix(javascript): preserve wide async chunk collection (#10362)

* fix(javascript): collect async chunks without call spreads

* test(javascript): bound the public checkpoint control
This commit is contained in:
Kaoru0822 authored and GitHub committed 2026-11-13 06:17:43 -08:00
1 parent e841fb16e4
commit 4b26f38d6f
4 files changed
+125 -1

No files matched your search

@@ -230,7 +230,7 @@ const recoverBundlerModules = (
);
if (recovered === null) continue;
modules.push(recovered.module);
asyncChunkKeys.push(...recovered.asyncChunkKeys);
for (const key of recovered.asyncChunkKeys) asyncChunkKeys.push(key);
unknownAsyncChunkKeys += recovered.unknownAsyncChunkKeys;
}
return {
@@ -0,0 +1,70 @@
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect } from "vitest";
import { parseEvidence } from "../../src/domain/evidence.js";
import { javascriptApplicationAnalysisResultSchema } from "../../src/domain/javascript/javascriptApplicationAnalysis.js";
import { JAVASCRIPT_STATIC_FACT_BYTES } from "../../src/domain/javascript/javascriptAnalysisResourceControls.js";
import { javascriptAsyncChunkCapacity } from "../fixtures/javascriptAsyncChunkCapacity.js";
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
import { cliTest } from "../support/cli/cliFixture.js";
cliTest(
"reports the complete-fact ceiling for wide async chunk metadata",
{ timeout: 60_000 },
async ({ cli }) => {
// This bounded public control exceeds the complete-fact allowance. The
// forked boundary regression separately owns call-argument capacity.
const { source } = javascriptAsyncChunkCapacity(50_000);
const root = await createTestTempDirectory("rea-async-chunk-budget-");
await writeFile(join(root, "entry.cjs"), source);
const response = await cli.run({
arguments: [
"analyze-javascript-application",
root,
"--max-semantic-nodes",
"1",
"--max-semantic-nodes-per-file",
"1",
"--max-heap-mb",
"2048",
"--analysis-timeout-ms",
"30000",
"--json",
],
timeoutMs: 55_000,
});
// Analysis retains the interrupted file rather than failing the command.
expect(response.exitCode, `${response.stdout}\n${response.stderr}`).toBe(0);
const result = javascriptApplicationAnalysisResultSchema.parse(
parseEvidence(response.json).normalized_result,
);
const failures = result.graph.nodes.flatMap(({ observations }) =>
observations.flatMap(({ properties }) =>
properties.analysis_failure === undefined
? []
: [properties.analysis_failure],
),
);
expect(failures).toEqual([
expect.objectContaining({
code: "resource_constraint",
retryable: false,
message: expect.stringContaining("static checkpoint"),
remediation: {
action: expect.stringContaining("no runtime override"),
},
details: expect.objectContaining({
resource: "transport",
reported_limits: expect.objectContaining({
checkpoint_bytes: expect.any(Number),
checkpoint_limit_bytes: JAVASCRIPT_STATIC_FACT_BYTES,
}),
}),
}),
]);
expect(JSON.stringify(failures)).not.toContain("call stack");
expect(JSON.stringify(failures)).toContain(
"Raising max_heap_mb does not raise it",
);
},
);
@@ -0,0 +1,41 @@
import { createHash } from "node:crypto";
import { expect, it } from "vitest";
import { javascriptAsyncChunkCapacity } from "../../fixtures/javascriptAsyncChunkCapacity.js";
import { analyzeJavaScriptStaticSource } from "../../fixtures/javascriptStaticAnalysis.js";
it("retains a wide factory's async keys before normalizing the registration", () => {
// The forked boundary project preserves the default main-thread stack.
const count = 130_000;
const { source, factory } = javascriptAsyncChunkCapacity(count);
const result = analyzeJavaScriptStaticSource(source);
expect(result.parse_status).toBe("partial");
expect(result.analysis_coverage?.syntax).toEqual({
status: "complete",
error_count: 0,
});
expect(result.bundler_registrations).toHaveLength(1);
const registration = result.bundler_registrations[0];
if (registration === undefined) throw new Error("Missing registration");
const keys = new Set(registration.async_chunk_keys);
expect(registration.async_chunk_keys).toHaveLength(count + 1);
expect(keys.size).toBe(count + 1);
expect(
Array.from({ length: count }, (_, index) => String(index)).every((key) =>
keys.has(key),
),
).toBe(true);
expect(registration.async_chunk_keys[0]).toBe("0");
expect(registration.async_chunk_keys.at(-1)).toBe("later");
expect(registration.unknown_async_chunk_keys).toBe(1);
expect(registration.modules).toHaveLength(2);
const start = source.indexOf(factory);
expect(registration.modules[0]).toMatchObject({
module_key: "1",
factory_require_name: "r",
source_sha256: createHash("sha256").update(factory).digest("hex"),
location: {
start: { line: 1, column: start },
end: { line: 1, column: start + factory.length },
},
});
}, 30_000);
+13
View File
@@ -0,0 +1,13 @@
/** Inert generated Webpack source; no fixture code is executed. */
export const javascriptAsyncChunkCapacity = (count: number) => {
const factory = `(m,e,r)=>{${Array.from(
{ length: count },
(_, index) => `r.e(${String(index)});`,
).join("")}}`;
return {
factory,
source:
`self.webpackChunkapp.push([[1],{1:${factory},` +
"2:(m,e,r)=>{r.e(0);r.e('later');r.e(dynamic);}}]);",
};
};