@@ -10,15 +10,16 @@ import { readFile } from 'node:fs/promises';
10
10
import { join } from 'node:path' ;
11
11
import { NormalizedCachedOptions } from '../normalize-cache' ;
12
12
import { NormalizedOptimizationOptions } from '../normalize-optimization' ;
13
+ import { addEventDispatchContract } from './add-event-dispatch-contract' ;
13
14
import { CrossOriginValue , Entrypoint , FileInfo , augmentIndexHtml } from './augment-index-html' ;
14
15
import { InlineCriticalCssProcessor } from './inline-critical-css' ;
15
16
import { InlineFontsProcessor } from './inline-fonts' ;
16
- import { addStyleNonce } from './style- nonce' ;
17
+ import { addNonce } from './nonce' ;
17
18
18
19
type IndexHtmlGeneratorPlugin = (
19
20
html : string ,
20
21
options : IndexHtmlGeneratorProcessOptions ,
21
- ) => Promise < string | IndexHtmlTransformResult > ;
22
+ ) => Promise < string | IndexHtmlPluginTransformResult > | string ;
22
23
23
24
export type HintMode = 'prefetch' | 'preload' | 'modulepreload' | 'preconnect' | 'dns-prefetch' ;
24
25
@@ -40,45 +41,78 @@ export interface IndexHtmlGeneratorOptions {
40
41
optimization ?: NormalizedOptimizationOptions ;
41
42
cache ?: NormalizedCachedOptions ;
42
43
imageDomains ?: string [ ] ;
44
+ generateDedicatedSSRContent ?: boolean ;
43
45
}
44
46
45
47
export type IndexHtmlTransform = ( content : string ) => Promise < string > ;
46
48
47
- export interface IndexHtmlTransformResult {
49
+ export interface IndexHtmlPluginTransformResult {
48
50
content : string ;
49
51
warnings : string [ ] ;
50
52
errors : string [ ] ;
51
53
}
52
54
55
+ export interface IndexHtmlProcessResult {
56
+ csrContent : string ;
57
+ ssrContent ?: string ;
58
+ warnings : string [ ] ;
59
+ errors : string [ ] ;
60
+ }
61
+
53
62
export class IndexHtmlGenerator {
54
63
private readonly plugins : IndexHtmlGeneratorPlugin [ ] ;
64
+ private readonly csrPlugins : IndexHtmlGeneratorPlugin [ ] = [ ] ;
65
+ private readonly ssrPlugins : IndexHtmlGeneratorPlugin [ ] = [ ] ;
55
66
56
67
constructor ( readonly options : IndexHtmlGeneratorOptions ) {
57
- const extraPlugins : IndexHtmlGeneratorPlugin [ ] = [ ] ;
58
- if ( this . options . optimization ?. fonts . inline ) {
59
- extraPlugins . push ( inlineFontsPlugin ( this ) ) ;
68
+ const extraCommonPlugins : IndexHtmlGeneratorPlugin [ ] = [ ] ;
69
+ if ( options ? .optimization ?. fonts . inline ) {
70
+ extraCommonPlugins . push ( inlineFontsPlugin ( this ) , addNonce ) ;
60
71
}
61
72
62
- if ( this . options . optimization ?. styles . inlineCritical ) {
63
- extraPlugins . push ( inlineCriticalCssPlugin ( this ) ) ;
73
+ // Common plugins
74
+ this . plugins = [ augmentIndexHtmlPlugin ( this ) , ...extraCommonPlugins , postTransformPlugin ( this ) ] ;
75
+
76
+ // CSR plugins
77
+ if ( options ?. optimization ?. styles ?. inlineCritical ) {
78
+ this . csrPlugins . push ( inlineCriticalCssPlugin ( this ) ) ;
64
79
}
65
80
66
- this . plugins = [
67
- augmentIndexHtmlPlugin ( this ) ,
68
- ...extraPlugins ,
69
- // Runs after the `extraPlugins` to capture any nonce or
70
- // `style` tags that might've been added by them.
71
- addStyleNoncePlugin ( ) ,
72
- postTransformPlugin ( this ) ,
73
- ] ;
81
+ // SSR plugins
82
+ if ( options . generateDedicatedSSRContent ) {
83
+ this . ssrPlugins . push ( addEventDispatchContractPlugin ( ) , addNoncePlugin ( ) ) ;
84
+ }
74
85
}
75
86
76
- async process ( options : IndexHtmlGeneratorProcessOptions ) : Promise < IndexHtmlTransformResult > {
87
+ async process ( options : IndexHtmlGeneratorProcessOptions ) : Promise < IndexHtmlProcessResult > {
77
88
let content = await this . readIndex ( this . options . indexPath ) ;
78
89
const warnings : string [ ] = [ ] ;
79
90
const errors : string [ ] = [ ] ;
80
91
81
- for ( const plugin of this . plugins ) {
92
+ content = await this . runPlugins ( content , this . plugins , options , warnings , errors ) ;
93
+ const [ csrContent , ssrContent ] = await Promise . all ( [
94
+ this . runPlugins ( content , this . csrPlugins , options , warnings , errors ) ,
95
+ this . ssrPlugins . length
96
+ ? this . runPlugins ( content , this . ssrPlugins , options , warnings , errors )
97
+ : undefined ,
98
+ ] ) ;
99
+
100
+ return {
101
+ ssrContent,
102
+ csrContent,
103
+ warnings,
104
+ errors,
105
+ } ;
106
+ }
107
+
108
+ private async runPlugins (
109
+ content : string ,
110
+ plugins : IndexHtmlGeneratorPlugin [ ] ,
111
+ options : IndexHtmlGeneratorProcessOptions ,
112
+ warnings : string [ ] ,
113
+ errors : string [ ] ,
114
+ ) : Promise < string > {
115
+ for ( const plugin of plugins ) {
82
116
const result = await plugin ( content , options ) ;
83
117
if ( typeof result === 'string' ) {
84
118
content = result ;
@@ -95,11 +129,7 @@ export class IndexHtmlGenerator {
95
129
}
96
130
}
97
131
98
- return {
99
- content,
100
- warnings,
101
- errors,
102
- } ;
132
+ return content ;
103
133
}
104
134
105
135
async readAsset ( path : string ) : Promise < string > {
@@ -160,10 +190,14 @@ function inlineCriticalCssPlugin(generator: IndexHtmlGenerator): IndexHtmlGenera
160
190
inlineCriticalCssProcessor . process ( html , { outputPath : options . outputPath } ) ;
161
191
}
162
192
163
- function addStyleNoncePlugin ( ) : IndexHtmlGeneratorPlugin {
164
- return ( html ) => addStyleNonce ( html ) ;
193
+ function addNoncePlugin ( ) : IndexHtmlGeneratorPlugin {
194
+ return ( html ) => addNonce ( html ) ;
165
195
}
166
196
167
197
function postTransformPlugin ( { options } : IndexHtmlGenerator ) : IndexHtmlGeneratorPlugin {
168
198
return async ( html ) => ( options . postTransform ? options . postTransform ( html ) : html ) ;
169
199
}
200
+
201
+ function addEventDispatchContractPlugin ( ) : IndexHtmlGeneratorPlugin {
202
+ return ( html ) => addEventDispatchContract ( html ) ;
203
+ }