@@ -448,6 +448,36 @@ function baseViteConfig(viteCfg) {
448448
449449// ── Build passes ────────────────────────────────────────────────────────────
450450
451+ /**
452+ * Return the output.manualChunks / output.experimentalMinChunkSize entry for
453+ * rollupOptions.output based on whether the codebase is fully ESM.
454+ *
455+ * CJS mode (clientFilesESM:false): @rollup/plugin-commonjs injects \0-prefixed
456+ * virtual proxy modules into the graph which inflate apparent module sizes and
457+ * add phantom importer edges. Rollup's native experimentalMinChunkSize would
458+ * produce inaccurate merges in this environment. viteManualChunksPlugin guards
459+ * against virtual modules explicitly and uses info.code.length as an honest size
460+ * proxy for pre-minification CJS source.
461+ *
462+ * ESM mode (clientFilesESM:true): the graph is clean — no proxy modules, no CJS
463+ * wrapper boilerplate. Rollup's native experimentalMinChunkSize is accurate and
464+ * optimal; viteManualChunksPlugin is no longer needed. manualChunksMinSize maps
465+ * directly to the native threshold (0 = no merging if the site leaves it unset).
466+ *
467+ * @param {object } viteCfg
468+ * @returns {object }
469+ */
470+ function buildChunkingOutput ( viteCfg ) {
471+ const minSize = viteCfg . manualChunksMinSize ?? 0 ;
472+
473+ if ( viteCfg . clientFilesESM ) {
474+ return { experimentalMinChunkSize : minSize } ;
475+ }
476+
477+ return { manualChunks : viteManualChunksPlugin ( minSize , CWD ) } ;
478+ }
479+
480+
451481/**
452482 * Pass 1 — view mode (splitting pass).
453483 *
@@ -492,21 +522,8 @@ async function runViewBuild(viteCfg) {
492522 format : 'esm' ,
493523 entryFileNames : '[name]-[hash].js' ,
494524 chunkFileNames : 'chunks/[name]-[hash].js' ,
495- // CJS mode (clientFilesESM:false): @rollup/plugin-commonjs injects \0-prefixed
496- // virtual proxy modules into the graph which inflate apparent module sizes and
497- // add phantom importer edges. Rollup's native experimentalMinChunkSize would
498- // produce inaccurate merges in this environment. viteManualChunksPlugin guards
499- // against virtual modules explicitly and uses info.code.length as a honest size
500- // proxy for pre-minification CJS source.
501- //
502- // ESM mode (clientFilesESM:true): the graph is clean — no proxy modules, no CJS
503- // wrapper boilerplate. Rollup's native experimentalMinChunkSize is accurate and
504- // optimal; viteManualChunksPlugin is no longer needed. manualChunksMinSize maps
505- // directly to the native threshold (0 = no merging if the site leaves it unset).
506- ...( viteCfg . clientFilesESM
507- ? { experimentalMinChunkSize : viteCfg . manualChunksMinSize ?? 0 }
508- : { manualChunks : viteManualChunksPlugin ( viteCfg . manualChunksMinSize ?? 0 , CWD ) }
509- ) ,
525+ // See buildChunkingOutput() for CJS vs ESM branching rationale.
526+ ...buildChunkingOutput ( viteCfg ) ,
510527 } ,
511528 onwarn : suppressWarning ,
512529 } ;
@@ -650,6 +667,56 @@ async function buildJS(options = {}) {
650667
651668exports . buildJS = buildJS ;
652669
670+ // ── Client env ──────────────────────────────────────────────────────────────
671+
672+ /**
673+ * Scan source JS/Vue files for `process.env.VAR_NAME` references and write
674+ * `client-env.json` — an array of unique env var names.
675+ *
676+ * amphora-html's `addEnvVars()` call in renderers.js reads this file at server
677+ * startup to know which `process.env` values need to be forwarded to the
678+ * browser. Without it the server never exposes those values and client-side
679+ * code that reads `process.env.FOO` gets undefined at runtime.
680+ *
681+ * @returns {Promise<string[]> } sorted list of env var names written
682+ */
683+ async function generateClientEnv ( ) {
684+ const SOURCE_GLOBS = [
685+ path . join ( CWD , 'components' , '**' , '*.js' ) ,
686+ path . join ( CWD , 'layouts' , '**' , '*.js' ) ,
687+ path . join ( CWD , 'services' , '**' , '*.js' ) ,
688+ path . join ( CWD , 'global' , '**' , '*.js' ) ,
689+ path . join ( CWD , 'amphora' , '**' , '*.js' ) ,
690+ path . join ( CWD , 'app.js' ) ,
691+ path . join ( CWD , 'components' , '**' , '*.vue' ) ,
692+ path . join ( CWD , 'layouts' , '**' , '*.vue' ) ,
693+ ] ;
694+
695+ const files = globSync ( SOURCE_GLOBS , { nodir : true } ) ;
696+ const found = new Set ( ) ;
697+ const ENV_VAR_RE = / p r o c e s s \. e n v \. ( [ A - Z _ ] [ A - Z 0 - 9 _ ] * ) / g;
698+
699+ for ( const file of files ) {
700+ let src ;
701+
702+ try {
703+ src = await fs . readFile ( file , 'utf8' ) ;
704+ } catch ( e ) {
705+ continue ;
706+ }
707+
708+ for ( const match of src . matchAll ( ENV_VAR_RE ) ) {
709+ found . add ( match [ 1 ] ) ;
710+ }
711+ }
712+
713+ const vars = [ ...found ] . sort ( ) ;
714+
715+ await fs . outputJson ( path . join ( CWD , 'client-env.json' ) , vars , { spaces : 2 } ) ;
716+
717+ return vars ;
718+ }
719+
653720// ── Full build (JS + assets in parallel) ────────────────────────────────────
654721
655722/**
@@ -742,11 +809,12 @@ async function buildAll(options = {}) {
742809 }
743810
744811 await Promise . all ( [
745- step ( 'js' , ( ) => buildJS ( options ) ) ,
746- step ( 'styles' , ( ) => buildStyles ( options ) ) ,
747- step ( 'fonts' , ( ) => buildFonts ( ) ) ,
748- step ( 'templates' , ( ) => buildTemplates ( options ) ) ,
749- step ( 'vendor' , ( ) => copyVendor ( ) ) ,
812+ step ( 'js' , ( ) => buildJS ( options ) ) ,
813+ step ( 'styles' , ( ) => buildStyles ( options ) ) ,
814+ step ( 'fonts' , ( ) => buildFonts ( ) ) ,
815+ step ( 'templates' , ( ) => buildTemplates ( options ) ) ,
816+ step ( 'vendor' , ( ) => copyVendor ( ) ) ,
817+ step ( 'client-env' , ( ) => generateClientEnv ( ) ) ,
750818 ] ) ;
751819
752820 if ( timer ) { clearInterval ( timer ) ; timer = null ; }
@@ -852,11 +920,8 @@ async function watch(options = {}) {
852920 format : 'esm' ,
853921 entryFileNames : '[name]-[hash].js' ,
854922 chunkFileNames : 'chunks/[name]-[hash].js' ,
855- // Same CJS/ESM branching as runViewBuild — see that function for full rationale.
856- ...( viteCfg . clientFilesESM
857- ? { experimentalMinChunkSize : viteCfg . manualChunksMinSize ?? 0 }
858- : { manualChunks : viteManualChunksPlugin ( viteCfg . manualChunksMinSize ?? 0 , CWD ) }
859- ) ,
923+ // See buildChunkingOutput() for CJS vs ESM branching rationale.
924+ ...buildChunkingOutput ( viteCfg ) ,
860925 } ,
861926 onwarn : suppressWarning ,
862927 watch : {
0 commit comments