@@ -13,66 +13,49 @@ import { create_node_analyser } from '../static_analysis/index.js';
1313 * @param {import('types').ManifestData } manifest_data
1414 * @param {import('vite').Manifest } server_manifest
1515 * @param {import('vite').Manifest | null } client_manifest
16- * @param {import('vite').Rollup.OutputAsset[] | null } css
16+ * @param {import('vite').Rollup.OutputBundle | null } server_bundle
17+ * @param {import('vite').Rollup.RollupOutput['output'] | null } client_bundle
1718 * @param {import('types').RecursiveRequired<import('types').ValidatedConfig['kit']['output']> } output_config
1819 * @param {Map<string, { page_options: Record<string, any> | null, children: string[] }> } static_exports
1920 */
20- export async function build_server_nodes ( out , kit , manifest_data , server_manifest , client_manifest , css , output_config , static_exports ) {
21+ export async function build_server_nodes ( out , kit , manifest_data , server_manifest , client_manifest , server_bundle , client_bundle , output_config , static_exports ) {
2122 mkdirp ( `${ out } /server/nodes` ) ;
2223 mkdirp ( `${ out } /server/stylesheets` ) ;
2324
2425 /** @type {Map<string, string> } */
25- const stylesheet_lookup = new Map ( ) ;
26-
27- if ( css ) {
28- /** @type {Set<string> } */
29- const client_stylesheets = new Set ( ) ;
30- for ( const key in client_manifest ) {
31- client_manifest [ key ] . css ?. forEach ( ( filename ) => {
32- client_stylesheets . add ( filename ) ;
33- } ) ;
34- }
26+ const stylesheets_to_inline = new Map ( ) ;
3527
36- /** @type {Map<number, string[]> } */
37- const server_stylesheets = new Map ( ) ;
38- manifest_data . nodes . forEach ( ( node , i ) => {
39- if ( ! node . component || ! server_manifest [ node . component ] ) return ;
28+ if ( server_bundle && client_bundle && kit . inlineStyleThreshold > 0 ) {
29+ const client = get_stylesheets ( client_bundle ) ;
4030
41- const { stylesheets } = find_deps ( server_manifest , node . component , false ) ;
31+ const server_chunks = Object . values ( server_bundle ) ;
32+ const server = get_stylesheets ( server_chunks ) ;
4233
43- if ( stylesheets . length ) {
44- server_stylesheets . set ( i , stylesheets ) ;
45- }
46- } ) ;
47-
48- for ( const asset of css ) {
49- // ignore dynamically imported stylesheets since we don't need to inline those
50- if ( ! client_stylesheets . has ( asset . fileName ) || asset . source . length >= kit . inlineStyleThreshold ) {
34+ // map server stylesheet name to the client stylesheet name
35+ for ( const [ id , client_stylesheet ] of client . stylesheets_used ) {
36+ const server_stylesheet = server . stylesheets_used . get ( id ) ;
37+ if ( ! server_stylesheet ) {
5138 continue ;
5239 }
40+ client_stylesheet . forEach ( ( file , i ) => {
41+ stylesheets_to_inline . set ( file , server_stylesheet [ i ] ) ;
42+ } )
43+ }
5344
54- // We know that the names for entry points are numbers.
55- const [ index ] = basename ( asset . fileName ) . split ( '.' ) ;
56- // There can also be other CSS files from shared components
57- // for example, which we need to ignore here.
58- if ( isNaN ( + index ) ) continue ;
59-
60- const file = `${ out } /server/stylesheets/${ index } .js` ;
61-
62- // we need to inline the server stylesheet instead of the client one
63- // so that asset paths are correct on document load
64- const filenames = server_stylesheets . get ( + index ) ;
65-
66- if ( ! filenames ) {
67- throw new Error ( 'This should never happen, but if it does, it means we failed to find the server stylesheet for a node.' ) ;
45+ // filter out stylesheets that should not be inlined
46+ for ( const [ fileName , content ] of client . stylesheet_content ) {
47+ if ( content . length >= kit . inlineStyleThreshold ) {
48+ stylesheets_to_inline . delete ( fileName ) ;
6849 }
50+ }
6951
70- const sources = filenames . map ( ( filename ) => {
71- return fs . readFileSync ( `${ out } /server/${ filename } ` , 'utf-8' ) ;
72- } ) ;
73- fs . writeFileSync ( file , `// ${ filenames . join ( ', ' ) } \nexport default ${ s ( sources . join ( '\n' ) ) } ;` ) ;
74-
75- stylesheet_lookup . set ( asset . fileName , index ) ;
52+ // map server stylesheet source to the client stylesheet name
53+ for ( const [ client_file , server_file ] of stylesheets_to_inline ) {
54+ const source = server . stylesheet_content . get ( server_file ) ;
55+ if ( ! source ) {
56+ throw new Error ( `Server stylesheet source not found for client stylesheet ${ client_file } ` ) ;
57+ }
58+ stylesheets_to_inline . set ( client_file , source ) ;
7659 }
7760 }
7861
@@ -139,8 +122,9 @@ export async function build_server_nodes(out, kit, manifest_data, server_manifes
139122 const entry_path = `${ normalizePath ( kit . outDir ) } /generated/client-optimized/nodes/${ i } .js` ;
140123 const entry = find_deps ( client_manifest , entry_path , true ) ;
141124
142- // eagerly load stylesheets and fonts imported by the SSR-ed page to avoid FOUC.
143- // If it is not used during SSR, it can be lazily loaded in the browser.
125+ // eagerly load client stylesheets and fonts imported by the SSR-ed page to avoid FOUC.
126+ // However, if it is not used during SSR (not present in the server manifest),
127+ // then it can be lazily loaded in the browser.
144128
145129 /** @type {import('types').AssetDependencies | undefined } */
146130 let component ;
@@ -155,26 +139,26 @@ export async function build_server_nodes(out, kit, manifest_data, server_manifes
155139 }
156140
157141 /** @type {Set<string> } */
158- const css_used_by_server = new Set ( ) ;
142+ const eager_css = new Set ( ) ;
159143 /** @type {Set<string> } */
160- const assets_used_by_server = new Set ( ) ;
144+ const eager_assets = new Set ( ) ;
161145
162- entry . stylesheet_map . forEach ( ( value , key ) => {
163- // pages and layouts are named as node indexes in the client manifest
164- // so we need to use the original filename when checking against the server manifest
165- if ( key === entry_path ) {
166- key = node . component ?? key ;
146+ entry . stylesheet_map . forEach ( ( value , filepath ) => {
147+ // pages and layouts are renamed to node indexes when optimised for the client
148+ // so we use the original filename instead to check against the server manifest
149+ if ( filepath === entry_path ) {
150+ filepath = node . component ?? filepath ;
167151 }
168152
169- if ( component ?. stylesheet_map . has ( key ) || universal ?. stylesheet_map . has ( key ) ) {
170- value . css . forEach ( file => css_used_by_server . add ( file ) ) ;
171- value . assets . forEach ( file => assets_used_by_server . add ( file ) ) ;
153+ if ( component ?. stylesheet_map . has ( filepath ) || universal ?. stylesheet_map . has ( filepath ) ) {
154+ value . css . forEach ( file => eager_css . add ( file ) ) ;
155+ value . assets . forEach ( file => eager_assets . add ( file ) ) ;
172156 }
173157 } ) ;
174158
175159 imported = entry . imports ;
176- stylesheets = Array . from ( css_used_by_server ) ;
177- fonts = filter_fonts ( Array . from ( assets_used_by_server ) ) ;
160+ stylesheets = Array . from ( eager_css ) ;
161+ fonts = filter_fonts ( Array . from ( eager_assets ) ) ;
178162 }
179163
180164 exports . push (
@@ -184,19 +168,26 @@ export async function build_server_nodes(out, kit, manifest_data, server_manifes
184168 ) ;
185169
186170 /** @type {string[] } */
187- const styles = [ ] ;
188-
189- stylesheets . forEach ( ( file ) => {
190- if ( stylesheet_lookup . has ( file ) ) {
191- const index = stylesheet_lookup . get ( file ) ;
192- const name = `stylesheet_${ index } ` ;
193- imports . push ( `import ${ name } from '../stylesheets/${ index } .js';` ) ;
194- styles . push ( `\t${ s ( file ) } : ${ name } ` ) ;
171+ const inline_styles = [ ] ;
172+
173+ stylesheets . forEach ( ( file , i ) => {
174+ if ( stylesheets_to_inline . has ( file ) ) {
175+ const filename = basename ( file ) ;
176+ const dest = `${ out } /server/stylesheets/${ filename } .js` ;
177+ const source = stylesheets_to_inline . get ( file ) ;
178+ if ( ! source ) {
179+ throw new Error ( `Server stylesheet source not found for client stylesheet ${ file } ` ) ;
180+ }
181+ fs . writeFileSync ( dest , `// ${ filename } \nexport default ${ s ( source ) } ;` ) ;
182+
183+ const name = `stylesheet_${ i } ` ;
184+ imports . push ( `import ${ name } from '../stylesheets/${ filename } .js';` ) ;
185+ inline_styles . push ( `\t${ s ( file ) } : ${ name } ` ) ;
195186 }
196187 } ) ;
197188
198- if ( styles . length > 0 ) {
199- exports . push ( `export const inline_styles = () => ({\n${ styles . join ( ',\n' ) } \n});` ) ;
189+ if ( inline_styles . length > 0 ) {
190+ exports . push ( `export const inline_styles = () => ({\n${ inline_styles . join ( ',\n' ) } \n});` ) ;
200191 }
201192
202193 fs . writeFileSync (
@@ -205,3 +196,37 @@ export async function build_server_nodes(out, kit, manifest_data, server_manifes
205196 ) ;
206197 }
207198}
199+
200+ /**
201+ * @param {(import('vite').Rollup.OutputAsset | import('vite').Rollup.OutputChunk)[] } chunks
202+ */
203+ function get_stylesheets ( chunks ) {
204+ /**
205+ * A map of module IDs and the stylesheets they use.
206+ * @type {Map<string, string[]> }
207+ */
208+ const stylesheets_used = new Map ( ) ;
209+
210+ /**
211+ * A map of stylesheet names and their content.
212+ * @type {Map<string, string> }
213+ */
214+ const stylesheet_content = new Map ( ) ;
215+
216+ for ( const chunk of chunks ) {
217+ if ( chunk . type === 'asset' ) {
218+ if ( chunk . fileName . endsWith ( '.css' ) ) {
219+ stylesheet_content . set ( chunk . fileName , chunk . source . toString ( ) ) ;
220+ }
221+ continue ;
222+ }
223+
224+ if ( chunk . viteMetadata ?. importedCss . size ) {
225+ const css = Array . from ( chunk . viteMetadata . importedCss ) ;
226+ for ( const id of chunk . moduleIds ) {
227+ stylesheets_used . set ( id , css ) ;
228+ }
229+ }
230+ }
231+ return { stylesheets_used, stylesheet_content } ;
232+ }
0 commit comments