@@ -13,66 +13,49 @@ import { create_node_analyser } from '../static_analysis/index.js';
13
13
* @param {import('types').ManifestData } manifest_data
14
14
* @param {import('vite').Manifest } server_manifest
15
15
* @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
17
18
* @param {import('types').RecursiveRequired<import('types').ValidatedConfig['kit']['output']> } output_config
18
19
* @param {Map<string, { page_options: Record<string, any> | null, children: string[] }> } static_exports
19
20
*/
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 ) {
21
22
mkdirp ( `${ out } /server/nodes` ) ;
22
23
mkdirp ( `${ out } /server/stylesheets` ) ;
23
24
24
25
/** @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 ( ) ;
35
27
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 ) ;
40
30
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 ) ;
42
33
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 ) {
51
38
continue ;
52
39
}
40
+ client_stylesheet . forEach ( ( file , i ) => {
41
+ stylesheets_to_inline . set ( file , server_stylesheet [ i ] ) ;
42
+ } )
43
+ }
53
44
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 ) ;
68
49
}
50
+ }
69
51
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 ) ;
76
59
}
77
60
}
78
61
@@ -139,8 +122,9 @@ export async function build_server_nodes(out, kit, manifest_data, server_manifes
139
122
const entry_path = `${ normalizePath ( kit . outDir ) } /generated/client-optimized/nodes/${ i } .js` ;
140
123
const entry = find_deps ( client_manifest , entry_path , true ) ;
141
124
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.
144
128
145
129
/** @type {import('types').AssetDependencies | undefined } */
146
130
let component ;
@@ -155,26 +139,26 @@ export async function build_server_nodes(out, kit, manifest_data, server_manifes
155
139
}
156
140
157
141
/** @type {Set<string> } */
158
- const css_used_by_server = new Set ( ) ;
142
+ const eager_css = new Set ( ) ;
159
143
/** @type {Set<string> } */
160
- const assets_used_by_server = new Set ( ) ;
144
+ const eager_assets = new Set ( ) ;
161
145
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 ;
167
151
}
168
152
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 ) ) ;
172
156
}
173
157
} ) ;
174
158
175
159
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 ) ) ;
178
162
}
179
163
180
164
exports . push (
@@ -184,19 +168,26 @@ export async function build_server_nodes(out, kit, manifest_data, server_manifes
184
168
) ;
185
169
186
170
/** @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 } ` ) ;
195
186
}
196
187
} ) ;
197
188
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});` ) ;
200
191
}
201
192
202
193
fs . writeFileSync (
@@ -205,3 +196,37 @@ export async function build_server_nodes(out, kit, manifest_data, server_manifes
205
196
) ;
206
197
}
207
198
}
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