1
+ // @ts -check
1
2
'use strict' ;
2
3
3
4
// use Polyfill for util.promisify in node versions < v8
4
5
const promisify = require ( 'util.promisify' ) ;
5
6
7
+ // Import types
8
+ /* eslint-disable */
9
+ /// <reference path="./index.d.ts" />
10
+ /* eslint-enable */
11
+ /** @typedef {import("webpack/lib/Compiler.js") } WebpackCompiler */
12
+ /** @typedef {import("webpack/lib/Compilation.js") } WebpackCompilation */
13
+
6
14
const vm = require ( 'vm' ) ;
7
15
const fs = require ( 'fs' ) ;
8
16
const _ = require ( 'lodash' ) ;
@@ -20,10 +28,17 @@ const fsStatAsync = promisify(fs.stat);
20
28
const fsReadFileAsync = promisify ( fs . readFile ) ;
21
29
22
30
class HtmlWebpackPlugin {
31
+ /**
32
+ * @param {Partial<HtmlWebpackPluginOptions> } options
33
+ */
23
34
constructor ( options ) {
24
35
// Default options
36
+ /**
37
+ * @type {HtmlWebpackPluginOptions }
38
+ */
25
39
this . options = _ . extend ( {
26
40
template : path . join ( __dirname , 'default_index.ejs' ) ,
41
+ templateContent : undefined ,
27
42
templateParameters : templateParametersGenerator ,
28
43
filename : 'index.html' ,
29
44
hash : false ,
@@ -40,8 +55,18 @@ class HtmlWebpackPlugin {
40
55
title : 'Webpack App' ,
41
56
xhtml : false
42
57
} , options ) ;
58
+ // Instance variables to keep caching information
59
+ // for multiple builds
60
+ this . childCompilerHash = undefined ;
61
+ this . childCompilationOutputName = undefined ;
62
+ this . assetJson = undefined ;
63
+ this . hash = undefined ;
43
64
}
44
65
66
+ /**
67
+ * apply is called by the webpack main compiler during the start phase
68
+ * @param {WebpackCompiler } compiler
69
+ */
45
70
apply ( compiler ) {
46
71
const self = this ;
47
72
let isCompilationCached = false ;
@@ -56,8 +81,12 @@ class HtmlWebpackPlugin {
56
81
this . options . filename = path . relative ( compiler . options . output . path , filename ) ;
57
82
}
58
83
59
- // setup hooks for webpack 4
84
+ // setup hooks for third party plugins
60
85
compiler . hooks . compilation . tap ( 'HtmlWebpackPluginHooks' , compilation => {
86
+ // Setup the hooks only once
87
+ if ( compilation . hooks . htmlWebpackPluginAlterChunks ) {
88
+ return ;
89
+ }
61
90
compilation . hooks . htmlWebpackPluginAlterChunks = new SyncWaterfallHook ( [ 'chunks' , 'objectWithPluginRef' ] ) ;
62
91
compilation . hooks . htmlWebpackPluginBeforeHtmlGeneration = new AsyncSeriesWaterfallHook ( [ 'pluginArgs' ] ) ;
63
92
compilation . hooks . htmlWebpackPluginBeforeHtmlProcessing = new AsyncSeriesWaterfallHook ( [ 'pluginArgs' ] ) ;
@@ -86,7 +115,13 @@ class HtmlWebpackPlugin {
86
115
} ) ;
87
116
} ) ;
88
117
89
- compiler . hooks . emit . tapAsync ( 'HtmlWebpackPlugin' , ( compilation , callback ) => {
118
+ compiler . hooks . emit . tapAsync ( 'HtmlWebpackPlugin' ,
119
+ /**
120
+ * Hook into the webpack emit phase
121
+ * @param {WebpackCompilation } compilation
122
+ * @param {() => void } callback
123
+ */
124
+ ( compilation , callback ) => {
90
125
const applyPluginsAsyncWaterfall = self . applyPluginsAsyncWaterfall ( compilation ) ;
91
126
// Get chunks info as json
92
127
// Note: we're excluding stuff that we don't need to improve toJson serialization speed.
@@ -175,7 +210,7 @@ class HtmlWebpackPlugin {
175
210
const html = result . html ;
176
211
const assets = result . assets ;
177
212
// Prepare script and link tags
178
- const assetTags = self . generateHtmlTags ( assets ) ;
213
+ const assetTags = self . generateHtmlTagObjects ( assets ) ;
179
214
const pluginArgs = { head : assetTags . head , body : assetTags . body , plugin : self , chunks : chunks , outputName : self . childCompilationOutputName } ;
180
215
// Allow plugins to change the assetTag definitions
181
216
return applyPluginsAsyncWaterfall ( 'htmlWebpackPluginAlterAssetTags' , true , pluginArgs )
@@ -252,6 +287,8 @@ class HtmlWebpackPlugin {
252
287
253
288
/**
254
289
* Generate the template parameters for the template function
290
+ * @param {WebpackCompilation } compilation
291
+ *
255
292
*/
256
293
getTemplateParameters ( compilation , assets ) {
257
294
if ( typeof this . options . templateParameters === 'function' ) {
@@ -266,22 +303,21 @@ class HtmlWebpackPlugin {
266
303
/**
267
304
* Html post processing
268
305
*
269
- * Returns a promise
306
+ * @returns Promise<string>
270
307
*/
271
308
executeTemplate ( templateFunction , chunks , assets , compilation ) {
272
- return Promise . resolve ( )
273
- // Template processing
274
- . then ( ( ) => {
275
- const templateParams = this . getTemplateParameters ( compilation , assets ) ;
276
- let html = '' ;
277
- try {
278
- html = templateFunction ( templateParams ) ;
279
- } catch ( e ) {
280
- compilation . errors . push ( new Error ( 'Template execution failed: ' + e ) ) ;
281
- return Promise . reject ( e ) ;
282
- }
283
- return html ;
284
- } ) ;
309
+ // Template processing
310
+ const templateParams = this . getTemplateParameters ( compilation , assets ) ;
311
+ let html = '' ;
312
+ try {
313
+ html = templateFunction ( templateParams ) ;
314
+ } catch ( e ) {
315
+ compilation . errors . push ( new Error ( 'Template execution failed: ' + e ) ) ;
316
+ return Promise . reject ( e ) ;
317
+ }
318
+ // If html is a promise return the promise
319
+ // If html is a string turn it into a promise
320
+ return Promise . resolve ( ) . then ( ( ) => html ) ;
285
321
}
286
322
287
323
/**
@@ -290,31 +326,32 @@ class HtmlWebpackPlugin {
290
326
* Returns a promise
291
327
*/
292
328
postProcessHtml ( html , assets , assetTags ) {
293
- const self = this ;
294
329
if ( typeof html !== 'string' ) {
295
330
return Promise . reject ( 'Expected html to be a string but got ' + JSON . stringify ( html ) ) ;
296
331
}
297
332
return Promise . resolve ( )
298
333
// Inject
299
334
. then ( ( ) => {
300
- if ( self . options . inject ) {
301
- return self . injectAssetsIntoHtml ( html , assets , assetTags ) ;
335
+ if ( this . options . inject ) {
336
+ return this . injectAssetsIntoHtml ( html , assets , assetTags ) ;
302
337
} else {
303
338
return html ;
304
339
}
305
340
} )
306
341
// Minify
307
342
. then ( html => {
308
- if ( self . options . minify ) {
343
+ if ( this . options . minify ) {
309
344
const minify = require ( 'html-minifier' ) . minify ;
310
- return minify ( html , self . options . minify ) ;
345
+ return minify ( html , this . options . minify === true ? { } : this . options . minify ) ;
311
346
}
312
347
return html ;
313
348
} ) ;
314
349
}
315
350
316
351
/*
317
352
* Pushes the content of the given filename to the compilation assets
353
+ * @param {string } filename
354
+ * @param {WebpackCompilation } compilation
318
355
*/
319
356
addFileToAssets ( filename , compilation ) {
320
357
filename = path . resolve ( compilation . compiler . context , filename ) ;
@@ -357,6 +394,9 @@ class HtmlWebpackPlugin {
357
394
358
395
/**
359
396
* Return all chunks from the compilation result which match the exclude and include filters
397
+ * @param {any } chunks
398
+ * @param {string[]|'all' } includedChunks
399
+ * @param {string[] } excludedChunks
360
400
*/
361
401
filterChunks ( chunks , includedChunks , excludedChunks ) {
362
402
return chunks . filter ( chunk => {
@@ -391,15 +431,14 @@ class HtmlWebpackPlugin {
391
431
}
392
432
393
433
htmlWebpackPluginAssets ( compilation , chunks ) {
394
- const self = this ;
395
434
const compilationHash = compilation . hash ;
396
435
397
436
// Use the configured public path or build a relative path
398
437
let publicPath = typeof compilation . options . output . publicPath !== 'undefined'
399
438
// If a hard coded public path exists use it
400
439
? compilation . mainTemplate . getPublicPath ( { hash : compilationHash } )
401
440
// If no public path was set get a relative url path
402
- : path . relative ( path . resolve ( compilation . options . output . path , path . dirname ( self . childCompilationOutputName ) ) , compilation . options . output . path )
441
+ : path . relative ( path . resolve ( compilation . options . output . path , path . dirname ( this . childCompilationOutputName ) ) , compilation . options . output . path )
403
442
. split ( path . sep ) . join ( '/' ) ;
404
443
405
444
if ( publicPath . length && publicPath . substr ( - 1 , 1 ) !== '/' ) {
@@ -421,8 +460,8 @@ class HtmlWebpackPlugin {
421
460
422
461
// Append a hash for cache busting
423
462
if ( this . options . hash ) {
424
- assets . manifest = self . appendHash ( assets . manifest , compilationHash ) ;
425
- assets . favicon = self . appendHash ( assets . favicon , compilationHash ) ;
463
+ assets . manifest = this . appendHash ( assets . manifest , compilationHash ) ;
464
+ assets . favicon = this . appendHash ( assets . favicon , compilationHash ) ;
426
465
}
427
466
428
467
for ( let i = 0 ; i < chunks . length ; i ++ ) {
@@ -436,7 +475,7 @@ class HtmlWebpackPlugin {
436
475
437
476
// Append a hash for cache busting
438
477
if ( this . options . hash ) {
439
- chunkFiles = chunkFiles . map ( chunkFile => self . appendHash ( chunkFile , compilationHash ) ) ;
478
+ chunkFiles = chunkFiles . map ( chunkFile => this . appendHash ( chunkFile , compilationHash ) ) ;
440
479
}
441
480
442
481
// Webpack outputs an array for each chunk when using sourcemaps
@@ -464,9 +503,11 @@ class HtmlWebpackPlugin {
464
503
465
504
/**
466
505
* Generate meta tags
506
+ * @returns {HtmlTagObject[] }
467
507
*/
468
508
getMetaTags ( ) {
469
- if ( this . options . meta === false ) {
509
+ const metaOptions = this . options . meta ;
510
+ if ( metaOptions === false ) {
470
511
return [ ] ;
471
512
}
472
513
// Make tags self-closing in case of xhtml
@@ -493,7 +534,19 @@ class HtmlWebpackPlugin {
493
534
}
494
535
495
536
/**
496
- * Injects the assets into the given html string
537
+ * Turns the given asset information into tag object representations
538
+ * which is seperated into head and body
539
+ *
540
+ * @param {{
541
+ js: {entryName: string, path: string}[],
542
+ css: {entryName: string, path: string}[],
543
+ favicon?: string
544
+ }} assets
545
+ *
546
+ * @returns {{
547
+ head: HtmlTagObject[],
548
+ body: HtmlTagObject[]
549
+ }}
497
550
*/
498
551
generateHtmlTags ( assets ) {
499
552
// Turn script files into script tags
@@ -546,6 +599,17 @@ class HtmlWebpackPlugin {
546
599
547
600
/**
548
601
* Injects the assets into the given html string
602
+ *
603
+ * @param {string } html
604
+ * @param {any } assets
605
+ * The input html
606
+ * @param {{
607
+ head: HtmlTagObject[],
608
+ body: HtmlTagObject[]
609
+ }} assetTags
610
+ * The asset tags to inject
611
+ *
612
+ * @returns {string }
549
613
*/
550
614
injectAssetsIntoHtml ( html , assets , assetTags ) {
551
615
const htmlRegExp = / ( < h t m l [ ^ > ] * > ) / i;
@@ -592,7 +656,10 @@ class HtmlWebpackPlugin {
592
656
}
593
657
594
658
/**
595
- * Appends a cache busting hash
659
+ * Appends a cache busting hash to the query string of the url
660
+ * E.g. http://localhost:8080/ -> http://localhost:8080/?50c9096ba6183fd728eeb065a26ec175
661
+ * @param {string } url
662
+ * @param {string } hash
596
663
*/
597
664
appendHash ( url , hash ) {
598
665
if ( ! url ) {
@@ -603,6 +670,10 @@ class HtmlWebpackPlugin {
603
670
604
671
/**
605
672
* Helper to return the absolute template path with a fallback loader
673
+ * @param {string } template
674
+ * The path to the tempalate e.g. './index.html'
675
+ * @param {string } context
676
+ * The webpack base resolution path for relative paths e.g. process.cwd()
606
677
*/
607
678
getFullTemplatePath ( template , context ) {
608
679
// If the template doesn't use a loader use the lodash template loader
@@ -628,6 +699,9 @@ class HtmlWebpackPlugin {
628
699
/**
629
700
* Helper to promisify compilation.applyPluginsAsyncWaterfall that returns
630
701
* a function that helps to merge given plugin arguments with processed ones
702
+ *
703
+ * @param {WebpackCompilation } compilation
704
+ *
631
705
*/
632
706
applyPluginsAsyncWaterfall ( compilation ) {
633
707
return ( eventName , requiresResult , pluginArgs ) => {
0 commit comments