@@ -11,6 +11,7 @@ import {
1111 type Plugin ,
1212 type ResolvedConfig ,
1313 type Rollup ,
14+ type ViteBuilder ,
1415 type ViteDevServer ,
1516 isCSSRequest ,
1617 isRunnableDevEnvironment ,
@@ -155,6 +156,84 @@ export function assetsPlugin(pluginOpts?: FullstackPluginOptions): Plugin[] {
155156 }
156157 }
157158
159+ let writeAssetsManifestCalled = false ;
160+ async function writeAssetsManifest ( builder : ViteBuilder ) {
161+ if ( writeAssetsManifestCalled ) return ;
162+ writeAssetsManifestCalled = true ;
163+
164+ // build manifest of imported assets
165+ const manifest : BuildAssetsManifest = { } ;
166+ for ( const [ environmentName , metas ] of Object . entries (
167+ importAssetsMetaMap ,
168+ ) ) {
169+ const bundle = bundleMap [ environmentName ] ! ;
170+ const assetDepsMap = collectAssetDeps ( bundle ) ;
171+ for ( const [ id , meta ] of Object . entries ( metas ) ) {
172+ const found = assetDepsMap [ id ] ;
173+ if ( ! found ) {
174+ builder . config . logger . error (
175+ `[vite-plugin-fullstack] failed to find built chunk for ${ meta . id } imported by ${ meta . importerEnvironment } environment` ,
176+ ) ;
177+ return ;
178+ }
179+ const result : ImportAssetsResultRaw = {
180+ js : [ ] ,
181+ css : [ ] ,
182+ } ;
183+ const { chunk, deps } = found ;
184+ // TODO: base
185+ if ( environmentName === "client" ) {
186+ result . entry = `/${ chunk . fileName } ` ;
187+ result . js = deps . js . map ( ( fileName ) => ( {
188+ href : `/${ fileName } ` ,
189+ } ) ) ;
190+ }
191+ result . css = deps . css . map ( ( fileName ) => ( {
192+ href : `/${ fileName } ` ,
193+ } ) ) ;
194+
195+ // add single css when `cssCodeSplit: false`
196+ // https://github.com/vitejs/vite/blob/3a92bc79b306a01b8aaf37f80b2239eaf6e488e7/packages/vite/src/node/plugins/css.ts#L999-L1011
197+ if ( ! builder . environments [ environmentName ] ! . config . build . cssCodeSplit ) {
198+ const singleCss = Object . values ( bundle ) . find (
199+ ( v ) =>
200+ v . type === "asset" && v . originalFileNames . includes ( "style.css" ) ,
201+ ) ;
202+ if ( singleCss ) {
203+ result . css . push ( { href : `/${ singleCss . fileName } ` } ) ;
204+ }
205+ }
206+
207+ ( manifest [ environmentName ] ??= { } ) [ meta . key ] = result ;
208+ }
209+ }
210+
211+ // write manifest to importer environments
212+ const importerEnvironments = new Set (
213+ Object . values ( importAssetsMetaMap )
214+ . flatMap ( ( metas ) => Object . values ( metas ) )
215+ . flatMap ( ( meta ) => meta . importerEnvironment ) ,
216+ ) ;
217+ for ( const environmentName of importerEnvironments ) {
218+ const outDir = builder . environments [ environmentName ] ! . config . build . outDir ;
219+ fs . writeFileSync (
220+ path . join ( outDir , BUILD_ASSETS_MANIFEST_NAME ) ,
221+ `export default ${ JSON . stringify ( manifest , null , 2 ) } ;` ,
222+ ) ;
223+
224+ // copy assets to client (mainly for server css)
225+ const clientOutDir = builder . environments [ "client" ] ! . config . build . outDir ;
226+ for ( const asset of Object . values ( bundleMap [ environmentName ] ! ) ) {
227+ if ( asset . type === "asset" ) {
228+ const srcFile = path . join ( outDir , asset . fileName ) ;
229+ const destFile = path . join ( clientOutDir , asset . fileName ) ;
230+ fs . mkdirSync ( path . dirname ( destFile ) , { recursive : true } ) ;
231+ fs . copyFileSync ( srcFile , destFile ) ;
232+ }
233+ }
234+ }
235+ }
236+
158237 return [
159238 {
160239 name : "fullstack:assets" ,
@@ -348,85 +427,23 @@ export function assetsPlugin(pluginOpts?: FullstackPluginOptions): Plugin[] {
348427 }
349428 }
350429 } ,
430+ buildApp : {
431+ order : "pre" ,
432+ async handler ( builder ) {
433+ // expose writeAssetsManifest to builder
434+ builder . writeAssetsManifest = async ( ) => {
435+ await writeAssetsManifest ( builder ) ;
436+ } ;
437+ } ,
438+ } ,
439+ } ,
440+ {
441+ name : "fullstack:write-assets-manifest-post" ,
351442 buildApp : {
352443 order : "post" ,
353444 async handler ( builder ) {
354- // build manifest of imported assets
355- const manifest : BuildAssetsManifest = { } ;
356- for ( const [ environmentName , metas ] of Object . entries (
357- importAssetsMetaMap ,
358- ) ) {
359- const bundle = bundleMap [ environmentName ] ! ;
360- const assetDepsMap = collectAssetDeps ( bundle ) ;
361- for ( const [ id , meta ] of Object . entries ( metas ) ) {
362- const found = assetDepsMap [ id ] ;
363- if ( ! found ) {
364- return this . error (
365- `[vite-plugin-fullstack] failed to find built chunk for ${ meta . id } imported by ${ meta . importerEnvironment } environment` ,
366- ) ;
367- }
368- const result : ImportAssetsResultRaw = {
369- js : [ ] ,
370- css : [ ] ,
371- } ;
372- const { chunk, deps } = found ;
373- // TODO: base
374- if ( environmentName === "client" ) {
375- result . entry = `/${ chunk . fileName } ` ;
376- result . js = deps . js . map ( ( fileName ) => ( {
377- href : `/${ fileName } ` ,
378- } ) ) ;
379- }
380- result . css = deps . css . map ( ( fileName ) => ( {
381- href : `/${ fileName } ` ,
382- } ) ) ;
383-
384- // add single css when `cssCodeSplit: false`
385- // https://github.com/vitejs/vite/blob/3a92bc79b306a01b8aaf37f80b2239eaf6e488e7/packages/vite/src/node/plugins/css.ts#L999-L1011
386- if (
387- ! builder . environments [ environmentName ] ! . config . build
388- . cssCodeSplit
389- ) {
390- const singleCss = Object . values ( bundle ) . find (
391- ( v ) =>
392- v . type === "asset" &&
393- v . originalFileNames . includes ( "style.css" ) ,
394- ) ;
395- if ( singleCss ) {
396- result . css . push ( { href : `/${ singleCss . fileName } ` } ) ;
397- }
398- }
399-
400- ( manifest [ environmentName ] ??= { } ) [ meta . key ] = result ;
401- }
402- }
403-
404- // write manifest to importer environments
405- const importerEnvironments = new Set (
406- Object . values ( importAssetsMetaMap )
407- . flatMap ( ( metas ) => Object . values ( metas ) )
408- . flatMap ( ( meta ) => meta . importerEnvironment ) ,
409- ) ;
410- for ( const environmentName of importerEnvironments ) {
411- const outDir =
412- builder . environments [ environmentName ] ! . config . build . outDir ;
413- fs . writeFileSync (
414- path . join ( outDir , BUILD_ASSETS_MANIFEST_NAME ) ,
415- `export default ${ JSON . stringify ( manifest , null , 2 ) } ;` ,
416- ) ;
417-
418- // copy assets to client (mainly for server css)
419- const clientOutDir =
420- builder . environments [ "client" ] ! . config . build . outDir ;
421- for ( const asset of Object . values ( bundleMap [ environmentName ] ! ) ) {
422- if ( asset . type === "asset" ) {
423- const srcFile = path . join ( outDir , asset . fileName ) ;
424- const destFile = path . join ( clientOutDir , asset . fileName ) ;
425- fs . mkdirSync ( path . dirname ( destFile ) , { recursive : true } ) ;
426- fs . copyFileSync ( srcFile , destFile ) ;
427- }
428- }
429- }
445+ // ensure this is called at least once
446+ await builder . writeAssetsManifest ( ) ;
430447 } ,
431448 } ,
432449 } ,
0 commit comments