@@ -10,6 +10,7 @@ import { createHash } from 'crypto';
10
10
import { RawSource , ReplaceSource } from 'webpack-sources' ;
11
11
12
12
const parse5 = require ( 'parse5' ) ;
13
+ const treeAdapter = require ( 'parse5-htmlparser2-tree-adapter' ) ;
13
14
14
15
export type LoadOutputFileFunctionType = ( file : string ) => Promise < string > ;
15
16
@@ -90,53 +91,27 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
90
91
}
91
92
92
93
// Find the head and body elements
93
- const treeAdapter = parse5 . treeAdapters . default ;
94
- const document = parse5 . parse ( params . inputContent , { treeAdapter, locationInfo : true } ) ;
95
- let headElement ;
96
- let bodyElement ;
97
- let htmlElement ;
98
- for ( const docChild of document . childNodes ) {
99
- if ( docChild . tagName === 'html' ) {
100
- htmlElement = docChild ;
101
- for ( const htmlChild of docChild . childNodes ) {
102
- if ( htmlChild . tagName === 'head' ) {
103
- headElement = htmlChild ;
104
- } else if ( htmlChild . tagName === 'body' ) {
105
- bodyElement = htmlChild ;
106
- }
107
- }
108
- }
109
- }
94
+ const document = parse5 . parse ( params . inputContent , {
95
+ treeAdapter,
96
+ sourceCodeLocationInfo : true ,
97
+ } ) ;
98
+
99
+ // tslint:disable: no-any
100
+ const htmlElement = document . children . find ( ( c : any ) => c . name === 'html' ) ;
101
+ const headElement = htmlElement . children . find ( ( c : any ) => c . name === 'head' ) ;
102
+ const bodyElement = htmlElement . children . find ( ( c : any ) => c . name === 'body' ) ;
103
+ // tslint:enable: no-any
110
104
111
105
if ( ! headElement || ! bodyElement ) {
112
106
throw new Error ( 'Missing head and/or body elements' ) ;
113
107
}
114
108
115
- // Determine script insertion point
116
- let scriptInsertionPoint ;
117
- if ( bodyElement . __location && bodyElement . __location . endTag ) {
118
- scriptInsertionPoint = bodyElement . __location . endTag . startOffset ;
119
- } else {
120
- // Less accurate fallback
121
- // parse5 4.x does not provide locations if malformed html is present
122
- scriptInsertionPoint = params . inputContent . indexOf ( '</body>' ) ;
123
- }
124
-
125
- let styleInsertionPoint ;
126
- if ( headElement . __location && headElement . __location . endTag ) {
127
- styleInsertionPoint = headElement . __location . endTag . startOffset ;
128
- } else {
129
- // Less accurate fallback
130
- // parse5 4.x does not provide locations if malformed html is present
131
- styleInsertionPoint = params . inputContent . indexOf ( '</head>' ) ;
132
- }
133
-
134
109
// Inject into the html
135
110
const indexSource = new ReplaceSource ( new RawSource ( params . inputContent ) , params . input ) ;
136
111
137
- let scriptElements = '' ;
112
+ const scriptsElements = treeAdapter . createDocumentFragment ( ) ;
138
113
for ( const script of scripts ) {
139
- const attrs : { name : string ; value : string | null } [ ] = [
114
+ const attrs : { name : string ; value : string } [ ] = [
140
115
{ name : 'src' , value : ( params . deployUrl || '' ) + script } ,
141
116
] ;
142
117
@@ -156,69 +131,55 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
156
131
157
132
if ( isNoModuleType && ! isModuleType ) {
158
133
attrs . push (
159
- { name : 'nomodule' , value : null } ,
160
- { name : 'defer' , value : null } ,
134
+ { name : 'nomodule' , value : '' } ,
135
+ { name : 'defer' , value : '' } ,
161
136
) ;
162
137
} else if ( isModuleType && ! isNoModuleType ) {
163
138
attrs . push ( { name : 'type' , value : 'module' } ) ;
164
139
} else {
165
- attrs . push ( { name : 'defer' , value : null } ) ;
140
+ attrs . push ( { name : 'defer' , value : '' } ) ;
166
141
}
167
142
} else {
168
- attrs . push ( { name : 'defer' , value : null } ) ;
143
+ attrs . push ( { name : 'defer' , value : '' } ) ;
169
144
}
170
145
171
146
if ( params . sri ) {
172
147
const content = await loadOutputFile ( script ) ;
173
- attrs . push ( ... _generateSriAttributes ( content ) ) ;
148
+ attrs . push ( _generateSriAttributes ( content ) ) ;
174
149
}
175
150
176
- const attributes = attrs
177
- . map ( attr => ( attr . value === null ? attr . name : `${ attr . name } ="${ attr . value } "` ) )
178
- . join ( ' ' ) ;
179
- scriptElements += `<script ${ attributes } ></script>` ;
151
+ const baseElement = treeAdapter . createElement ( 'script' , undefined , attrs ) ;
152
+ treeAdapter . setTemplateContent ( scriptsElements , baseElement ) ;
180
153
}
181
154
182
- indexSource . insert ( scriptInsertionPoint , scriptElements ) ;
155
+ indexSource . insert (
156
+ // parse5 does not provide locations if malformed html is present
157
+ bodyElement . sourceCodeLocation ?. endTag ?. startOffset || params . inputContent . indexOf ( '</body>' ) ,
158
+ parse5 . serialize ( scriptsElements , { treeAdapter } ) . replace ( / \= " " / g, '' ) ,
159
+ ) ;
183
160
184
161
// Adjust base href if specified
185
162
if ( typeof params . baseHref == 'string' ) {
186
- let baseElement ;
187
- for ( const headChild of headElement . childNodes ) {
188
- if ( headChild . tagName === 'base' ) {
189
- baseElement = headChild ;
190
- }
191
- }
192
-
163
+ // tslint:disable-next-line: no-any
164
+ let baseElement = headElement . children . find ( ( t : any ) => t . name === 'base' ) ;
193
165
const baseFragment = treeAdapter . createDocumentFragment ( ) ;
194
166
195
167
if ( ! baseElement ) {
196
168
baseElement = treeAdapter . createElement ( 'base' , undefined , [
197
169
{ name : 'href' , value : params . baseHref } ,
198
170
] ) ;
199
171
200
- treeAdapter . appendChild ( baseFragment , baseElement ) ;
172
+ treeAdapter . setTemplateContent ( baseFragment , baseElement ) ;
201
173
indexSource . insert (
202
- headElement . __location . startTag . endOffset ,
174
+ headElement . sourceCodeLocation . startTag . endOffset ,
203
175
parse5 . serialize ( baseFragment , { treeAdapter } ) ,
204
176
) ;
205
177
} else {
206
- let hrefAttribute ;
207
- for ( const attribute of baseElement . attrs ) {
208
- if ( attribute . name === 'href' ) {
209
- hrefAttribute = attribute ;
210
- }
211
- }
212
- if ( hrefAttribute ) {
213
- hrefAttribute . value = params . baseHref ;
214
- } else {
215
- baseElement . attrs . push ( { name : 'href' , value : params . baseHref } ) ;
216
- }
217
-
218
- treeAdapter . appendChild ( baseFragment , baseElement ) ;
178
+ baseElement . attribs [ 'href' ] = params . baseHref ;
179
+ treeAdapter . setTemplateContent ( baseFragment , baseElement ) ;
219
180
indexSource . replace (
220
- baseElement . __location . startOffset ,
221
- baseElement . __location . endOffset ,
181
+ baseElement . sourceCodeLocation . startOffset ,
182
+ baseElement . sourceCodeLocation . endOffset ,
222
183
parse5 . serialize ( baseFragment , { treeAdapter } ) ,
223
184
) ;
224
185
}
@@ -237,38 +198,31 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
237
198
238
199
if ( params . sri ) {
239
200
const content = await loadOutputFile ( stylesheet ) ;
240
- attrs . push ( ... _generateSriAttributes ( content ) ) ;
201
+ attrs . push ( _generateSriAttributes ( content ) ) ;
241
202
}
242
203
243
204
const element = treeAdapter . createElement ( 'link' , undefined , attrs ) ;
244
- treeAdapter . appendChild ( styleElements , element ) ;
205
+ treeAdapter . setTemplateContent ( styleElements , element ) ;
245
206
}
246
207
247
- indexSource . insert ( styleInsertionPoint , parse5 . serialize ( styleElements , { treeAdapter } ) ) ;
208
+ indexSource . insert (
209
+ // parse5 does not provide locations if malformed html is present
210
+ headElement . sourceCodeLocation ?. endTag ?. startOffset || params . inputContent . indexOf ( '</head>' ) ,
211
+ parse5 . serialize ( styleElements , { treeAdapter } ) ,
212
+ ) ;
248
213
249
214
// Adjust document locale if specified
250
215
if ( typeof params . lang == 'string' ) {
251
-
252
216
const htmlFragment = treeAdapter . createDocumentFragment ( ) ;
217
+ htmlElement . attribs [ 'lang' ] = params . lang ;
253
218
254
- let langAttribute ;
255
- for ( const attribute of htmlElement . attrs ) {
256
- if ( attribute . name === 'lang' ) {
257
- langAttribute = attribute ;
258
- }
259
- }
260
- if ( langAttribute ) {
261
- langAttribute . value = params . lang ;
262
- } else {
263
- htmlElement . attrs . push ( { name : 'lang' , value : params . lang } ) ;
264
- }
265
219
// we want only openning tag
266
- htmlElement . childNodes = [ ] ;
220
+ htmlElement . children = [ ] ;
267
221
268
- treeAdapter . appendChild ( htmlFragment , htmlElement ) ;
222
+ treeAdapter . setTemplateContent ( htmlFragment , htmlElement ) ;
269
223
indexSource . replace (
270
- htmlElement . __location . startTag . startOffset ,
271
- htmlElement . __location . startTag . endOffset - 1 ,
224
+ htmlElement . sourceCodeLocation . startTag . startOffset ,
225
+ htmlElement . sourceCodeLocation . startTag . endOffset - 1 ,
272
226
parse5 . serialize ( htmlFragment , { treeAdapter } ) . replace ( '</html>' , '' ) ,
273
227
) ;
274
228
}
@@ -282,5 +236,5 @@ function _generateSriAttributes(content: string) {
282
236
. update ( content , 'utf8' )
283
237
. digest ( 'base64' ) ;
284
238
285
- return [ { name : 'integrity' , value : `${ algo } -${ hash } ` } ] ;
239
+ return { name : 'integrity' , value : `${ algo } -${ hash } ` } ;
286
240
}
0 commit comments