@@ -8,7 +8,7 @@ import crypto from "node:crypto";
88import { readFile } from "node:fs/promises" ;
99import { 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" ;
1212import { type BuildOptions , getPackagePath } from "@opennextjs/aws/build/helper.js" ;
1313import { applyRule , patchCode , type RuleConfig } from "@opennextjs/aws/build/patch/astCodePatcher.js" ;
1414import 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