@@ -15,7 +15,7 @@ import { BinaryLike, createHash } from 'node:crypto';
1515import { readFile } from 'node:fs/promises' ;
1616import { ServerResponse } from 'node:http' ;
1717import type { AddressInfo } from 'node:net' ;
18- import path from 'node:path' ;
18+ import path , { posix } from 'node:path' ;
1919import { Connect , InlineConfig , ViteDevServer , createServer , normalizePath } from 'vite' ;
2020import { JavaScriptTransformer } from '../../tools/esbuild/javascript-transformer' ;
2121import { RenderOptions , renderPage } from '../../utils/server-rendering/render-page' ;
@@ -32,6 +32,8 @@ interface OutputFileRecord {
3232 updated : boolean ;
3333}
3434
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+
3537function hashContent ( contents : BinaryLike ) : Buffer {
3638 // TODO: Consider xxhash
3739 return createHash ( 'sha256' ) . update ( contents ) . digest ( ) ;
@@ -328,50 +330,46 @@ export async function setupServer(
328330 next : Connect . NextFunction ,
329331 ) {
330332 const url = req . originalUrl ;
331- if ( ! url ) {
333+ if ( ! url || url . endsWith ( '.html' ) ) {
332334 next ( ) ;
333335
334336 return ;
335337 }
336338
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+
337349 const rawHtml = outputFiles . get ( '/index.server.html' ) ?. contents ;
338350 if ( ! rawHtml ) {
339351 next ( ) ;
340352
341353 return ;
342354 }
343355
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+ } ) ;
375373 }
376374
377375 if ( ssr ) {
@@ -392,19 +390,7 @@ export async function setupServer(
392390 if ( pathname === '/' || pathname === `/index.html` ) {
393391 const rawHtml = outputFiles . get ( '/index.html' ) ?. contents ;
394392 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 ) ;
408394
409395 return ;
410396 }
@@ -413,6 +399,39 @@ export async function setupServer(
413399 next ( ) ;
414400 } ) ;
415401 } ;
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+ }
416435 } ,
417436 } ,
418437 ] ,
0 commit comments