html-webpack-plugin 3.2.0 → 4.0.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -18
- package/default_index.ejs +1 -1
- package/index.js +298 -336
- package/lib/chunksorter.js +26 -118
- package/lib/compiler.js +5 -16
- package/lib/errors.js +2 -1
- package/lib/hooks.js +132 -0
- package/lib/html-tags.js +73 -0
- package/lib/loader.js +4 -4
- package/package.json +11 -8
package/index.js
CHANGED
|
@@ -1,24 +1,44 @@
|
|
|
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="./typings.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');
|
|
9
17
|
const path = require('path');
|
|
18
|
+
|
|
19
|
+
const htmlTagObjectToString = require('./lib/html-tags').htmlTagObjectToString;
|
|
20
|
+
|
|
10
21
|
const childCompiler = require('./lib/compiler.js');
|
|
11
22
|
const prettyError = require('./lib/errors.js');
|
|
12
23
|
const chunkSorter = require('./lib/chunksorter.js');
|
|
24
|
+
const getHtmlWebpackPluginHooks = require('./lib/hooks.js').getHtmlWebpackPluginHooks;
|
|
25
|
+
const getHtmlWebpackPluginHook = require('./lib/hooks.js').getHtmlWebpackPluginHook;
|
|
13
26
|
|
|
14
27
|
const fsStatAsync = promisify(fs.stat);
|
|
15
28
|
const fsReadFileAsync = promisify(fs.readFile);
|
|
16
29
|
|
|
17
30
|
class HtmlWebpackPlugin {
|
|
31
|
+
/**
|
|
32
|
+
* @param {Partial<HtmlWebpackPluginOptions>} options
|
|
33
|
+
*/
|
|
18
34
|
constructor (options) {
|
|
19
35
|
// Default options
|
|
20
|
-
|
|
36
|
+
/**
|
|
37
|
+
* @type {HtmlWebpackPluginOptions}
|
|
38
|
+
*/
|
|
39
|
+
this.options = Object.assign({
|
|
21
40
|
template: path.join(__dirname, 'default_index.ejs'),
|
|
41
|
+
templateContent: false,
|
|
22
42
|
templateParameters: templateParametersGenerator,
|
|
23
43
|
filename: 'index.html',
|
|
24
44
|
hash: false,
|
|
@@ -35,8 +55,22 @@ class HtmlWebpackPlugin {
|
|
|
35
55
|
title: 'Webpack App',
|
|
36
56
|
xhtml: false
|
|
37
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;
|
|
64
|
+
/**
|
|
65
|
+
* The major version number of this plugin
|
|
66
|
+
*/
|
|
67
|
+
this.version = 4;
|
|
38
68
|
}
|
|
39
69
|
|
|
70
|
+
/**
|
|
71
|
+
* apply is called by the webpack main compiler during the start phase
|
|
72
|
+
* @param {WebpackCompiler} compiler
|
|
73
|
+
*/
|
|
40
74
|
apply (compiler) {
|
|
41
75
|
const self = this;
|
|
42
76
|
let isCompilationCached = false;
|
|
@@ -51,22 +85,10 @@ class HtmlWebpackPlugin {
|
|
|
51
85
|
this.options.filename = path.relative(compiler.options.output.path, filename);
|
|
52
86
|
}
|
|
53
87
|
|
|
54
|
-
// setup hooks for
|
|
55
|
-
|
|
56
|
-
compiler.hooks.compilation.tap('HtmlWebpackPluginHooks', compilation => {
|
|
57
|
-
const SyncWaterfallHook = require('tapable').SyncWaterfallHook;
|
|
58
|
-
const AsyncSeriesWaterfallHook = require('tapable').AsyncSeriesWaterfallHook;
|
|
59
|
-
compilation.hooks.htmlWebpackPluginAlterChunks = new SyncWaterfallHook(['chunks', 'objectWithPluginRef']);
|
|
60
|
-
compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration = new AsyncSeriesWaterfallHook(['pluginArgs']);
|
|
61
|
-
compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing = new AsyncSeriesWaterfallHook(['pluginArgs']);
|
|
62
|
-
compilation.hooks.htmlWebpackPluginAlterAssetTags = new AsyncSeriesWaterfallHook(['pluginArgs']);
|
|
63
|
-
compilation.hooks.htmlWebpackPluginAfterHtmlProcessing = new AsyncSeriesWaterfallHook(['pluginArgs']);
|
|
64
|
-
compilation.hooks.htmlWebpackPluginAfterEmit = new AsyncSeriesWaterfallHook(['pluginArgs']);
|
|
65
|
-
});
|
|
66
|
-
}
|
|
88
|
+
// setup hooks for third party plugins
|
|
89
|
+
compiler.hooks.compilation.tap('HtmlWebpackPluginHooks', getHtmlWebpackPluginHooks);
|
|
67
90
|
|
|
68
|
-
|
|
69
|
-
(compiler.hooks ? compiler.hooks.make.tapAsync.bind(compiler.hooks.make, 'HtmlWebpackPlugin') : compiler.plugin.bind(compiler, 'make'))((compilation, callback) => {
|
|
91
|
+
compiler.hooks.make.tapAsync('HtmlWebpackPlugin', (compilation, callback) => {
|
|
70
92
|
// Compile the template (queued)
|
|
71
93
|
compilationPromise = childCompiler.compileTemplate(self.options.template, compiler.context, self.options.filename, compilation)
|
|
72
94
|
.catch(err => {
|
|
@@ -86,144 +108,124 @@ class HtmlWebpackPlugin {
|
|
|
86
108
|
});
|
|
87
109
|
});
|
|
88
110
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
chunks
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const allChunks = compilation.getStats().toJson(chunkOnlyConfig).chunks;
|
|
110
|
-
// Filter chunks (options.chunks and options.excludeCHunks)
|
|
111
|
-
let chunks = self.filterChunks(allChunks, self.options.chunks, self.options.excludeChunks);
|
|
112
|
-
// Sort chunks
|
|
113
|
-
chunks = self.sortChunks(chunks, self.options.chunksSortMode, compilation);
|
|
114
|
-
// Let plugins alter the chunks and the chunk sorting
|
|
115
|
-
if (compilation.hooks) {
|
|
116
|
-
chunks = compilation.hooks.htmlWebpackPluginAlterChunks.call(chunks, { plugin: self });
|
|
117
|
-
} else {
|
|
118
|
-
// Before Webpack 4
|
|
119
|
-
chunks = compilation.applyPluginsWaterfall('html-webpack-plugin-alter-chunks', chunks, { plugin: self });
|
|
120
|
-
}
|
|
121
|
-
// Get assets
|
|
122
|
-
const assets = self.htmlWebpackPluginAssets(compilation, chunks);
|
|
123
|
-
// If this is a hot update compilation, move on!
|
|
124
|
-
// This solves a problem where an `index.html` file is generated for hot-update js files
|
|
125
|
-
// It only happens in Webpack 2, where hot updates are emitted separately before the full bundle
|
|
126
|
-
if (self.isHotUpdateCompilation(assets)) {
|
|
127
|
-
return callback();
|
|
128
|
-
}
|
|
111
|
+
compiler.hooks.emit.tapAsync('HtmlWebpackPlugin',
|
|
112
|
+
/**
|
|
113
|
+
* Hook into the webpack emit phase
|
|
114
|
+
* @param {WebpackCompilation} compilation
|
|
115
|
+
* @param {() => void} callback
|
|
116
|
+
*/
|
|
117
|
+
(compilation, callback) => {
|
|
118
|
+
// Get all entry point names for this html file
|
|
119
|
+
const entryNames = Array.from(compilation.entrypoints.keys());
|
|
120
|
+
const filteredEntryNames = self.filterChunks(entryNames, self.options.chunks, self.options.excludeChunks);
|
|
121
|
+
const sortedEntryNames = self.sortEntryChunks(filteredEntryNames, this.options.chunksSortMode, compilation);
|
|
122
|
+
// Turn the entry point names into file paths
|
|
123
|
+
const assets = self.htmlWebpackPluginAssets(compilation, sortedEntryNames);
|
|
124
|
+
|
|
125
|
+
// If this is a hot update compilation, move on!
|
|
126
|
+
// This solves a problem where an `index.html` file is generated for hot-update js files
|
|
127
|
+
// It only happens in Webpack 2, where hot updates are emitted separately before the full bundle
|
|
128
|
+
if (self.isHotUpdateCompilation(assets)) {
|
|
129
|
+
return callback();
|
|
130
|
+
}
|
|
129
131
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
132
|
+
// If the template and the assets did not change we don't have to emit the html
|
|
133
|
+
const assetJson = JSON.stringify(self.getAssetFiles(assets));
|
|
134
|
+
if (isCompilationCached && self.options.cache && assetJson === self.assetJson) {
|
|
135
|
+
return callback();
|
|
136
|
+
} else {
|
|
137
|
+
self.assetJson = assetJson;
|
|
138
|
+
}
|
|
137
139
|
|
|
138
|
-
|
|
140
|
+
Promise.resolve()
|
|
139
141
|
// Favicon
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
142
|
+
.then(() => {
|
|
143
|
+
if (self.options.favicon) {
|
|
144
|
+
return self.addFileToAssets(self.options.favicon, compilation)
|
|
145
|
+
.then(faviconBasename => {
|
|
146
|
+
let publicPath = compilation.mainTemplate.getPublicPath({hash: compilation.hash}) || '';
|
|
147
|
+
if (publicPath && publicPath.substr(-1) !== '/') {
|
|
148
|
+
publicPath += '/';
|
|
149
|
+
}
|
|
150
|
+
assets.favicon = publicPath + faviconBasename;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
})
|
|
152
154
|
// Wait for the compilation to finish
|
|
153
|
-
|
|
154
|
-
|
|
155
|
+
.then(() => compilationPromise)
|
|
156
|
+
.then(compiledTemplate => {
|
|
155
157
|
// Allow to use a custom function / string instead
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
158
|
+
if (self.options.templateContent !== false) {
|
|
159
|
+
return self.options.templateContent;
|
|
160
|
+
}
|
|
161
|
+
// Once everything is compiled evaluate the html factory
|
|
162
|
+
// and replace it with its content
|
|
163
|
+
return self.evaluateCompilationResult(compilation, compiledTemplate);
|
|
164
|
+
})
|
|
163
165
|
// Allow plugins to make changes to the assets before invoking the template
|
|
164
166
|
// This only makes sense to use if `inject` is `false`
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
167
|
+
.then(compilationResult => getHtmlWebpackPluginHook(compilation, 'htmlWebpackPluginBeforeHtmlGeneration').promise({
|
|
168
|
+
assets: assets,
|
|
169
|
+
outputName: self.childCompilationOutputName,
|
|
170
|
+
plugin: self
|
|
171
|
+
})
|
|
172
|
+
.then(() => compilationResult))
|
|
171
173
|
// Execute the template
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
174
|
+
.then(compilationResult => typeof compilationResult !== 'function'
|
|
175
|
+
? compilationResult
|
|
176
|
+
: self.executeTemplate(compilationResult, assets, compilation))
|
|
175
177
|
// Allow plugins to change the html before assets are injected
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
178
|
+
.then(html => {
|
|
179
|
+
const pluginArgs = {html: html, assets: assets, plugin: self, outputName: self.childCompilationOutputName};
|
|
180
|
+
return getHtmlWebpackPluginHook(compilation, 'htmlWebpackPluginBeforeHtmlProcessing').promise(pluginArgs);
|
|
181
|
+
})
|
|
182
|
+
.then(result => {
|
|
183
|
+
const html = result.html;
|
|
184
|
+
const assets = result.assets;
|
|
185
|
+
// Prepare script and link tags
|
|
186
|
+
const assetTags = self.generateHtmlTagObjects(assets);
|
|
187
|
+
const pluginArgs = {head: assetTags.head, body: assetTags.body, plugin: self, outputName: self.childCompilationOutputName};
|
|
188
|
+
// Allow plugins to change the assetTag definitions
|
|
189
|
+
return getHtmlWebpackPluginHook(compilation, 'htmlWebpackPluginAlterAssetTags').promise(pluginArgs)
|
|
190
|
+
.then(result => self.postProcessHtml(html, assets, { body: result.body, head: result.head })
|
|
191
|
+
.then(html => _.extend(result, {html: html, assets: assets})));
|
|
192
|
+
})
|
|
191
193
|
// Allow plugins to change the html after assets are injected
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
194
|
+
.then(result => {
|
|
195
|
+
const html = result.html;
|
|
196
|
+
const assets = result.assets;
|
|
197
|
+
const pluginArgs = {html: html, assets: assets, plugin: self, outputName: self.childCompilationOutputName};
|
|
198
|
+
return getHtmlWebpackPluginHook(compilation, 'htmlWebpackPluginAfterHtmlProcessing').promise(pluginArgs)
|
|
199
|
+
.then(result => result.html);
|
|
200
|
+
})
|
|
201
|
+
.catch(err => {
|
|
200
202
|
// In case anything went wrong the promise is resolved
|
|
201
203
|
// with the error message and an error is logged
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
204
|
+
compilation.errors.push(prettyError(err, compiler.context).toString());
|
|
205
|
+
// Prevent caching
|
|
206
|
+
self.hash = null;
|
|
207
|
+
return self.options.showErrors ? prettyError(err, compiler.context).toHtml() : 'ERROR';
|
|
208
|
+
})
|
|
209
|
+
.then(html => {
|
|
208
210
|
// Replace the compilation result with the evaluated html code
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
211
|
+
compilation.assets[self.childCompilationOutputName] = {
|
|
212
|
+
source: () => html,
|
|
213
|
+
size: () => html.length
|
|
214
|
+
};
|
|
215
|
+
})
|
|
216
|
+
.then(() => getHtmlWebpackPluginHook(compilation, 'htmlWebpackPluginAfterEmit').promise({
|
|
217
|
+
html: compilation.assets[self.childCompilationOutputName],
|
|
218
|
+
outputName: self.childCompilationOutputName,
|
|
219
|
+
plugin: self
|
|
220
|
+
}).catch(err => {
|
|
221
|
+
console.error(err);
|
|
222
|
+
return null;
|
|
223
|
+
}).then(() => null))
|
|
222
224
|
// Let webpack continue with it
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
225
|
+
.then(() => {
|
|
226
|
+
callback();
|
|
227
|
+
});
|
|
228
|
+
});
|
|
227
229
|
}
|
|
228
230
|
|
|
229
231
|
/**
|
|
@@ -258,6 +260,8 @@ class HtmlWebpackPlugin {
|
|
|
258
260
|
|
|
259
261
|
/**
|
|
260
262
|
* Generate the template parameters for the template function
|
|
263
|
+
* @param {WebpackCompilation} compilation
|
|
264
|
+
*
|
|
261
265
|
*/
|
|
262
266
|
getTemplateParameters (compilation, assets) {
|
|
263
267
|
if (typeof this.options.templateParameters === 'function') {
|
|
@@ -272,22 +276,21 @@ class HtmlWebpackPlugin {
|
|
|
272
276
|
/**
|
|
273
277
|
* Html post processing
|
|
274
278
|
*
|
|
275
|
-
*
|
|
279
|
+
* @returns Promise<string>
|
|
276
280
|
*/
|
|
277
|
-
executeTemplate (templateFunction,
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
});
|
|
281
|
+
executeTemplate (templateFunction, assets, compilation) {
|
|
282
|
+
// Template processing
|
|
283
|
+
const templateParams = this.getTemplateParameters(compilation, assets);
|
|
284
|
+
let html = '';
|
|
285
|
+
try {
|
|
286
|
+
html = templateFunction(templateParams);
|
|
287
|
+
} catch (e) {
|
|
288
|
+
compilation.errors.push(new Error('Template execution failed: ' + e));
|
|
289
|
+
return Promise.reject(e);
|
|
290
|
+
}
|
|
291
|
+
// If html is a promise return the promise
|
|
292
|
+
// If html is a string turn it into a promise
|
|
293
|
+
return Promise.resolve().then(() => html);
|
|
291
294
|
}
|
|
292
295
|
|
|
293
296
|
/**
|
|
@@ -296,24 +299,23 @@ class HtmlWebpackPlugin {
|
|
|
296
299
|
* Returns a promise
|
|
297
300
|
*/
|
|
298
301
|
postProcessHtml (html, assets, assetTags) {
|
|
299
|
-
const self = this;
|
|
300
302
|
if (typeof html !== 'string') {
|
|
301
303
|
return Promise.reject('Expected html to be a string but got ' + JSON.stringify(html));
|
|
302
304
|
}
|
|
303
305
|
return Promise.resolve()
|
|
304
306
|
// Inject
|
|
305
307
|
.then(() => {
|
|
306
|
-
if (
|
|
307
|
-
return
|
|
308
|
+
if (this.options.inject) {
|
|
309
|
+
return this.injectAssetsIntoHtml(html, assets, assetTags);
|
|
308
310
|
} else {
|
|
309
311
|
return html;
|
|
310
312
|
}
|
|
311
313
|
})
|
|
312
314
|
// Minify
|
|
313
315
|
.then(html => {
|
|
314
|
-
if (
|
|
316
|
+
if (this.options.minify) {
|
|
315
317
|
const minify = require('html-minifier').minify;
|
|
316
|
-
return minify(html,
|
|
318
|
+
return minify(html, this.options.minify === true ? {} : this.options.minify);
|
|
317
319
|
}
|
|
318
320
|
return html;
|
|
319
321
|
});
|
|
@@ -321,6 +323,8 @@ class HtmlWebpackPlugin {
|
|
|
321
323
|
|
|
322
324
|
/*
|
|
323
325
|
* Pushes the content of the given filename to the compilation assets
|
|
326
|
+
* @param {string} filename
|
|
327
|
+
* @param {WebpackCompilation} compilation
|
|
324
328
|
*/
|
|
325
329
|
addFileToAssets (filename, compilation) {
|
|
326
330
|
filename = path.resolve(compilation.compiler.context, filename);
|
|
@@ -328,62 +332,50 @@ class HtmlWebpackPlugin {
|
|
|
328
332
|
fsStatAsync(filename),
|
|
329
333
|
fsReadFileAsync(filename)
|
|
330
334
|
])
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (compilation.fileDependencies.add) {
|
|
335
|
+
.then(([size, source]) => {
|
|
336
|
+
return {
|
|
337
|
+
size,
|
|
338
|
+
source
|
|
339
|
+
};
|
|
340
|
+
})
|
|
341
|
+
.catch(() => Promise.reject(new Error('HtmlWebpackPlugin: could not load file ' + filename)))
|
|
342
|
+
.then(results => {
|
|
343
|
+
const basename = path.basename(filename);
|
|
341
344
|
compilation.fileDependencies.add(filename);
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
size: () => results.size.size
|
|
349
|
-
};
|
|
350
|
-
return basename;
|
|
351
|
-
});
|
|
345
|
+
compilation.assets[basename] = {
|
|
346
|
+
source: () => results.source,
|
|
347
|
+
size: () => results.size.size
|
|
348
|
+
};
|
|
349
|
+
return basename;
|
|
350
|
+
});
|
|
352
351
|
}
|
|
353
352
|
|
|
354
353
|
/**
|
|
355
354
|
* Helper to sort chunks
|
|
355
|
+
* @param {string[]} entryNames
|
|
356
|
+
* @param {string|((entryNameA: string, entryNameB: string) => number)} sortMode
|
|
357
|
+
* @param {WebpackCompilation} compilation
|
|
356
358
|
*/
|
|
357
|
-
|
|
359
|
+
sortEntryChunks (entryNames, sortMode, compilation) {
|
|
358
360
|
// Custom function
|
|
359
361
|
if (typeof sortMode === 'function') {
|
|
360
|
-
return
|
|
362
|
+
return entryNames.sort(sortMode);
|
|
361
363
|
}
|
|
362
364
|
// Check if the given sort mode is a valid chunkSorter sort mode
|
|
363
365
|
if (typeof chunkSorter[sortMode] !== 'undefined') {
|
|
364
|
-
return chunkSorter[sortMode](
|
|
366
|
+
return chunkSorter[sortMode](entryNames, compilation, this.options);
|
|
365
367
|
}
|
|
366
368
|
throw new Error('"' + sortMode + '" is not a valid chunk sort mode');
|
|
367
369
|
}
|
|
368
370
|
|
|
369
371
|
/**
|
|
370
372
|
* Return all chunks from the compilation result which match the exclude and include filters
|
|
373
|
+
* @param {any} chunks
|
|
374
|
+
* @param {string[]|'all'} includedChunks
|
|
375
|
+
* @param {string[]} excludedChunks
|
|
371
376
|
*/
|
|
372
377
|
filterChunks (chunks, includedChunks, excludedChunks) {
|
|
373
|
-
return chunks.filter(
|
|
374
|
-
const chunkName = chunk.names[0];
|
|
375
|
-
// This chunk doesn't have a name. This script can't handled it.
|
|
376
|
-
if (chunkName === undefined) {
|
|
377
|
-
return false;
|
|
378
|
-
}
|
|
379
|
-
// Skip if the chunk should be lazy loaded
|
|
380
|
-
if (typeof chunk.isInitial === 'function') {
|
|
381
|
-
if (!chunk.isInitial()) {
|
|
382
|
-
return false;
|
|
383
|
-
}
|
|
384
|
-
} else if (!chunk.initial) {
|
|
385
|
-
return false;
|
|
386
|
-
}
|
|
378
|
+
return chunks.filter(chunkName => {
|
|
387
379
|
// Skip if the chunks should be filtered and the given chunk was not added explicity
|
|
388
380
|
if (Array.isArray(includedChunks) && includedChunks.indexOf(chunkName) === -1) {
|
|
389
381
|
return false;
|
|
@@ -401,95 +393,116 @@ class HtmlWebpackPlugin {
|
|
|
401
393
|
return assets.js.length && assets.js.every(name => /\.hot-update\.js$/.test(name));
|
|
402
394
|
}
|
|
403
395
|
|
|
404
|
-
|
|
405
|
-
|
|
396
|
+
/**
|
|
397
|
+
* The htmlWebpackPluginAssets extracts the asset information of a webpack compilation
|
|
398
|
+
* for all given entry names
|
|
399
|
+
* @param {WebpackCompilation} compilation
|
|
400
|
+
* @param {string[]} entryNames
|
|
401
|
+
* @returns {{
|
|
402
|
+
publicPath: string,
|
|
403
|
+
js: Array<{entryName: string, path: string}>,
|
|
404
|
+
css: Array<{entryName: string, path: string}>,
|
|
405
|
+
manifest?: string,
|
|
406
|
+
favicon?: string
|
|
407
|
+
}}
|
|
408
|
+
*/
|
|
409
|
+
htmlWebpackPluginAssets (compilation, entryNames) {
|
|
406
410
|
const compilationHash = compilation.hash;
|
|
407
411
|
|
|
408
|
-
|
|
412
|
+
/**
|
|
413
|
+
* @type {string} the configured public path to the asset root
|
|
414
|
+
* if a publicPath is set in the current webpack config use it otherwise
|
|
415
|
+
* fallback to a realtive path
|
|
416
|
+
*/
|
|
409
417
|
let publicPath = typeof compilation.options.output.publicPath !== 'undefined'
|
|
410
418
|
// If a hard coded public path exists use it
|
|
411
419
|
? compilation.mainTemplate.getPublicPath({hash: compilationHash})
|
|
412
420
|
// If no public path was set get a relative url path
|
|
413
|
-
: path.relative(path.resolve(compilation.options.output.path, path.dirname(
|
|
421
|
+
: path.relative(path.resolve(compilation.options.output.path, path.dirname(this.childCompilationOutputName)), compilation.options.output.path)
|
|
414
422
|
.split(path.sep).join('/');
|
|
415
423
|
|
|
416
424
|
if (publicPath.length && publicPath.substr(-1, 1) !== '/') {
|
|
417
425
|
publicPath += '/';
|
|
418
426
|
}
|
|
419
427
|
|
|
428
|
+
/**
|
|
429
|
+
* @type {{
|
|
430
|
+
publicPath: string,
|
|
431
|
+
js: Array<{entryName: string, path: string}>,
|
|
432
|
+
css: Array<{entryName: string, path: string}>,
|
|
433
|
+
manifest?: string,
|
|
434
|
+
favicon?: string
|
|
435
|
+
}}
|
|
436
|
+
*/
|
|
420
437
|
const assets = {
|
|
421
438
|
// The public path
|
|
422
439
|
publicPath: publicPath,
|
|
423
|
-
// Will contain all js & css files by chunk
|
|
424
|
-
chunks: {},
|
|
425
440
|
// Will contain all js files
|
|
426
441
|
js: [],
|
|
427
442
|
// Will contain all css files
|
|
428
443
|
css: [],
|
|
429
444
|
// Will contain the html5 appcache manifest files if it exists
|
|
430
|
-
manifest: Object.keys(compilation.assets).
|
|
445
|
+
manifest: Object.keys(compilation.assets).find(assetFile => path.extname(assetFile) === '.appcache'),
|
|
446
|
+
// Favicon
|
|
447
|
+
favicon: undefined
|
|
431
448
|
};
|
|
432
449
|
|
|
433
450
|
// Append a hash for cache busting
|
|
434
|
-
if (this.options.hash) {
|
|
435
|
-
assets.manifest =
|
|
436
|
-
assets.favicon = self.appendHash(assets.favicon, compilationHash);
|
|
451
|
+
if (this.options.hash && assets.manifest) {
|
|
452
|
+
assets.manifest = this.appendHash(assets.manifest, compilationHash);
|
|
437
453
|
}
|
|
438
454
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
//
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
// or when one chunk hosts js and css simultaneously
|
|
455
|
-
const js = chunkFiles.find(chunkFile => /.js($|\?)/.test(chunkFile));
|
|
456
|
-
if (js) {
|
|
457
|
-
assets.chunks[chunkName].size = chunk.size;
|
|
458
|
-
assets.chunks[chunkName].entry = js;
|
|
459
|
-
assets.chunks[chunkName].hash = chunk.hash;
|
|
460
|
-
assets.js.push(js);
|
|
461
|
-
}
|
|
455
|
+
// Extract paths to .js and .css files from the current compilation
|
|
456
|
+
const extensionRegexp = /\.(css|js)(\?|$)/;
|
|
457
|
+
for (let i = 0; i < entryNames.length; i++) {
|
|
458
|
+
const entryName = entryNames[i];
|
|
459
|
+
const entryPointFiles = compilation.entrypoints.get(entryName).getFiles();
|
|
460
|
+
// Prepend the publicPath and append the hash depending on the
|
|
461
|
+
// webpack.output.publicPath and hashOptions
|
|
462
|
+
// E.g. bundle.js -> /bundle.js?hash
|
|
463
|
+
const entryPointPublicPaths = entryPointFiles
|
|
464
|
+
.map(chunkFile => {
|
|
465
|
+
const entryPointPublicPath = publicPath + chunkFile;
|
|
466
|
+
return this.options.hash
|
|
467
|
+
? this.appendHash(entryPointPublicPath, compilationHash)
|
|
468
|
+
: entryPointPublicPath;
|
|
469
|
+
});
|
|
462
470
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
471
|
+
entryPointPublicPaths.forEach((entryPointPublicPaths) => {
|
|
472
|
+
const extMatch = extensionRegexp.exec(entryPointPublicPaths);
|
|
473
|
+
// Skip if the public path is not a .css or .js file
|
|
474
|
+
if (!extMatch) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
// ext will contain .js or .css
|
|
478
|
+
const ext = extMatch[1];
|
|
479
|
+
assets[ext].push({
|
|
480
|
+
entryName: entryName,
|
|
481
|
+
path: entryPointPublicPaths
|
|
482
|
+
});
|
|
483
|
+
});
|
|
467
484
|
}
|
|
468
|
-
|
|
469
|
-
// Duplicate css assets can occur on occasion if more than one chunk
|
|
470
|
-
// requires the same css.
|
|
471
|
-
assets.css = _.uniq(assets.css);
|
|
472
|
-
|
|
473
485
|
return assets;
|
|
474
486
|
}
|
|
475
487
|
|
|
476
488
|
/**
|
|
477
489
|
* Generate meta tags
|
|
490
|
+
* @returns {HtmlTagObject[]}
|
|
478
491
|
*/
|
|
479
492
|
getMetaTags () {
|
|
480
|
-
|
|
493
|
+
const metaOptions = this.options.meta;
|
|
494
|
+
if (metaOptions === false) {
|
|
481
495
|
return [];
|
|
482
496
|
}
|
|
483
497
|
// Make tags self-closing in case of xhtml
|
|
484
498
|
// Turn { "viewport" : "width=500, initial-scale=1" } into
|
|
485
499
|
// [{ name:"viewport" content:"width=500, initial-scale=1" }]
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
return (typeof metaTagContent === 'object') ? metaTagContent : {
|
|
500
|
+
const metaTagAttributeObjects = Object.keys(metaOptions).map((metaName) => {
|
|
501
|
+
const metaTagContent = metaOptions[metaName];
|
|
502
|
+
return (typeof metaTagContent === 'string') ? {
|
|
490
503
|
name: metaName,
|
|
491
504
|
content: metaTagContent
|
|
492
|
-
};
|
|
505
|
+
} : metaTagContent;
|
|
493
506
|
});
|
|
494
507
|
// Turn [{ name:"viewport" content:"width=500, initial-scale=1" }] into
|
|
495
508
|
// the html-webpack-plugin tag structure
|
|
@@ -497,34 +510,41 @@ class HtmlWebpackPlugin {
|
|
|
497
510
|
return {
|
|
498
511
|
tagName: 'meta',
|
|
499
512
|
voidTag: true,
|
|
500
|
-
selfClosingTag: selfClosingTag,
|
|
501
513
|
attributes: metaTagAttributes
|
|
502
514
|
};
|
|
503
515
|
});
|
|
504
516
|
}
|
|
505
517
|
|
|
506
518
|
/**
|
|
507
|
-
*
|
|
519
|
+
* Turns the given asset information into tag object representations
|
|
520
|
+
* which is seperated into head and body
|
|
521
|
+
*
|
|
522
|
+
* @param {{
|
|
523
|
+
js: {entryName: string, path: string}[],
|
|
524
|
+
css: {entryName: string, path: string}[],
|
|
525
|
+
favicon?: string
|
|
526
|
+
}} assets
|
|
527
|
+
*
|
|
528
|
+
* @returns {{
|
|
529
|
+
head: HtmlTagObject[],
|
|
530
|
+
body: HtmlTagObject[]
|
|
531
|
+
}}
|
|
508
532
|
*/
|
|
509
|
-
|
|
533
|
+
generateHtmlTagObjects (assets) {
|
|
510
534
|
// Turn script files into script tags
|
|
511
|
-
const scripts = assets.js.map(
|
|
535
|
+
const scripts = assets.js.map(scriptAsset => ({
|
|
512
536
|
tagName: 'script',
|
|
513
|
-
|
|
537
|
+
voidTag: false,
|
|
514
538
|
attributes: {
|
|
515
|
-
|
|
516
|
-
src: scriptPath
|
|
539
|
+
src: scriptAsset.path
|
|
517
540
|
}
|
|
518
541
|
}));
|
|
519
|
-
// Make tags self-closing in case of xhtml
|
|
520
|
-
const selfClosingTag = !!this.options.xhtml;
|
|
521
542
|
// Turn css files into link tags
|
|
522
|
-
const styles = assets.css.map(
|
|
543
|
+
const styles = assets.css.map(styleAsset => ({
|
|
523
544
|
tagName: 'link',
|
|
524
|
-
selfClosingTag: selfClosingTag,
|
|
525
545
|
voidTag: true,
|
|
526
546
|
attributes: {
|
|
527
|
-
href:
|
|
547
|
+
href: styleAsset.path,
|
|
528
548
|
rel: 'stylesheet'
|
|
529
549
|
}
|
|
530
550
|
}));
|
|
@@ -536,7 +556,6 @@ class HtmlWebpackPlugin {
|
|
|
536
556
|
if (assets.favicon) {
|
|
537
557
|
head.push({
|
|
538
558
|
tagName: 'link',
|
|
539
|
-
selfClosingTag: selfClosingTag,
|
|
540
559
|
voidTag: true,
|
|
541
560
|
attributes: {
|
|
542
561
|
rel: 'shortcut icon',
|
|
@@ -557,13 +576,24 @@ class HtmlWebpackPlugin {
|
|
|
557
576
|
|
|
558
577
|
/**
|
|
559
578
|
* Injects the assets into the given html string
|
|
579
|
+
*
|
|
580
|
+
* @param {string} html
|
|
581
|
+
* @param {any} assets
|
|
582
|
+
* The input html
|
|
583
|
+
* @param {{
|
|
584
|
+
head: HtmlTagObject[],
|
|
585
|
+
body: HtmlTagObject[]
|
|
586
|
+
}} assetTags
|
|
587
|
+
* The asset tags to inject
|
|
588
|
+
*
|
|
589
|
+
* @returns {string}
|
|
560
590
|
*/
|
|
561
591
|
injectAssetsIntoHtml (html, assets, assetTags) {
|
|
562
592
|
const htmlRegExp = /(<html[^>]*>)/i;
|
|
563
593
|
const headRegExp = /(<\/head\s*>)/i;
|
|
564
594
|
const bodyRegExp = /(<\/body\s*>)/i;
|
|
565
|
-
const body = assetTags.body.map(this.
|
|
566
|
-
const head = assetTags.head.map(this.
|
|
595
|
+
const body = assetTags.body.map((assetTagObject) => htmlTagObjectToString(assetTagObject, this.options.xhtml));
|
|
596
|
+
const head = assetTags.head.map((assetTagObject) => htmlTagObjectToString(assetTagObject, this.options.xhtml));
|
|
567
597
|
|
|
568
598
|
if (body.length) {
|
|
569
599
|
if (bodyRegExp.test(html)) {
|
|
@@ -603,7 +633,10 @@ class HtmlWebpackPlugin {
|
|
|
603
633
|
}
|
|
604
634
|
|
|
605
635
|
/**
|
|
606
|
-
* Appends a cache busting hash
|
|
636
|
+
* Appends a cache busting hash to the query string of the url
|
|
637
|
+
* E.g. http://localhost:8080/ -> http://localhost:8080/?50c9096ba6183fd728eeb065a26ec175
|
|
638
|
+
* @param {string} url
|
|
639
|
+
* @param {string} hash
|
|
607
640
|
*/
|
|
608
641
|
appendHash (url, hash) {
|
|
609
642
|
if (!url) {
|
|
@@ -612,28 +645,12 @@ class HtmlWebpackPlugin {
|
|
|
612
645
|
return url + (url.indexOf('?') === -1 ? '?' : '&') + hash;
|
|
613
646
|
}
|
|
614
647
|
|
|
615
|
-
/**
|
|
616
|
-
* Turn a tag definition into a html string
|
|
617
|
-
*/
|
|
618
|
-
createHtmlTag (tagDefinition) {
|
|
619
|
-
const attributes = Object.keys(tagDefinition.attributes || {})
|
|
620
|
-
.filter(attributeName => tagDefinition.attributes[attributeName] !== false)
|
|
621
|
-
.map(attributeName => {
|
|
622
|
-
if (tagDefinition.attributes[attributeName] === true) {
|
|
623
|
-
return attributeName;
|
|
624
|
-
}
|
|
625
|
-
return attributeName + '="' + tagDefinition.attributes[attributeName] + '"';
|
|
626
|
-
});
|
|
627
|
-
// Backport of 3.x void tag definition
|
|
628
|
-
const voidTag = tagDefinition.voidTag !== undefined ? tagDefinition.voidTag : !tagDefinition.closeTag;
|
|
629
|
-
const selfClosingTag = tagDefinition.voidTag !== undefined ? tagDefinition.voidTag && this.options.xhtml : tagDefinition.selfClosingTag;
|
|
630
|
-
return '<' + [tagDefinition.tagName].concat(attributes).join(' ') + (selfClosingTag ? '/' : '') + '>' +
|
|
631
|
-
(tagDefinition.innerHTML || '') +
|
|
632
|
-
(voidTag ? '' : '</' + tagDefinition.tagName + '>');
|
|
633
|
-
}
|
|
634
|
-
|
|
635
648
|
/**
|
|
636
649
|
* Helper to return the absolute template path with a fallback loader
|
|
650
|
+
* @param {string} template
|
|
651
|
+
* The path to the tempalate e.g. './index.html'
|
|
652
|
+
* @param {string} context
|
|
653
|
+
* The webpack base resolution path for relative paths e.g. process.cwd()
|
|
637
654
|
*/
|
|
638
655
|
getFullTemplatePath (template, context) {
|
|
639
656
|
// If the template doesn't use a loader use the lodash template loader
|
|
@@ -655,59 +672,6 @@ class HtmlWebpackPlugin {
|
|
|
655
672
|
files.sort();
|
|
656
673
|
return files;
|
|
657
674
|
}
|
|
658
|
-
|
|
659
|
-
/**
|
|
660
|
-
* Helper to promisify compilation.applyPluginsAsyncWaterfall that returns
|
|
661
|
-
* a function that helps to merge given plugin arguments with processed ones
|
|
662
|
-
*/
|
|
663
|
-
applyPluginsAsyncWaterfall (compilation) {
|
|
664
|
-
if (compilation.hooks) {
|
|
665
|
-
return (eventName, requiresResult, pluginArgs) => {
|
|
666
|
-
const ccEventName = trainCaseToCamelCase(eventName);
|
|
667
|
-
if (!compilation.hooks[ccEventName]) {
|
|
668
|
-
compilation.errors.push(
|
|
669
|
-
new Error('No hook found for ' + eventName)
|
|
670
|
-
);
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
return compilation.hooks[ccEventName].promise(pluginArgs);
|
|
674
|
-
};
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
// Before Webpack 4
|
|
678
|
-
const promisedApplyPluginsAsyncWaterfall = function (name, init) {
|
|
679
|
-
return new Promise((resolve, reject) => {
|
|
680
|
-
const callback = function (err, result) {
|
|
681
|
-
if (err) {
|
|
682
|
-
return reject(err);
|
|
683
|
-
}
|
|
684
|
-
resolve(result);
|
|
685
|
-
};
|
|
686
|
-
compilation.applyPluginsAsyncWaterfall(name, init, callback);
|
|
687
|
-
});
|
|
688
|
-
};
|
|
689
|
-
|
|
690
|
-
return (eventName, requiresResult, pluginArgs) => promisedApplyPluginsAsyncWaterfall(eventName, pluginArgs)
|
|
691
|
-
.then(result => {
|
|
692
|
-
if (requiresResult && !result) {
|
|
693
|
-
compilation.warnings.push(
|
|
694
|
-
new Error('Using ' + eventName + ' without returning a result is deprecated.')
|
|
695
|
-
);
|
|
696
|
-
}
|
|
697
|
-
return _.extend(pluginArgs, result);
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
/**
|
|
703
|
-
* Takes a string in train case and transforms it to camel case
|
|
704
|
-
*
|
|
705
|
-
* Example: 'hello-my-world' to 'helloMyWorld'
|
|
706
|
-
*
|
|
707
|
-
* @param {string} word
|
|
708
|
-
*/
|
|
709
|
-
function trainCaseToCamelCase (word) {
|
|
710
|
-
return word.replace(/-([\w])/g, (match, p1) => p1.toUpperCase());
|
|
711
675
|
}
|
|
712
676
|
|
|
713
677
|
/**
|
|
@@ -717,7 +681,6 @@ function trainCaseToCamelCase (word) {
|
|
|
717
681
|
function templateParametersGenerator (compilation, assets, options) {
|
|
718
682
|
return {
|
|
719
683
|
compilation: compilation,
|
|
720
|
-
webpack: compilation.getStats().toJson(),
|
|
721
684
|
webpackConfig: compilation.options,
|
|
722
685
|
htmlWebpackPlugin: {
|
|
723
686
|
files: assets,
|
|
@@ -725,5 +688,4 @@ function templateParametersGenerator (compilation, assets, options) {
|
|
|
725
688
|
}
|
|
726
689
|
};
|
|
727
690
|
}
|
|
728
|
-
|
|
729
691
|
module.exports = HtmlWebpackPlugin;
|