Skip to content

Commit fbe2d67

Browse files
author
Josh Kahn
committed
feat(manifest): extend manifest deduplication with edge mappings, entry files, and chunks arrays
- Replace empty edgeSSRModuleMapping/edgeRscModuleMapping with shared __EMPTY variable - Factor entryCSSFiles and entryJSFiles through existing factorManifestValue - Deduplicate repeated chunks arrays within module mappings into shared c_<hash> variables Builds on opennextjs#1186 (vicb/dedup).
1 parent 8371be5 commit fbe2d67

1 file changed

Lines changed: 77 additions & 4 deletions

File tree

packages/cloudflare/src/cli/build/patches/plugins/load-manifest.ts

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import crypto from "node:crypto";
88
import { readFile } from "node:fs/promises";
99
import { join, posix, relative, sep } from "node:path";
1010

11-
import { Lang, parse } from "@ast-grep/napi";
11+
import { Lang, parse, type SgNode } from "@ast-grep/napi";
1212
import { type BuildOptions, getPackagePath } from "@opennextjs/aws/build/helper.js";
1313
import { applyRule, patchCode, type RuleConfig } from "@opennextjs/aws/build/patch/astCodePatcher.js";
1414
import type { ContentUpdater, Plugin } from "@opennextjs/aws/plugins/content-updater.js";
@@ -115,13 +115,29 @@ async function getEvalManifestRule(buildOpts: BuildOptions) {
115115
manifest = factorManifestValue(manifest, "ssrModuleMapping", factoredValues);
116116
manifest = factorManifestValue(manifest, "edgeSSRModuleMapping", factoredValues);
117117
manifest = factorManifestValue(manifest, "rscModuleMapping", factoredValues);
118+
manifest = replaceEmptyEdgeMappings(manifest);
119+
manifest = factorManifestValue(manifest, "entryCSSFiles", factoredValues);
120+
manifest = factorManifestValue(manifest, "entryJSFiles", factoredValues);
118121
factoredManifest.set(path, manifest);
119122
}
120123
}
121124

122-
const factoredValuesCode = [...factoredValues.entries()]
123-
.map(([varName, value]) => `const ${varName} = ${value};`)
124-
.join("\n");
125+
// After factoring values but before generating factoredValuesCode:
126+
const chunksVars = new Map<string, string>();
127+
128+
for (const [varName, value] of factoredValues) {
129+
const deduped = deduplicateChunksArrays(value, chunksVars);
130+
factoredValues.set(varName, deduped);
131+
}
132+
133+
// Prepend chunks variable declarations before the factored values
134+
const chunksVarsCode = [...chunksVars.entries()].map(([name, val]) => `const ${name} = ${val};`).join("\n");
135+
136+
const factoredValuesCode =
137+
chunksVarsCode +
138+
"\n" +
139+
"const __EMPTY = {};\n" +
140+
[...factoredValues.entries()].map(([varName, value]) => `const ${varName} = ${value};`).join("\n");
125141

126142
const returnManifests = manifestPaths
127143
// Sort by path length descending so longer (more specific) paths match first,
@@ -224,3 +240,60 @@ fix: '"${key}": $${valueName}'
224240
// return the original manifest if the value is not found or is small enough to not warrant factoring out.
225241
return manifest;
226242
}
243+
244+
/**
245+
* Replace empty objects with a single shared variable.
246+
* @param manifest
247+
* @returns
248+
*/
249+
function replaceEmptyEdgeMappings(manifest: string): string {
250+
for (const key of ["edgeSSRModuleMapping", "edgeRscModuleMapping"]) {
251+
manifest = manifest.replace(`"${key}": {}`, `"${key}": __EMPTY`);
252+
}
253+
return manifest;
254+
}
255+
256+
/**
257+
* Deduplicate repeated 'chunks' arrays within a module mapping value.
258+
*
259+
* @param valueText The JS source text of the module mapping object
260+
* @param sharedVars Map to accumulate shared variable declarations
261+
* @returns The rewritten value text with chunks arrays replaced by variable refs
262+
*/
263+
function deduplicateChunksArrays(valueText: string, sharedVars: Map<string, string>): string {
264+
const rootNode = parse(Lang.JavaScript, valueText).root();
265+
266+
// Find all "chunks": [...] pairs
267+
const chunksRule = `
268+
rule:
269+
kind: pair
270+
all:
271+
- has:
272+
field: key
273+
pattern: '"chunks"'
274+
- has:
275+
field: value
276+
kind: array
277+
pattern: $CHUNKS
278+
fix: '"chunks": $CHUNKS'
279+
`;
280+
281+
const { matches } = applyRule(chunksRule, rootNode, { once: false });
282+
283+
const edits: Array<{ match: SgNode; replacement: string }> = [];
284+
285+
for (const match of matches) {
286+
const chunksNode = match.getMatch("CHUNKS");
287+
if (!chunksNode) continue;
288+
const chunksText = chunksNode.text();
289+
if (chunksText.length <= 30) continue; // Skip small arrays
290+
291+
const hash = crypto.createHash("sha1").update(chunksText).digest("hex");
292+
const varName = `c_${hash}`;
293+
sharedVars.set(varName, chunksText);
294+
edits.push({ match, replacement: `"chunks": ${varName}` });
295+
}
296+
297+
if (edits.length === 0) return valueText;
298+
return rootNode.commitEdits(edits.map((e) => e.match.replace(e.replacement)));
299+
}

0 commit comments

Comments
 (0)