html-webpack-plugin 3.0.7 → 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/CHANGELOG.md +37 -0
- package/README.md +65 -34
- package/default_index.ejs +1 -1
- package/index.js +351 -347
- package/lib/chunksorter.js +26 -116
- package/lib/compiler.js +13 -17
- package/lib/errors.js +2 -1
- package/lib/hooks.js +132 -0
- package/lib/html-tags.js +73 -0
- package/lib/loader.js +8 -24
- package/package.json +20 -9
package/index.js
CHANGED
|
@@ -1,24 +1,45 @@
|
|
|
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,
|
|
42
|
+
templateParameters: templateParametersGenerator,
|
|
22
43
|
filename: 'index.html',
|
|
23
44
|
hash: false,
|
|
24
45
|
inject: true,
|
|
@@ -29,11 +50,27 @@ class HtmlWebpackPlugin {
|
|
|
29
50
|
showErrors: true,
|
|
30
51
|
chunks: 'all',
|
|
31
52
|
excludeChunks: [],
|
|
53
|
+
chunksSortMode: 'auto',
|
|
54
|
+
meta: {},
|
|
32
55
|
title: 'Webpack App',
|
|
33
56
|
xhtml: false
|
|
34
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;
|
|
35
68
|
}
|
|
36
69
|
|
|
70
|
+
/**
|
|
71
|
+
* apply is called by the webpack main compiler during the start phase
|
|
72
|
+
* @param {WebpackCompiler} compiler
|
|
73
|
+
*/
|
|
37
74
|
apply (compiler) {
|
|
38
75
|
const self = this;
|
|
39
76
|
let isCompilationCached = false;
|
|
@@ -48,22 +85,10 @@ class HtmlWebpackPlugin {
|
|
|
48
85
|
this.options.filename = path.relative(compiler.options.output.path, filename);
|
|
49
86
|
}
|
|
50
87
|
|
|
51
|
-
// setup hooks for
|
|
52
|
-
|
|
53
|
-
compiler.hooks.compilation.tap('HtmlWebpackPluginHooks', compilation => {
|
|
54
|
-
const SyncWaterfallHook = require('tapable').SyncWaterfallHook;
|
|
55
|
-
const AsyncSeriesWaterfallHook = require('tapable').AsyncSeriesWaterfallHook;
|
|
56
|
-
compilation.hooks.htmlWebpackPluginAlterChunks = new SyncWaterfallHook(['chunks', 'objectWithPluginRef']);
|
|
57
|
-
compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration = new AsyncSeriesWaterfallHook(['pluginArgs']);
|
|
58
|
-
compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing = new AsyncSeriesWaterfallHook(['pluginArgs']);
|
|
59
|
-
compilation.hooks.htmlWebpackPluginAlterAssetTags = new AsyncSeriesWaterfallHook(['pluginArgs']);
|
|
60
|
-
compilation.hooks.htmlWebpackPluginAfterHtmlProcessing = new AsyncSeriesWaterfallHook(['pluginArgs']);
|
|
61
|
-
compilation.hooks.htmlWebpackPluginAfterEmit = new AsyncSeriesWaterfallHook(['pluginArgs']);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
88
|
+
// setup hooks for third party plugins
|
|
89
|
+
compiler.hooks.compilation.tap('HtmlWebpackPluginHooks', getHtmlWebpackPluginHooks);
|
|
64
90
|
|
|
65
|
-
|
|
66
|
-
(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) => {
|
|
67
92
|
// Compile the template (queued)
|
|
68
93
|
compilationPromise = childCompiler.compileTemplate(self.options.template, compiler.context, self.options.filename, compilation)
|
|
69
94
|
.catch(err => {
|
|
@@ -83,144 +108,124 @@ class HtmlWebpackPlugin {
|
|
|
83
108
|
});
|
|
84
109
|
});
|
|
85
110
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
chunks
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const allChunks = compilation.getStats().toJson(chunkOnlyConfig).chunks;
|
|
107
|
-
// Filter chunks (options.chunks and options.excludeCHunks)
|
|
108
|
-
let chunks = self.filterChunks(allChunks, self.options.chunks, self.options.excludeChunks);
|
|
109
|
-
// Sort chunks
|
|
110
|
-
chunks = self.sortChunks(chunks, self.options.chunksSortMode, compilation.chunkGroups);
|
|
111
|
-
// Let plugins alter the chunks and the chunk sorting
|
|
112
|
-
if (compilation.hooks) {
|
|
113
|
-
chunks = compilation.hooks.htmlWebpackPluginAlterChunks.call(chunks, { plugin: self });
|
|
114
|
-
} else {
|
|
115
|
-
// Before Webpack 4
|
|
116
|
-
chunks = compilation.applyPluginsWaterfall('html-webpack-plugin-alter-chunks', chunks, { plugin: self });
|
|
117
|
-
}
|
|
118
|
-
// Get assets
|
|
119
|
-
const assets = self.htmlWebpackPluginAssets(compilation, chunks);
|
|
120
|
-
// If this is a hot update compilation, move on!
|
|
121
|
-
// This solves a problem where an `index.html` file is generated for hot-update js files
|
|
122
|
-
// It only happens in Webpack 2, where hot updates are emitted separately before the full bundle
|
|
123
|
-
if (self.isHotUpdateCompilation(assets)) {
|
|
124
|
-
return callback();
|
|
125
|
-
}
|
|
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
|
+
}
|
|
126
131
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
+
}
|
|
134
139
|
|
|
135
|
-
|
|
140
|
+
Promise.resolve()
|
|
136
141
|
// Favicon
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
+
})
|
|
149
154
|
// Wait for the compilation to finish
|
|
150
|
-
|
|
151
|
-
|
|
155
|
+
.then(() => compilationPromise)
|
|
156
|
+
.then(compiledTemplate => {
|
|
152
157
|
// Allow to use a custom function / string instead
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
+
})
|
|
160
165
|
// Allow plugins to make changes to the assets before invoking the template
|
|
161
166
|
// This only makes sense to use if `inject` is `false`
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
.then(compilationResult => getHtmlWebpackPluginHook(compilation, 'htmlWebpackPluginBeforeHtmlGeneration').promise({
|
|
168
|
+
assets: assets,
|
|
169
|
+
outputName: self.childCompilationOutputName,
|
|
170
|
+
plugin: self
|
|
171
|
+
})
|
|
172
|
+
.then(() => compilationResult))
|
|
168
173
|
// Execute the template
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
174
|
+
.then(compilationResult => typeof compilationResult !== 'function'
|
|
175
|
+
? compilationResult
|
|
176
|
+
: self.executeTemplate(compilationResult, assets, compilation))
|
|
172
177
|
// Allow plugins to change the html before assets are injected
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
+
})
|
|
188
193
|
// Allow plugins to change the html after assets are injected
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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 => {
|
|
197
202
|
// In case anything went wrong the promise is resolved
|
|
198
203
|
// with the error message and an error is logged
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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 => {
|
|
205
210
|
// Replace the compilation result with the evaluated html code
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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))
|
|
219
224
|
// Let webpack continue with it
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
225
|
+
.then(() => {
|
|
226
|
+
callback();
|
|
227
|
+
});
|
|
228
|
+
});
|
|
224
229
|
}
|
|
225
230
|
|
|
226
231
|
/**
|
|
@@ -253,34 +258,39 @@ class HtmlWebpackPlugin {
|
|
|
253
258
|
: Promise.reject('The loader "' + this.options.template + '" didn\'t return html.');
|
|
254
259
|
}
|
|
255
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Generate the template parameters for the template function
|
|
263
|
+
* @param {WebpackCompilation} compilation
|
|
264
|
+
*
|
|
265
|
+
*/
|
|
266
|
+
getTemplateParameters (compilation, assets) {
|
|
267
|
+
if (typeof this.options.templateParameters === 'function') {
|
|
268
|
+
return this.options.templateParameters(compilation, assets, this.options);
|
|
269
|
+
}
|
|
270
|
+
if (typeof this.options.templateParameters === 'object') {
|
|
271
|
+
return this.options.templateParameters;
|
|
272
|
+
}
|
|
273
|
+
return {};
|
|
274
|
+
}
|
|
275
|
+
|
|
256
276
|
/**
|
|
257
277
|
* Html post processing
|
|
258
278
|
*
|
|
259
|
-
*
|
|
279
|
+
* @returns Promise<string>
|
|
260
280
|
*/
|
|
261
|
-
executeTemplate (templateFunction,
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
};
|
|
275
|
-
let html = '';
|
|
276
|
-
try {
|
|
277
|
-
html = templateFunction(templateParams);
|
|
278
|
-
} catch (e) {
|
|
279
|
-
compilation.errors.push(new Error('Template execution failed: ' + e));
|
|
280
|
-
return Promise.reject(e);
|
|
281
|
-
}
|
|
282
|
-
return html;
|
|
283
|
-
});
|
|
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);
|
|
284
294
|
}
|
|
285
295
|
|
|
286
296
|
/**
|
|
@@ -289,24 +299,23 @@ class HtmlWebpackPlugin {
|
|
|
289
299
|
* Returns a promise
|
|
290
300
|
*/
|
|
291
301
|
postProcessHtml (html, assets, assetTags) {
|
|
292
|
-
const self = this;
|
|
293
302
|
if (typeof html !== 'string') {
|
|
294
303
|
return Promise.reject('Expected html to be a string but got ' + JSON.stringify(html));
|
|
295
304
|
}
|
|
296
305
|
return Promise.resolve()
|
|
297
306
|
// Inject
|
|
298
307
|
.then(() => {
|
|
299
|
-
if (
|
|
300
|
-
return
|
|
308
|
+
if (this.options.inject) {
|
|
309
|
+
return this.injectAssetsIntoHtml(html, assets, assetTags);
|
|
301
310
|
} else {
|
|
302
311
|
return html;
|
|
303
312
|
}
|
|
304
313
|
})
|
|
305
314
|
// Minify
|
|
306
315
|
.then(html => {
|
|
307
|
-
if (
|
|
316
|
+
if (this.options.minify) {
|
|
308
317
|
const minify = require('html-minifier').minify;
|
|
309
|
-
return minify(html,
|
|
318
|
+
return minify(html, this.options.minify === true ? {} : this.options.minify);
|
|
310
319
|
}
|
|
311
320
|
return html;
|
|
312
321
|
});
|
|
@@ -314,6 +323,8 @@ class HtmlWebpackPlugin {
|
|
|
314
323
|
|
|
315
324
|
/*
|
|
316
325
|
* Pushes the content of the given filename to the compilation assets
|
|
326
|
+
* @param {string} filename
|
|
327
|
+
* @param {WebpackCompilation} compilation
|
|
317
328
|
*/
|
|
318
329
|
addFileToAssets (filename, compilation) {
|
|
319
330
|
filename = path.resolve(compilation.compiler.context, filename);
|
|
@@ -321,73 +332,50 @@ class HtmlWebpackPlugin {
|
|
|
321
332
|
fsStatAsync(filename),
|
|
322
333
|
fsReadFileAsync(filename)
|
|
323
334
|
])
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
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);
|
|
334
344
|
compilation.fileDependencies.add(filename);
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
size: () => results.size.size
|
|
342
|
-
};
|
|
343
|
-
return basename;
|
|
344
|
-
});
|
|
345
|
+
compilation.assets[basename] = {
|
|
346
|
+
source: () => results.source,
|
|
347
|
+
size: () => results.size.size
|
|
348
|
+
};
|
|
349
|
+
return basename;
|
|
350
|
+
});
|
|
345
351
|
}
|
|
346
352
|
|
|
347
353
|
/**
|
|
348
354
|
* Helper to sort chunks
|
|
355
|
+
* @param {string[]} entryNames
|
|
356
|
+
* @param {string|((entryNameA: string, entryNameB: string) => number)} sortMode
|
|
357
|
+
* @param {WebpackCompilation} compilation
|
|
349
358
|
*/
|
|
350
|
-
|
|
351
|
-
// Sort mode auto by default:
|
|
352
|
-
if (typeof sortMode === 'undefined') {
|
|
353
|
-
sortMode = 'auto';
|
|
354
|
-
}
|
|
359
|
+
sortEntryChunks (entryNames, sortMode, compilation) {
|
|
355
360
|
// Custom function
|
|
356
361
|
if (typeof sortMode === 'function') {
|
|
357
|
-
return
|
|
358
|
-
}
|
|
359
|
-
// Disabled sorting:
|
|
360
|
-
if (sortMode === 'none') {
|
|
361
|
-
return chunkSorter.none(chunks);
|
|
362
|
-
}
|
|
363
|
-
if (sortMode === 'manual') {
|
|
364
|
-
return chunkSorter.manual(chunks, this.options.chunks);
|
|
362
|
+
return entryNames.sort(sortMode);
|
|
365
363
|
}
|
|
366
364
|
// Check if the given sort mode is a valid chunkSorter sort mode
|
|
367
365
|
if (typeof chunkSorter[sortMode] !== 'undefined') {
|
|
368
|
-
return chunkSorter[sortMode](
|
|
366
|
+
return chunkSorter[sortMode](entryNames, compilation, this.options);
|
|
369
367
|
}
|
|
370
368
|
throw new Error('"' + sortMode + '" is not a valid chunk sort mode');
|
|
371
369
|
}
|
|
372
370
|
|
|
373
371
|
/**
|
|
374
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
|
|
375
376
|
*/
|
|
376
377
|
filterChunks (chunks, includedChunks, excludedChunks) {
|
|
377
|
-
return chunks.filter(
|
|
378
|
-
const chunkName = chunk.names[0];
|
|
379
|
-
// This chunk doesn't have a name. This script can't handled it.
|
|
380
|
-
if (chunkName === undefined) {
|
|
381
|
-
return false;
|
|
382
|
-
}
|
|
383
|
-
// Skip if the chunk should be lazy loaded
|
|
384
|
-
if (typeof chunk.isInitial === 'function') {
|
|
385
|
-
if (!chunk.isInitial()) {
|
|
386
|
-
return false;
|
|
387
|
-
}
|
|
388
|
-
} else if (!chunk.initial) {
|
|
389
|
-
return false;
|
|
390
|
-
}
|
|
378
|
+
return chunks.filter(chunkName => {
|
|
391
379
|
// Skip if the chunks should be filtered and the given chunk was not added explicity
|
|
392
380
|
if (Array.isArray(includedChunks) && includedChunks.indexOf(chunkName) === -1) {
|
|
393
381
|
return false;
|
|
@@ -405,113 +393,170 @@ class HtmlWebpackPlugin {
|
|
|
405
393
|
return assets.js.length && assets.js.every(name => /\.hot-update\.js$/.test(name));
|
|
406
394
|
}
|
|
407
395
|
|
|
408
|
-
|
|
409
|
-
|
|
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) {
|
|
410
410
|
const compilationHash = compilation.hash;
|
|
411
411
|
|
|
412
|
-
|
|
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
|
+
*/
|
|
413
417
|
let publicPath = typeof compilation.options.output.publicPath !== 'undefined'
|
|
414
418
|
// If a hard coded public path exists use it
|
|
415
419
|
? compilation.mainTemplate.getPublicPath({hash: compilationHash})
|
|
416
420
|
// If no public path was set get a relative url path
|
|
417
|
-
: 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)
|
|
418
422
|
.split(path.sep).join('/');
|
|
419
423
|
|
|
420
424
|
if (publicPath.length && publicPath.substr(-1, 1) !== '/') {
|
|
421
425
|
publicPath += '/';
|
|
422
426
|
}
|
|
423
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
|
+
*/
|
|
424
437
|
const assets = {
|
|
425
438
|
// The public path
|
|
426
439
|
publicPath: publicPath,
|
|
427
|
-
// Will contain all js & css files by chunk
|
|
428
|
-
chunks: {},
|
|
429
440
|
// Will contain all js files
|
|
430
441
|
js: [],
|
|
431
442
|
// Will contain all css files
|
|
432
443
|
css: [],
|
|
433
444
|
// Will contain the html5 appcache manifest files if it exists
|
|
434
|
-
manifest: Object.keys(compilation.assets).
|
|
445
|
+
manifest: Object.keys(compilation.assets).find(assetFile => path.extname(assetFile) === '.appcache'),
|
|
446
|
+
// Favicon
|
|
447
|
+
favicon: undefined
|
|
435
448
|
};
|
|
436
449
|
|
|
437
450
|
// Append a hash for cache busting
|
|
438
|
-
if (this.options.hash) {
|
|
439
|
-
assets.manifest =
|
|
440
|
-
assets.favicon = self.appendHash(assets.favicon, compilationHash);
|
|
451
|
+
if (this.options.hash && assets.manifest) {
|
|
452
|
+
assets.manifest = this.appendHash(assets.manifest, compilationHash);
|
|
441
453
|
}
|
|
442
454
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
//
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
// or when one chunk hosts js and css simultaneously
|
|
459
|
-
const js = chunkFiles.find(chunkFile => /.js($|\?)/.test(chunkFile));
|
|
460
|
-
if (js) {
|
|
461
|
-
assets.chunks[chunkName].size = chunk.size;
|
|
462
|
-
assets.chunks[chunkName].entry = js;
|
|
463
|
-
assets.chunks[chunkName].hash = chunk.hash;
|
|
464
|
-
assets.js.push(js);
|
|
465
|
-
}
|
|
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
|
+
});
|
|
466
470
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
+
});
|
|
471
484
|
}
|
|
472
|
-
|
|
473
|
-
// Duplicate css assets can occur on occasion if more than one chunk
|
|
474
|
-
// requires the same css.
|
|
475
|
-
assets.css = _.uniq(assets.css);
|
|
476
|
-
|
|
477
485
|
return assets;
|
|
478
486
|
}
|
|
479
487
|
|
|
480
488
|
/**
|
|
481
|
-
*
|
|
489
|
+
* Generate meta tags
|
|
490
|
+
* @returns {HtmlTagObject[]}
|
|
482
491
|
*/
|
|
483
|
-
|
|
492
|
+
getMetaTags () {
|
|
493
|
+
const metaOptions = this.options.meta;
|
|
494
|
+
if (metaOptions === false) {
|
|
495
|
+
return [];
|
|
496
|
+
}
|
|
497
|
+
// Make tags self-closing in case of xhtml
|
|
498
|
+
// Turn { "viewport" : "width=500, initial-scale=1" } into
|
|
499
|
+
// [{ name:"viewport" content:"width=500, initial-scale=1" }]
|
|
500
|
+
const metaTagAttributeObjects = Object.keys(metaOptions).map((metaName) => {
|
|
501
|
+
const metaTagContent = metaOptions[metaName];
|
|
502
|
+
return (typeof metaTagContent === 'string') ? {
|
|
503
|
+
name: metaName,
|
|
504
|
+
content: metaTagContent
|
|
505
|
+
} : metaTagContent;
|
|
506
|
+
});
|
|
507
|
+
// Turn [{ name:"viewport" content:"width=500, initial-scale=1" }] into
|
|
508
|
+
// the html-webpack-plugin tag structure
|
|
509
|
+
return metaTagAttributeObjects.map((metaTagAttributes) => {
|
|
510
|
+
return {
|
|
511
|
+
tagName: 'meta',
|
|
512
|
+
voidTag: true,
|
|
513
|
+
attributes: metaTagAttributes
|
|
514
|
+
};
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
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
|
+
}}
|
|
532
|
+
*/
|
|
533
|
+
generateHtmlTagObjects (assets) {
|
|
484
534
|
// Turn script files into script tags
|
|
485
|
-
const scripts = assets.js.map(
|
|
535
|
+
const scripts = assets.js.map(scriptAsset => ({
|
|
486
536
|
tagName: 'script',
|
|
487
|
-
|
|
488
|
-
|
|
537
|
+
voidTag: false,
|
|
489
538
|
attributes: {
|
|
490
|
-
|
|
491
|
-
src: scriptPath
|
|
539
|
+
src: scriptAsset.path
|
|
492
540
|
}
|
|
493
541
|
}));
|
|
494
|
-
// Make tags self-closing in case of xhtml
|
|
495
|
-
const selfClosingTag = !!this.options.xhtml;
|
|
496
542
|
// Turn css files into link tags
|
|
497
|
-
const styles = assets.css.map(
|
|
543
|
+
const styles = assets.css.map(styleAsset => ({
|
|
498
544
|
tagName: 'link',
|
|
499
|
-
|
|
500
|
-
|
|
545
|
+
voidTag: true,
|
|
501
546
|
attributes: {
|
|
502
|
-
href:
|
|
547
|
+
href: styleAsset.path,
|
|
503
548
|
rel: 'stylesheet'
|
|
504
549
|
}
|
|
505
550
|
}));
|
|
506
551
|
// Injection targets
|
|
507
|
-
let head =
|
|
552
|
+
let head = this.getMetaTags();
|
|
508
553
|
let body = [];
|
|
509
554
|
|
|
510
555
|
// If there is a favicon present, add it to the head
|
|
511
556
|
if (assets.favicon) {
|
|
512
557
|
head.push({
|
|
513
558
|
tagName: 'link',
|
|
514
|
-
|
|
559
|
+
voidTag: true,
|
|
515
560
|
attributes: {
|
|
516
561
|
rel: 'shortcut icon',
|
|
517
562
|
href: assets.favicon
|
|
@@ -531,13 +576,24 @@ class HtmlWebpackPlugin {
|
|
|
531
576
|
|
|
532
577
|
/**
|
|
533
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}
|
|
534
590
|
*/
|
|
535
591
|
injectAssetsIntoHtml (html, assets, assetTags) {
|
|
536
592
|
const htmlRegExp = /(<html[^>]*>)/i;
|
|
537
593
|
const headRegExp = /(<\/head\s*>)/i;
|
|
538
594
|
const bodyRegExp = /(<\/body\s*>)/i;
|
|
539
|
-
const body = assetTags.body.map(this.
|
|
540
|
-
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));
|
|
541
597
|
|
|
542
598
|
if (body.length) {
|
|
543
599
|
if (bodyRegExp.test(html)) {
|
|
@@ -577,7 +633,10 @@ class HtmlWebpackPlugin {
|
|
|
577
633
|
}
|
|
578
634
|
|
|
579
635
|
/**
|
|
580
|
-
* 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
|
|
581
640
|
*/
|
|
582
641
|
appendHash (url, hash) {
|
|
583
642
|
if (!url) {
|
|
@@ -586,28 +645,12 @@ class HtmlWebpackPlugin {
|
|
|
586
645
|
return url + (url.indexOf('?') === -1 ? '?' : '&') + hash;
|
|
587
646
|
}
|
|
588
647
|
|
|
589
|
-
/**
|
|
590
|
-
* Turn a tag definition into a html string
|
|
591
|
-
*/
|
|
592
|
-
createHtmlTag (tagDefinition) {
|
|
593
|
-
const attributes = Object.keys(tagDefinition.attributes || {})
|
|
594
|
-
.filter(attributeName => tagDefinition.attributes[attributeName] !== false)
|
|
595
|
-
.map(attributeName => {
|
|
596
|
-
if (tagDefinition.attributes[attributeName] === true) {
|
|
597
|
-
return attributeName;
|
|
598
|
-
}
|
|
599
|
-
return attributeName + '="' + tagDefinition.attributes[attributeName] + '"';
|
|
600
|
-
});
|
|
601
|
-
// Backport of 3.x void tag definition
|
|
602
|
-
const voidTag = tagDefinition.voidTag !== undefined ? tagDefinition.voidTag : !tagDefinition.closeTag;
|
|
603
|
-
const selfClosingTag = tagDefinition.voidTag !== undefined ? tagDefinition.voidTag && this.options.xhtml : tagDefinition.selfClosingTag;
|
|
604
|
-
return '<' + [tagDefinition.tagName].concat(attributes).join(' ') + (selfClosingTag ? '/' : '') + '>' +
|
|
605
|
-
(tagDefinition.innerHTML || '') +
|
|
606
|
-
(voidTag ? '' : '</' + tagDefinition.tagName + '>');
|
|
607
|
-
}
|
|
608
|
-
|
|
609
648
|
/**
|
|
610
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()
|
|
611
654
|
*/
|
|
612
655
|
getFullTemplatePath (template, context) {
|
|
613
656
|
// If the template doesn't use a loader use the lodash template loader
|
|
@@ -629,59 +672,20 @@ class HtmlWebpackPlugin {
|
|
|
629
672
|
files.sort();
|
|
630
673
|
return files;
|
|
631
674
|
}
|
|
632
|
-
|
|
633
|
-
/**
|
|
634
|
-
* Helper to promisify compilation.applyPluginsAsyncWaterfall that returns
|
|
635
|
-
* a function that helps to merge given plugin arguments with processed ones
|
|
636
|
-
*/
|
|
637
|
-
applyPluginsAsyncWaterfall (compilation) {
|
|
638
|
-
if (compilation.hooks) {
|
|
639
|
-
return (eventName, requiresResult, pluginArgs) => {
|
|
640
|
-
const ccEventName = trainCaseToCamelCase(eventName);
|
|
641
|
-
if (!compilation.hooks[ccEventName]) {
|
|
642
|
-
compilation.errors.push(
|
|
643
|
-
new Error('No hook found for ' + eventName)
|
|
644
|
-
);
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
return compilation.hooks[ccEventName].promise(pluginArgs);
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
// Before Webpack 4
|
|
652
|
-
const promisedApplyPluginsAsyncWaterfall = function (name, init) {
|
|
653
|
-
return new Promise((resolve, reject) => {
|
|
654
|
-
const callback = function (err, result) {
|
|
655
|
-
if (err) {
|
|
656
|
-
return reject(err);
|
|
657
|
-
}
|
|
658
|
-
resolve(result);
|
|
659
|
-
};
|
|
660
|
-
compilation.applyPluginsAsyncWaterfall(name, init, callback);
|
|
661
|
-
});
|
|
662
|
-
};
|
|
663
|
-
|
|
664
|
-
return (eventName, requiresResult, pluginArgs) => promisedApplyPluginsAsyncWaterfall(eventName, pluginArgs)
|
|
665
|
-
.then(result => {
|
|
666
|
-
if (requiresResult && !result) {
|
|
667
|
-
compilation.warnings.push(
|
|
668
|
-
new Error('Using ' + eventName + ' without returning a result is deprecated.')
|
|
669
|
-
);
|
|
670
|
-
}
|
|
671
|
-
return _.extend(pluginArgs, result);
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
675
|
}
|
|
675
676
|
|
|
676
677
|
/**
|
|
677
|
-
*
|
|
678
|
-
*
|
|
679
|
-
* Example: 'hello-my-world' to 'helloMyWorld'
|
|
680
|
-
*
|
|
681
|
-
* @param {string} word
|
|
678
|
+
* The default for options.templateParameter
|
|
679
|
+
* Generate the template parameters
|
|
682
680
|
*/
|
|
683
|
-
function
|
|
684
|
-
return
|
|
681
|
+
function templateParametersGenerator (compilation, assets, options) {
|
|
682
|
+
return {
|
|
683
|
+
compilation: compilation,
|
|
684
|
+
webpackConfig: compilation.options,
|
|
685
|
+
htmlWebpackPlugin: {
|
|
686
|
+
files: assets,
|
|
687
|
+
options: options
|
|
688
|
+
}
|
|
689
|
+
};
|
|
685
690
|
}
|
|
686
|
-
|
|
687
691
|
module.exports = HtmlWebpackPlugin;
|