@@ -15,7 +15,7 @@ import { BinaryLike, createHash } from 'node:crypto';
15
15
import { readFile } from 'node:fs/promises' ;
16
16
import { ServerResponse } from 'node:http' ;
17
17
import type { AddressInfo } from 'node:net' ;
18
- import path from 'node:path' ;
18
+ import path , { posix } from 'node:path' ;
19
19
import { Connect , InlineConfig , ViteDevServer , createServer , normalizePath } from 'vite' ;
20
20
import { JavaScriptTransformer } from '../../tools/esbuild/javascript-transformer' ;
21
21
import { RenderOptions , renderPage } from '../../utils/server-rendering/render-page' ;
@@ -32,6 +32,8 @@ interface OutputFileRecord {
32
32
updated : boolean ;
33
33
}
34
34
35
+ const SSG_MARKER_REGEXP = / n g - s e r v e r - c o n t e x t = [ " ' ] \w * \| ? s s g \| ? \w * [ " ' ] / ;
36
+
35
37
function hashContent ( contents : BinaryLike ) : Buffer {
36
38
// TODO: Consider xxhash
37
39
return createHash ( 'sha256' ) . update ( contents ) . digest ( ) ;
@@ -328,50 +330,46 @@ export async function setupServer(
328
330
next : Connect . NextFunction ,
329
331
) {
330
332
const url = req . originalUrl ;
331
- if ( ! url ) {
333
+ if ( ! url || url . endsWith ( '.html' ) ) {
332
334
next ( ) ;
333
335
334
336
return ;
335
337
}
336
338
339
+ const potentialPrerendered = outputFiles . get ( posix . join ( url , 'index.html' ) ) ?. contents ;
340
+ if ( potentialPrerendered ) {
341
+ const content = Buffer . from ( potentialPrerendered ) . toString ( 'utf-8' ) ;
342
+ if ( SSG_MARKER_REGEXP . test ( content ) ) {
343
+ transformIndexHtmlAndAddHeaders ( url , potentialPrerendered , res , next ) ;
344
+
345
+ return ;
346
+ }
347
+ }
348
+
337
349
const rawHtml = outputFiles . get ( '/index.server.html' ) ?. contents ;
338
350
if ( ! rawHtml ) {
339
351
next ( ) ;
340
352
341
353
return ;
342
354
}
343
355
344
- server
345
- . transformIndexHtml ( url , Buffer . from ( rawHtml ) . toString ( 'utf-8' ) )
346
- . then ( async ( html ) => {
347
- const { content } = await renderPage ( {
348
- document : html ,
349
- route : pathnameWithoutServePath ( url , serverOptions ) ,
350
- serverContext : 'ssr' ,
351
- loadBundle : ( path : string ) =>
352
- server . ssrLoadModule ( path . slice ( 1 ) ) as ReturnType <
353
- NonNullable < RenderOptions [ 'loadBundle' ] >
354
- > ,
355
- // Files here are only needed for critical CSS inlining.
356
- outputFiles : { } ,
357
- // TODO: add support for critical css inlining.
358
- inlineCriticalCss : false ,
359
- } ) ;
360
-
361
- if ( content ) {
362
- res . setHeader ( 'Content-Type' , 'text/html' ) ;
363
- res . setHeader ( 'Cache-Control' , 'no-cache' ) ;
364
- if ( serverOptions . headers ) {
365
- Object . entries ( serverOptions . headers ) . forEach ( ( [ name , value ] ) =>
366
- res . setHeader ( name , value ) ,
367
- ) ;
368
- }
369
- res . end ( content ) ;
370
- } else {
371
- next ( ) ;
372
- }
373
- } )
374
- . catch ( ( error ) => next ( error ) ) ;
356
+ transformIndexHtmlAndAddHeaders ( url , rawHtml , res , next , async ( html ) => {
357
+ const { content } = await renderPage ( {
358
+ document : html ,
359
+ route : pathnameWithoutServePath ( url , serverOptions ) ,
360
+ serverContext : 'ssr' ,
361
+ loadBundle : ( path : string ) =>
362
+ server . ssrLoadModule ( path . slice ( 1 ) ) as ReturnType <
363
+ NonNullable < RenderOptions [ 'loadBundle' ] >
364
+ > ,
365
+ // Files here are only needed for critical CSS inlining.
366
+ outputFiles : { } ,
367
+ // TODO: add support for critical css inlining.
368
+ inlineCriticalCss : false ,
369
+ } ) ;
370
+
371
+ return content ;
372
+ } ) ;
375
373
}
376
374
377
375
if ( ssr ) {
@@ -392,19 +390,7 @@ export async function setupServer(
392
390
if ( pathname === '/' || pathname === `/index.html` ) {
393
391
const rawHtml = outputFiles . get ( '/index.html' ) ?. contents ;
394
392
if ( rawHtml ) {
395
- server
396
- . transformIndexHtml ( req . url , Buffer . from ( rawHtml ) . toString ( 'utf-8' ) )
397
- . then ( ( processedHtml ) => {
398
- res . setHeader ( 'Content-Type' , 'text/html' ) ;
399
- res . setHeader ( 'Cache-Control' , 'no-cache' ) ;
400
- if ( serverOptions . headers ) {
401
- Object . entries ( serverOptions . headers ) . forEach ( ( [ name , value ] ) =>
402
- res . setHeader ( name , value ) ,
403
- ) ;
404
- }
405
- res . end ( processedHtml ) ;
406
- } )
407
- . catch ( ( error ) => next ( error ) ) ;
393
+ transformIndexHtmlAndAddHeaders ( req . url , rawHtml , res , next ) ;
408
394
409
395
return ;
410
396
}
@@ -413,6 +399,39 @@ export async function setupServer(
413
399
next ( ) ;
414
400
} ) ;
415
401
} ;
402
+
403
+ function transformIndexHtmlAndAddHeaders (
404
+ url : string ,
405
+ rawHtml : Uint8Array ,
406
+ res : ServerResponse < import ( 'http' ) . IncomingMessage > ,
407
+ next : Connect . NextFunction ,
408
+ additionalTransformer ?: ( html : string ) => Promise < string | undefined > ,
409
+ ) {
410
+ server
411
+ . transformIndexHtml ( url , Buffer . from ( rawHtml ) . toString ( 'utf-8' ) )
412
+ . then ( async ( processedHtml ) => {
413
+ if ( additionalTransformer ) {
414
+ const content = await additionalTransformer ( processedHtml ) ;
415
+ if ( ! content ) {
416
+ next ( ) ;
417
+
418
+ return ;
419
+ }
420
+
421
+ processedHtml = content ;
422
+ }
423
+
424
+ res . setHeader ( 'Content-Type' , 'text/html' ) ;
425
+ res . setHeader ( 'Cache-Control' , 'no-cache' ) ;
426
+ if ( serverOptions . headers ) {
427
+ Object . entries ( serverOptions . headers ) . forEach ( ( [ name , value ] ) =>
428
+ res . setHeader ( name , value ) ,
429
+ ) ;
430
+ }
431
+ res . end ( processedHtml ) ;
432
+ } )
433
+ . catch ( ( error ) => next ( error ) ) ;
434
+ }
416
435
} ,
417
436
} ,
418
437
] ,
0 commit comments