Skip to content

Commit 8964bc4

Browse files
committed
feat: use webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS to add assets
1 parent 1d59e9a commit 8964bc4

File tree

1 file changed

+155
-144
lines changed

1 file changed

+155
-144
lines changed

index.js

+155-144
Original file line numberDiff line numberDiff line change
@@ -188,164 +188,175 @@ function hookIntoCompiler (compiler, options, plugin) {
188188
};
189189
}
190190

191-
compiler.hooks.emit.tapAsync('HtmlWebpackPlugin',
191+
compiler.hooks.thisCompilation.tap('HtmlWebpackPlugin',
192192
/**
193-
* Hook into the webpack emit phase
193+
* Hook into the webpack compilation
194194
* @param {WebpackCompilation} compilation
195+
*/
196+
(compilation) => {
197+
compilation.hooks.processAssets.tapAsync(
198+
{
199+
name: 'HtmlWebpackPlugin',
200+
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
201+
},
202+
/**
203+
* Hook into the PROCESS_ASSETS_STAGE_ADDITIONS hook
204+
* @param {WebpackCompilation} compilationAssets
195205
* @param {(err?: Error) => void} callback
196206
*/
197-
(compilation, callback) => {
198-
// Get all entry point names for this html file
199-
const entryNames = Array.from(compilation.entrypoints.keys());
200-
const filteredEntryNames = filterChunks(entryNames, options.chunks, options.excludeChunks);
201-
const sortedEntryNames = sortEntryChunks(filteredEntryNames, options.chunksSortMode, compilation);
202-
203-
const templateResult = options.templateContent
204-
? { mainCompilationHash: compilation.hash }
205-
: childCompilerPlugin.getCompilationEntryResult(options.template);
206-
207-
if ('error' in templateResult) {
208-
compilation.errors.push(prettyError(templateResult.error, compiler.context).toString());
209-
}
207+
(compilationAssets, callback) => {
208+
// Get all entry point names for this html file
209+
const entryNames = Array.from(compilation.entrypoints.keys());
210+
const filteredEntryNames = filterChunks(entryNames, options.chunks, options.excludeChunks);
211+
const sortedEntryNames = sortEntryChunks(filteredEntryNames, options.chunksSortMode, compilation);
210212

211-
const compiledEntries = 'compiledEntry' in templateResult ? {
212-
hash: templateResult.compiledEntry.hash,
213-
chunk: templateResult.compiledEntry.entry
214-
} : {
215-
hash: templateResult.mainCompilationHash
216-
};
213+
const templateResult = options.templateContent
214+
? { mainCompilationHash: compilation.hash }
215+
: childCompilerPlugin.getCompilationEntryResult(options.template);
217216

218-
const childCompilationOutputName = compilation.getAssetPath(options.filename, compiledEntries);
219-
220-
// If the child compilation was not executed during a previous main compile run
221-
// it is a cached result
222-
const isCompilationCached = templateResult.mainCompilationHash !== compilation.hash;
217+
if ('error' in templateResult) {
218+
compilation.errors.push(prettyError(templateResult.error, compiler.context).toString());
219+
}
223220

224-
// Turn the entry point names into file paths
225-
const assets = htmlWebpackPluginAssets(compilation, childCompilationOutputName, sortedEntryNames, options.publicPath);
221+
const compiledEntries = 'compiledEntry' in templateResult ? {
222+
hash: templateResult.compiledEntry.hash,
223+
chunk: templateResult.compiledEntry.entry
224+
} : {
225+
hash: templateResult.mainCompilationHash
226+
};
226227

227-
// If the template and the assets did not change we don't have to emit the html
228-
const newAssetJson = JSON.stringify(getAssetFiles(assets));
229-
if (isCompilationCached && options.cache && assetJson === newAssetJson) {
230-
return callback();
231-
} else {
232-
assetJson = newAssetJson;
233-
}
228+
const childCompilationOutputName = compilation.getAssetPath(options.filename, compiledEntries);
234229

235-
// The html-webpack plugin uses a object representation for the html-tags which will be injected
236-
// to allow altering them more easily
237-
// Just before they are converted a third-party-plugin author might change the order and content
238-
const assetsPromise = getFaviconPublicPath(options.favicon, compilation, assets.publicPath)
239-
.then((faviconPath) => {
240-
assets.favicon = faviconPath;
241-
return getHtmlWebpackPluginHooks(compilation).beforeAssetTagGeneration.promise({
242-
assets: assets,
243-
outputName: childCompilationOutputName,
244-
plugin: plugin
245-
});
246-
});
230+
// If the child compilation was not executed during a previous main compile run
231+
// it is a cached result
232+
const isCompilationCached = templateResult.mainCompilationHash !== compilation.hash;
247233

248-
// Turn the js and css paths into grouped HtmlTagObjects
249-
const assetTagGroupsPromise = assetsPromise
250-
// And allow third-party-plugin authors to reorder and change the assetTags before they are grouped
251-
.then(({ assets }) => getHtmlWebpackPluginHooks(compilation).alterAssetTags.promise({
252-
assetTags: {
253-
scripts: generatedScriptTags(assets.js),
254-
styles: generateStyleTags(assets.css),
255-
meta: [
256-
...generateBaseTag(options.base),
257-
...generatedMetaTags(options.meta),
258-
...generateFaviconTags(assets.favicon)
259-
]
260-
},
261-
outputName: childCompilationOutputName,
262-
plugin: plugin
263-
}))
264-
.then(({ assetTags }) => {
265-
// Inject scripts to body unless it set explicitly to head
266-
const scriptTarget = options.inject === 'head' ||
267-
(options.inject === false && options.scriptLoading !== 'blocking') ? 'head' : 'body';
268-
// Group assets to `head` and `body` tag arrays
269-
const assetGroups = generateAssetGroups(assetTags, scriptTarget);
270-
// Allow third-party-plugin authors to reorder and change the assetTags once they are grouped
271-
return getHtmlWebpackPluginHooks(compilation).alterAssetTagGroups.promise({
272-
headTags: assetGroups.headTags,
273-
bodyTags: assetGroups.bodyTags,
274-
outputName: childCompilationOutputName,
275-
plugin: plugin
276-
});
277-
});
234+
// Turn the entry point names into file paths
235+
const assets = htmlWebpackPluginAssets(compilation, childCompilationOutputName, sortedEntryNames, options.publicPath);
278236

279-
// Turn the compiled template into a nodejs function or into a nodejs string
280-
const templateEvaluationPromise = Promise.resolve()
281-
.then(() => {
282-
if ('error' in templateResult) {
283-
return options.showErrors ? prettyError(templateResult.error, compiler.context).toHtml() : 'ERROR';
284-
}
285-
// Allow to use a custom function / string instead
286-
if (options.templateContent !== false) {
287-
return options.templateContent;
237+
// If the template and the assets did not change we don't have to emit the html
238+
const newAssetJson = JSON.stringify(getAssetFiles(assets));
239+
if (isCompilationCached && options.cache && assetJson === newAssetJson) {
240+
return callback();
241+
} else {
242+
assetJson = newAssetJson;
288243
}
289-
// Once everything is compiled evaluate the html factory
290-
// and replace it with its content
291-
return ('compiledEntry' in templateResult)
292-
? plugin.evaluateCompilationResult(templateResult.compiledEntry.content, options.template)
293-
: Promise.reject(new Error('Child compilation contained no compiledEntry'));
294-
});
295-
296-
const templateExectutionPromise = Promise.all([assetsPromise, assetTagGroupsPromise, templateEvaluationPromise])
297-
// Execute the template
298-
.then(([assetsHookResult, assetTags, compilationResult]) => typeof compilationResult !== 'function'
299-
? compilationResult
300-
: executeTemplate(compilationResult, assetsHookResult.assets, { headTags: assetTags.headTags, bodyTags: assetTags.bodyTags }, compilation));
301-
302-
const injectedHtmlPromise = Promise.all([assetTagGroupsPromise, templateExectutionPromise])
303-
// Allow plugins to change the html before assets are injected
304-
.then(([assetTags, html]) => {
305-
const pluginArgs = { html, headTags: assetTags.headTags, bodyTags: assetTags.bodyTags, plugin: plugin, outputName: childCompilationOutputName };
306-
return getHtmlWebpackPluginHooks(compilation).afterTemplateExecution.promise(pluginArgs);
307-
})
308-
.then(({ html, headTags, bodyTags }) => {
309-
return postProcessHtml(html, assets, { headTags, bodyTags });
310-
});
311244

312-
const emitHtmlPromise = injectedHtmlPromise
313-
// Allow plugins to change the html after assets are injected
314-
.then((html) => {
315-
const pluginArgs = { html, plugin: plugin, outputName: childCompilationOutputName };
316-
return getHtmlWebpackPluginHooks(compilation).beforeEmit.promise(pluginArgs)
317-
.then(result => result.html);
318-
})
319-
.catch(err => {
320-
// In case anything went wrong the promise is resolved
321-
// with the error message and an error is logged
322-
compilation.errors.push(prettyError(err, compiler.context).toString());
323-
return options.showErrors ? prettyError(err, compiler.context).toHtml() : 'ERROR';
324-
})
325-
.then(html => {
326-
// Allow to use [templatehash] as placeholder for the html-webpack-plugin name
327-
// See also https://survivejs.com/webpack/optimizing/adding-hashes-to-filenames/
328-
// From https://github.com/webpack-contrib/extract-text-webpack-plugin/blob/8de6558e33487e7606e7cd7cb2adc2cccafef272/src/index.js#L212-L214
329-
const finalOutputName = childCompilationOutputName.replace(/\[(?:(\w+):)?templatehash(?::([a-z]+\d*))?(?::(\d+))?\]/ig, (_, hashType, digestType, maxLength) => {
330-
return loaderUtils.getHashDigest(Buffer.from(html, 'utf8'), hashType, digestType, parseInt(maxLength, 10));
245+
// The html-webpack plugin uses a object representation for the html-tags which will be injected
246+
// to allow altering them more easily
247+
// Just before they are converted a third-party-plugin author might change the order and content
248+
const assetsPromise = getFaviconPublicPath(options.favicon, compilation, assets.publicPath)
249+
.then((faviconPath) => {
250+
assets.favicon = faviconPath;
251+
return getHtmlWebpackPluginHooks(compilation).beforeAssetTagGeneration.promise({
252+
assets: assets,
253+
outputName: childCompilationOutputName,
254+
plugin: plugin
255+
});
256+
});
257+
258+
// Turn the js and css paths into grouped HtmlTagObjects
259+
const assetTagGroupsPromise = assetsPromise
260+
// And allow third-party-plugin authors to reorder and change the assetTags before they are grouped
261+
.then(({ assets }) => getHtmlWebpackPluginHooks(compilation).alterAssetTags.promise({
262+
assetTags: {
263+
scripts: generatedScriptTags(assets.js),
264+
styles: generateStyleTags(assets.css),
265+
meta: [
266+
...generateBaseTag(options.base),
267+
...generatedMetaTags(options.meta),
268+
...generateFaviconTags(assets.favicon)
269+
]
270+
},
271+
outputName: childCompilationOutputName,
272+
plugin: plugin
273+
}))
274+
.then(({ assetTags }) => {
275+
// Inject scripts to body unless it set explicitly to head
276+
const scriptTarget = options.inject === 'head' ||
277+
(options.inject === false && options.scriptLoading !== 'blocking') ? 'head' : 'body';
278+
// Group assets to `head` and `body` tag arrays
279+
const assetGroups = generateAssetGroups(assetTags, scriptTarget);
280+
// Allow third-party-plugin authors to reorder and change the assetTags once they are grouped
281+
return getHtmlWebpackPluginHooks(compilation).alterAssetTagGroups.promise({
282+
headTags: assetGroups.headTags,
283+
bodyTags: assetGroups.bodyTags,
284+
outputName: childCompilationOutputName,
285+
plugin: plugin
286+
});
287+
});
288+
289+
// Turn the compiled template into a nodejs function or into a nodejs string
290+
const templateEvaluationPromise = Promise.resolve()
291+
.then(() => {
292+
if ('error' in templateResult) {
293+
return options.showErrors ? prettyError(templateResult.error, compiler.context).toHtml() : 'ERROR';
294+
}
295+
// Allow to use a custom function / string instead
296+
if (options.templateContent !== false) {
297+
return options.templateContent;
298+
}
299+
// Once everything is compiled evaluate the html factory
300+
// and replace it with its content
301+
return ('compiledEntry' in templateResult)
302+
? plugin.evaluateCompilationResult(templateResult.compiledEntry.content, options.template)
303+
: Promise.reject(new Error('Child compilation contained no compiledEntry'));
304+
});
305+
306+
const templateExectutionPromise = Promise.all([assetsPromise, assetTagGroupsPromise, templateEvaluationPromise])
307+
// Execute the template
308+
.then(([assetsHookResult, assetTags, compilationResult]) => typeof compilationResult !== 'function'
309+
? compilationResult
310+
: executeTemplate(compilationResult, assetsHookResult.assets, { headTags: assetTags.headTags, bodyTags: assetTags.bodyTags }, compilation));
311+
312+
const injectedHtmlPromise = Promise.all([assetTagGroupsPromise, templateExectutionPromise])
313+
// Allow plugins to change the html before assets are injected
314+
.then(([assetTags, html]) => {
315+
const pluginArgs = { html, headTags: assetTags.headTags, bodyTags: assetTags.bodyTags, plugin: plugin, outputName: childCompilationOutputName };
316+
return getHtmlWebpackPluginHooks(compilation).afterTemplateExecution.promise(pluginArgs);
317+
})
318+
.then(({ html, headTags, bodyTags }) => {
319+
return postProcessHtml(html, assets, { headTags, bodyTags });
320+
});
321+
322+
const emitHtmlPromise = injectedHtmlPromise
323+
// Allow plugins to change the html after assets are injected
324+
.then((html) => {
325+
const pluginArgs = { html, plugin: plugin, outputName: childCompilationOutputName };
326+
return getHtmlWebpackPluginHooks(compilation).beforeEmit.promise(pluginArgs)
327+
.then(result => result.html);
328+
})
329+
.catch(err => {
330+
// In case anything went wrong the promise is resolved
331+
// with the error message and an error is logged
332+
compilation.errors.push(prettyError(err, compiler.context).toString());
333+
return options.showErrors ? prettyError(err, compiler.context).toHtml() : 'ERROR';
334+
})
335+
.then(html => {
336+
// Allow to use [templatehash] as placeholder for the html-webpack-plugin name
337+
// See also https://survivejs.com/webpack/optimizing/adding-hashes-to-filenames/
338+
// From https://github.com/webpack-contrib/extract-text-webpack-plugin/blob/8de6558e33487e7606e7cd7cb2adc2cccafef272/src/index.js#L212-L214
339+
const finalOutputName = childCompilationOutputName.replace(/\[(?:(\w+):)?templatehash(?::([a-z]+\d*))?(?::(\d+))?\]/ig, (_, hashType, digestType, maxLength) => {
340+
return loaderUtils.getHashDigest(Buffer.from(html, 'utf8'), hashType, digestType, parseInt(maxLength, 10));
341+
});
342+
// Add the evaluated html code to the webpack assets
343+
compilation.emitAsset(finalOutputName, new webpack.sources.RawSource(html, false));
344+
return finalOutputName;
345+
})
346+
.then((finalOutputName) => getHtmlWebpackPluginHooks(compilation).afterEmit.promise({
347+
outputName: finalOutputName,
348+
plugin: plugin
349+
}).catch(err => {
350+
console.error(err);
351+
return null;
352+
}).then(() => null));
353+
354+
// Once all files are added to the webpack compilation
355+
// let the webpack compiler continue
356+
emitHtmlPromise.then(() => {
357+
callback();
331358
});
332-
// Add the evaluated html code to the webpack assets
333-
compilation.emitAsset(finalOutputName, new webpack.sources.RawSource(html, false));
334-
return finalOutputName;
335-
})
336-
.then((finalOutputName) => getHtmlWebpackPluginHooks(compilation).afterEmit.promise({
337-
outputName: finalOutputName,
338-
plugin: plugin
339-
}).catch(err => {
340-
console.error(err);
341-
return null;
342-
}).then(() => null));
343-
344-
// Once all files are added to the webpack compilation
345-
// let the webpack compiler continue
346-
emitHtmlPromise.then(() => {
347-
callback();
348-
});
359+
});
349360
});
350361

351362
/**

0 commit comments

Comments
 (0)