@@ -10,6 +10,7 @@ import { createHash } from 'crypto';
1010import { RawSource , ReplaceSource } from 'webpack-sources' ;
1111
1212const parse5 = require ( 'parse5' ) ;
13+ const treeAdapter = require ( 'parse5-htmlparser2-tree-adapter' ) ;
1314
1415export type LoadOutputFileFunctionType = ( file : string ) => Promise < string > ;
1516
@@ -90,53 +91,27 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
9091 }
9192
9293 // 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
110104
111105 if ( ! headElement || ! bodyElement ) {
112106 throw new Error ( 'Missing head and/or body elements' ) ;
113107 }
114108
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-
134109 // Inject into the html
135110 const indexSource = new ReplaceSource ( new RawSource ( params . inputContent ) , params . input ) ;
136111
137- let scriptElements = '' ;
112+ const scriptsElements = treeAdapter . createDocumentFragment ( ) ;
138113 for ( const script of scripts ) {
139- const attrs : { name : string ; value : string | null } [ ] = [
114+ const attrs : { name : string ; value : string } [ ] = [
140115 { name : 'src' , value : ( params . deployUrl || '' ) + script } ,
141116 ] ;
142117
@@ -156,69 +131,55 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
156131
157132 if ( isNoModuleType && ! isModuleType ) {
158133 attrs . push (
159- { name : 'nomodule' , value : null } ,
160- { name : 'defer' , value : null } ,
134+ { name : 'nomodule' , value : '' } ,
135+ { name : 'defer' , value : '' } ,
161136 ) ;
162137 } else if ( isModuleType && ! isNoModuleType ) {
163138 attrs . push ( { name : 'type' , value : 'module' } ) ;
164139 } else {
165- attrs . push ( { name : 'defer' , value : null } ) ;
140+ attrs . push ( { name : 'defer' , value : '' } ) ;
166141 }
167142 } else {
168- attrs . push ( { name : 'defer' , value : null } ) ;
143+ attrs . push ( { name : 'defer' , value : '' } ) ;
169144 }
170145
171146 if ( params . sri ) {
172147 const content = await loadOutputFile ( script ) ;
173- attrs . push ( ... _generateSriAttributes ( content ) ) ;
148+ attrs . push ( _generateSriAttributes ( content ) ) ;
174149 }
175150
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 ) ;
180153 }
181154
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+ ) ;
183160
184161 // Adjust base href if specified
185162 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' ) ;
193165 const baseFragment = treeAdapter . createDocumentFragment ( ) ;
194166
195167 if ( ! baseElement ) {
196168 baseElement = treeAdapter . createElement ( 'base' , undefined , [
197169 { name : 'href' , value : params . baseHref } ,
198170 ] ) ;
199171
200- treeAdapter . appendChild ( baseFragment , baseElement ) ;
172+ treeAdapter . setTemplateContent ( baseFragment , baseElement ) ;
201173 indexSource . insert (
202- headElement . __location . startTag . endOffset ,
174+ headElement . sourceCodeLocation . startTag . endOffset ,
203175 parse5 . serialize ( baseFragment , { treeAdapter } ) ,
204176 ) ;
205177 } 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 ) ;
219180 indexSource . replace (
220- baseElement . __location . startOffset ,
221- baseElement . __location . endOffset ,
181+ baseElement . sourceCodeLocation . startOffset ,
182+ baseElement . sourceCodeLocation . endOffset ,
222183 parse5 . serialize ( baseFragment , { treeAdapter } ) ,
223184 ) ;
224185 }
@@ -237,38 +198,31 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
237198
238199 if ( params . sri ) {
239200 const content = await loadOutputFile ( stylesheet ) ;
240- attrs . push ( ... _generateSriAttributes ( content ) ) ;
201+ attrs . push ( _generateSriAttributes ( content ) ) ;
241202 }
242203
243204 const element = treeAdapter . createElement ( 'link' , undefined , attrs ) ;
244- treeAdapter . appendChild ( styleElements , element ) ;
205+ treeAdapter . setTemplateContent ( styleElements , element ) ;
245206 }
246207
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+ ) ;
248213
249214 // Adjust document locale if specified
250215 if ( typeof params . lang == 'string' ) {
251-
252216 const htmlFragment = treeAdapter . createDocumentFragment ( ) ;
217+ htmlElement . attribs [ 'lang' ] = params . lang ;
253218
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- }
265219 // we want only openning tag
266- htmlElement . childNodes = [ ] ;
220+ htmlElement . children = [ ] ;
267221
268- treeAdapter . appendChild ( htmlFragment , htmlElement ) ;
222+ treeAdapter . setTemplateContent ( htmlFragment , htmlElement ) ;
269223 indexSource . replace (
270- htmlElement . __location . startTag . startOffset ,
271- htmlElement . __location . startTag . endOffset - 1 ,
224+ htmlElement . sourceCodeLocation . startTag . startOffset ,
225+ htmlElement . sourceCodeLocation . startTag . endOffset - 1 ,
272226 parse5 . serialize ( htmlFragment , { treeAdapter } ) . replace ( '</html>' , '' ) ,
273227 ) ;
274228 }
@@ -282,5 +236,5 @@ function _generateSriAttributes(content: string) {
282236 . update ( content , 'utf8' )
283237 . digest ( 'base64' ) ;
284238
285- return [ { name : 'integrity' , value : `${ algo } -${ hash } ` } ] ;
239+ return { name : 'integrity' , value : `${ algo } -${ hash } ` } ;
286240}
0 commit comments