@@ -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 type { Connect , InlineConfig , ViteDevServer } 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 ( ) ;
@@ -335,50 +337,46 @@ export async function setupServer(
335
337
next : Connect . NextFunction ,
336
338
) {
337
339
const url = req . originalUrl ;
338
- if ( ! url ) {
340
+ if ( ! url || url . endsWith ( '.html' ) ) {
339
341
next ( ) ;
340
342
341
343
return ;
342
344
}
343
345
346
+ const potentialPrerendered = outputFiles . get ( posix . join ( url , 'index.html' ) ) ?. contents ;
347
+ if ( potentialPrerendered ) {
348
+ const content = Buffer . from ( potentialPrerendered ) . toString ( 'utf-8' ) ;
349
+ if ( SSG_MARKER_REGEXP . test ( content ) ) {
350
+ transformIndexHtmlAndAddHeaders ( url , potentialPrerendered , res , next ) ;
351
+
352
+ return ;
353
+ }
354
+ }
355
+
344
356
const rawHtml = outputFiles . get ( '/index.server.html' ) ?. contents ;
345
357
if ( ! rawHtml ) {
346
358
next ( ) ;
347
359
348
360
return ;
349
361
}
350
362
351
- server
352
- . transformIndexHtml ( url , Buffer . from ( rawHtml ) . toString ( 'utf-8' ) )
353
- . then ( async ( html ) => {
354
- const { content } = await renderPage ( {
355
- document : html ,
356
- route : pathnameWithoutServePath ( url , serverOptions ) ,
357
- serverContext : 'ssr' ,
358
- loadBundle : ( path : string ) =>
359
- server . ssrLoadModule ( path . slice ( 1 ) ) as ReturnType <
360
- NonNullable < RenderOptions [ 'loadBundle' ] >
361
- > ,
362
- // Files here are only needed for critical CSS inlining.
363
- outputFiles : { } ,
364
- // TODO: add support for critical css inlining.
365
- inlineCriticalCss : false ,
366
- } ) ;
367
-
368
- if ( content ) {
369
- res . setHeader ( 'Content-Type' , 'text/html' ) ;
370
- res . setHeader ( 'Cache-Control' , 'no-cache' ) ;
371
- if ( serverOptions . headers ) {
372
- Object . entries ( serverOptions . headers ) . forEach ( ( [ name , value ] ) =>
373
- res . setHeader ( name , value ) ,
374
- ) ;
375
- }
376
- res . end ( content ) ;
377
- } else {
378
- next ( ) ;
379
- }
380
- } )
381
- . catch ( ( error ) => next ( error ) ) ;
363
+ transformIndexHtmlAndAddHeaders ( url , rawHtml , res , next , async ( html ) => {
364
+ const { content } = await renderPage ( {
365
+ document : html ,
366
+ route : pathnameWithoutServePath ( url , serverOptions ) ,
367
+ serverContext : 'ssr' ,
368
+ loadBundle : ( path : string ) =>
369
+ server . ssrLoadModule ( path . slice ( 1 ) ) as ReturnType <
370
+ NonNullable < RenderOptions [ 'loadBundle' ] >
371
+ > ,
372
+ // Files here are only needed for critical CSS inlining.
373
+ outputFiles : { } ,
374
+ // TODO: add support for critical css inlining.
375
+ inlineCriticalCss : false ,
376
+ } ) ;
377
+
378
+ return content ;
379
+ } ) ;
382
380
}
383
381
384
382
if ( ssr ) {
@@ -399,19 +397,7 @@ export async function setupServer(
399
397
if ( pathname === '/' || pathname === `/index.html` ) {
400
398
const rawHtml = outputFiles . get ( '/index.html' ) ?. contents ;
401
399
if ( rawHtml ) {
402
- server
403
- . transformIndexHtml ( req . url , Buffer . from ( rawHtml ) . toString ( 'utf-8' ) )
404
- . then ( ( processedHtml ) => {
405
- res . setHeader ( 'Content-Type' , 'text/html' ) ;
406
- res . setHeader ( 'Cache-Control' , 'no-cache' ) ;
407
- if ( serverOptions . headers ) {
408
- Object . entries ( serverOptions . headers ) . forEach ( ( [ name , value ] ) =>
409
- res . setHeader ( name , value ) ,
410
- ) ;
411
- }
412
- res . end ( processedHtml ) ;
413
- } )
414
- . catch ( ( error ) => next ( error ) ) ;
400
+ transformIndexHtmlAndAddHeaders ( req . url , rawHtml , res , next ) ;
415
401
416
402
return ;
417
403
}
@@ -420,6 +406,39 @@ export async function setupServer(
420
406
next ( ) ;
421
407
} ) ;
422
408
} ;
409
+
410
+ function transformIndexHtmlAndAddHeaders (
411
+ url : string ,
412
+ rawHtml : Uint8Array ,
413
+ res : ServerResponse < import ( 'http' ) . IncomingMessage > ,
414
+ next : Connect . NextFunction ,
415
+ additionalTransformer ?: ( html : string ) => Promise < string | undefined > ,
416
+ ) {
417
+ server
418
+ . transformIndexHtml ( url , Buffer . from ( rawHtml ) . toString ( 'utf-8' ) )
419
+ . then ( async ( processedHtml ) => {
420
+ if ( additionalTransformer ) {
421
+ const content = await additionalTransformer ( processedHtml ) ;
422
+ if ( ! content ) {
423
+ next ( ) ;
424
+
425
+ return ;
426
+ }
427
+
428
+ processedHtml = content ;
429
+ }
430
+
431
+ res . setHeader ( 'Content-Type' , 'text/html' ) ;
432
+ res . setHeader ( 'Cache-Control' , 'no-cache' ) ;
433
+ if ( serverOptions . headers ) {
434
+ Object . entries ( serverOptions . headers ) . forEach ( ( [ name , value ] ) =>
435
+ res . setHeader ( name , value ) ,
436
+ ) ;
437
+ }
438
+ res . end ( processedHtml ) ;
439
+ } )
440
+ . catch ( ( error ) => next ( error ) ) ;
441
+ }
423
442
} ,
424
443
} ,
425
444
] ,
0 commit comments