Skip to content

Commit 1eb2575

Browse files
committed
feat(hooks): Add a helper for easier hook access
1 parent 18074dd commit 1eb2575

File tree

4 files changed

+153
-47
lines changed

4 files changed

+153
-47
lines changed

index.js

+9-45
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const promisify = require('util.promisify');
66

77
// Import types
88
/* eslint-disable */
9-
/// <reference path="./index.d.ts" />
9+
/// <reference path="./typings.d.ts" />
1010
/* eslint-enable */
1111
/** @typedef {import("webpack/lib/Compiler.js")} WebpackCompiler */
1212
/** @typedef {import("webpack/lib/Compilation.js")} WebpackCompilation */
@@ -15,14 +15,14 @@ const vm = require('vm');
1515
const fs = require('fs');
1616
const _ = require('lodash');
1717
const path = require('path');
18-
const SyncWaterfallHook = require('tapable').SyncWaterfallHook;
19-
const AsyncSeriesWaterfallHook = require('tapable').AsyncSeriesWaterfallHook;
2018

2119
const htmlTagObjectToString = require('./lib/html-tags').htmlTagObjectToString;
2220

2321
const childCompiler = require('./lib/compiler.js');
2422
const prettyError = require('./lib/errors.js');
2523
const chunkSorter = require('./lib/chunksorter.js');
24+
const getHtmlWebpackPluginHooks = require('./lib/hooks.js').getHtmlWebpackPluginHooks;
25+
const getHtmlWebpackPluginHook = require('./lib/hooks.js').getHtmlWebpackPluginHook;
2626

2727
const fsStatAsync = promisify(fs.stat);
2828
const fsReadFileAsync = promisify(fs.readFile);
@@ -86,18 +86,7 @@ class HtmlWebpackPlugin {
8686
}
8787

8888
// setup hooks for third party plugins
89-
compiler.hooks.compilation.tap('HtmlWebpackPluginHooks', compilation => {
90-
// Setup the hooks only once
91-
if (compilation.hooks.htmlWebpackPluginAlterChunks) {
92-
return;
93-
}
94-
compilation.hooks.htmlWebpackPluginAlterChunks = new SyncWaterfallHook(['chunks', 'objectWithPluginRef']);
95-
compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration = new AsyncSeriesWaterfallHook(['pluginArgs']);
96-
compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing = new AsyncSeriesWaterfallHook(['pluginArgs']);
97-
compilation.hooks.htmlWebpackPluginAlterAssetTags = new AsyncSeriesWaterfallHook(['pluginArgs']);
98-
compilation.hooks.htmlWebpackPluginAfterHtmlProcessing = new AsyncSeriesWaterfallHook(['pluginArgs']);
99-
compilation.hooks.htmlWebpackPluginAfterEmit = new AsyncSeriesWaterfallHook(['pluginArgs']);
100-
});
89+
compiler.hooks.compilation.tap('HtmlWebpackPluginHooks', getHtmlWebpackPluginHooks);
10190

10291
compiler.hooks.make.tapAsync('HtmlWebpackPlugin', (compilation, callback) => {
10392
// Compile the template (queued)
@@ -126,7 +115,6 @@ class HtmlWebpackPlugin {
126115
* @param {() => void} callback
127116
*/
128117
(compilation, callback) => {
129-
const applyPluginsAsyncWaterfall = self.applyPluginsAsyncWaterfall(compilation);
130118
// Get all entry point names for this html file
131119
const entryNames = Array.from(compilation.entrypoints.keys());
132120
const filteredEntryNames = self.filterChunks(entryNames, self.options.chunks, self.options.excludeChunks);
@@ -176,7 +164,7 @@ class HtmlWebpackPlugin {
176164
})
177165
// Allow plugins to make changes to the assets before invoking the template
178166
// This only makes sense to use if `inject` is `false`
179-
.then(compilationResult => applyPluginsAsyncWaterfall('htmlWebpackPluginBeforeHtmlGeneration', false, {
167+
.then(compilationResult => getHtmlWebpackPluginHook(compilation, 'htmlWebpackPluginBeforeHtmlGeneration').promise({
180168
assets: assets,
181169
outputName: self.childCompilationOutputName,
182170
plugin: self
@@ -189,7 +177,7 @@ class HtmlWebpackPlugin {
189177
// Allow plugins to change the html before assets are injected
190178
.then(html => {
191179
const pluginArgs = {html: html, assets: assets, plugin: self, outputName: self.childCompilationOutputName};
192-
return applyPluginsAsyncWaterfall('htmlWebpackPluginBeforeHtmlProcessing', true, pluginArgs);
180+
return getHtmlWebpackPluginHook(compilation, 'htmlWebpackPluginBeforeHtmlProcessing').promise(pluginArgs);
193181
})
194182
.then(result => {
195183
const html = result.html;
@@ -198,7 +186,7 @@ class HtmlWebpackPlugin {
198186
const assetTags = self.generateHtmlTagObjects(assets);
199187
const pluginArgs = {head: assetTags.head, body: assetTags.body, plugin: self, outputName: self.childCompilationOutputName};
200188
// Allow plugins to change the assetTag definitions
201-
return applyPluginsAsyncWaterfall('htmlWebpackPluginAlterAssetTags', true, pluginArgs)
189+
return getHtmlWebpackPluginHook(compilation, 'htmlWebpackPluginAlterAssetTags').promise(pluginArgs)
202190
.then(result => self.postProcessHtml(html, assets, { body: result.body, head: result.head })
203191
.then(html => _.extend(result, {html: html, assets: assets})));
204192
})
@@ -207,7 +195,7 @@ class HtmlWebpackPlugin {
207195
const html = result.html;
208196
const assets = result.assets;
209197
const pluginArgs = {html: html, assets: assets, plugin: self, outputName: self.childCompilationOutputName};
210-
return applyPluginsAsyncWaterfall('htmlWebpackPluginAfterHtmlProcessing', true, pluginArgs)
198+
return getHtmlWebpackPluginHook(compilation, 'htmlWebpackPluginAfterHtmlProcessing').promise(pluginArgs)
211199
.then(result => result.html);
212200
})
213201
.catch(err => {
@@ -225,7 +213,7 @@ class HtmlWebpackPlugin {
225213
size: () => html.length
226214
};
227215
})
228-
.then(() => applyPluginsAsyncWaterfall('htmlWebpackPluginAfterEmit', false, {
216+
.then(() => getHtmlWebpackPluginHook(compilation, 'htmlWebpackPluginAfterEmit').promise({
229217
html: compilation.assets[self.childCompilationOutputName],
230218
outputName: self.childCompilationOutputName,
231219
plugin: self
@@ -684,30 +672,6 @@ class HtmlWebpackPlugin {
684672
files.sort();
685673
return files;
686674
}
687-
688-
/**
689-
* Helper to promisify compilation.applyPluginsAsyncWaterfall that returns
690-
* a function that helps to merge given plugin arguments with processed ones
691-
*
692-
* @param {WebpackCompilation} compilation
693-
*
694-
*/
695-
applyPluginsAsyncWaterfall (compilation) {
696-
return (eventName, requiresResult, pluginArgs) => {
697-
if (!compilation.hooks[eventName]) {
698-
compilation.errors.push(
699-
new Error('No hook found for ' + eventName)
700-
);
701-
}
702-
return compilation.hooks[eventName].promise(pluginArgs)
703-
.then(result => {
704-
if (requiresResult && !result) {
705-
throw new Error('Using ' + eventName + ' did not return a result.');
706-
}
707-
return result;
708-
});
709-
};
710-
}
711675
}
712676

713677
/**

lib/hooks.js

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// @ts-check
2+
/* eslint-disable */
3+
/// <reference path="../typings.d.ts" />
4+
/* eslint-enable */
5+
'use strict';
6+
/**
7+
* This file provides access to all public htmlWebpackPlugin hooks
8+
*
9+
* Usage:
10+
* ```js
11+
* const getHtmlWebpackPluginHooks = require('html-webpack-plugin/lib/hooks').getHtmlWebpackPluginHooks;
12+
*
13+
* compiler.hooks.compilation.tap('YOUR_PLUGIN_NAME', (compilation) => {
14+
* const htmlWebpackPluginHooks = getHtmlWebpackPluginHooks(compilation);
15+
* htmlWebpackPluginHooks.htmlWebpackPluginAfterEmit.tap('YOUR_PLUGIN_NAME', (pluginArgs) => {
16+
* // your code
17+
* });
18+
* });
19+
* ```
20+
*/
21+
22+
/** @typedef {import("webpack/lib/Compilation.js")} WebpackCompilation */
23+
/** @typedef {import("../index.js")} HtmlWebpackPlugin */
24+
25+
const AsyncSeriesWaterfallHook = require('tapable').AsyncSeriesWaterfallHook;
26+
27+
// The following typedef holds the API definition for all available hooks
28+
// to allow easier access when using ts-check or typescript inside plugins
29+
/** @typedef {{
30+
htmlWebpackPluginBeforeHtmlGeneration:
31+
AsyncSeriesWaterfallHook<{
32+
assets: {
33+
publicPath: string,
34+
js: Array<{entryName: string, path: string}>,
35+
css: Array<{entryName: string, path: string}>,
36+
manifest: string,
37+
},
38+
outputName: string,
39+
plugin: HtmlWebpackPlugin
40+
}>,
41+
htmlWebpackPluginBeforeHtmlProcessing:
42+
AsyncSeriesWaterfallHook<{
43+
html: string,
44+
assets: {
45+
publicPath: string,
46+
js: Array<{entryName: string, path: string}>,
47+
css: Array<{entryName: string, path: string}>,
48+
manifest: string,
49+
},
50+
outputName: string,
51+
plugin: HtmlWebpackPlugin,
52+
}>,
53+
htmlWebpackPluginAfterHtmlProcessing:
54+
AsyncSeriesWaterfallHook<{
55+
html: string,
56+
assets: {
57+
publicPath: string,
58+
js: Array<{entryName: string, path: string}>,
59+
css: Array<{entryName: string, path: string}>,
60+
manifest: string,
61+
},
62+
outputName: string,
63+
plugin: HtmlWebpackPlugin,
64+
}>,
65+
htmlWebpackPluginAlterAssetTags:
66+
AsyncSeriesWaterfallHook<{
67+
head: Array<HtmlTagObject>,
68+
body: Array<HtmlTagObject>,
69+
outputName: string,
70+
plugin: HtmlWebpackPlugin
71+
}>,
72+
htmlWebpackPluginAfterEmit:
73+
AsyncSeriesWaterfallHook<{
74+
html: string,
75+
outputName: string,
76+
plugin: HtmlWebpackPlugin
77+
}>,
78+
}} HtmlWebpackPluginHooks
79+
*/
80+
81+
/**
82+
* Returns all public hooks of the html webpack plugin for the given compilation
83+
*
84+
* @param {WebpackCompilation} compilation
85+
* @returns {HtmlWebpackPluginHooks}
86+
*/
87+
function getHtmlWebpackPluginHooks (compilation) {
88+
/** @type {HtmlWebpackPluginHooks} */
89+
const hooks = compilation.hooks;
90+
// Setup the hooks only once
91+
if (!hooks.htmlWebpackPluginAfterEmit) {
92+
attachHooksToCompilation(compilation);
93+
}
94+
return {
95+
htmlWebpackPluginBeforeHtmlGeneration: hooks.htmlWebpackPluginBeforeHtmlGeneration,
96+
htmlWebpackPluginBeforeHtmlProcessing: hooks.htmlWebpackPluginBeforeHtmlProcessing,
97+
htmlWebpackPluginAlterAssetTags: hooks.htmlWebpackPluginAlterAssetTags,
98+
htmlWebpackPluginAfterHtmlProcessing: hooks.htmlWebpackPluginAfterHtmlProcessing,
99+
htmlWebpackPluginAfterEmit: hooks.htmlWebpackPluginAfterEmit
100+
};
101+
}
102+
103+
/**
104+
* Add hooks to the webpack compilation object to allow foreign plugins to
105+
* extend the HtmlWebpackPlugin
106+
*
107+
* @param {WebpackCompilation} compilation
108+
*/
109+
function attachHooksToCompilation (compilation) {
110+
/** @type {HtmlWebpackPluginHooks} */
111+
const hooks = compilation.hooks;
112+
hooks.htmlWebpackPluginBeforeHtmlGeneration = new AsyncSeriesWaterfallHook(['pluginArgs']);
113+
hooks.htmlWebpackPluginBeforeHtmlProcessing = new AsyncSeriesWaterfallHook(['pluginArgs']);
114+
hooks.htmlWebpackPluginAlterAssetTags = new AsyncSeriesWaterfallHook(['pluginArgs']);
115+
hooks.htmlWebpackPluginAfterHtmlProcessing = new AsyncSeriesWaterfallHook(['pluginArgs']);
116+
hooks.htmlWebpackPluginAfterEmit = new AsyncSeriesWaterfallHook(['pluginArgs']);
117+
}
118+
119+
/**
120+
* Small workaround helper to work around https://github.com/Microsoft/TypeScript/issues/1178
121+
* Returns the hook of the given name
122+
*
123+
* @type {
124+
<T extends keyof HtmlWebpackPluginHooks>(compilation: WebpackCompilation, hookName: T) => HtmlWebpackPluginHooks[T]
125+
}
126+
*/
127+
const getHtmlWebpackPluginHook = (compilation, hookName) => {
128+
const hooks = getHtmlWebpackPluginHooks(compilation);
129+
return /** @type {any} */hooks[hookName];
130+
};
131+
132+
module.exports = {
133+
getHtmlWebpackPluginHooks,
134+
getHtmlWebpackPluginHook
135+
};

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"webpack-recompilation-simulator": "^1.3.0"
5050
},
5151
"dependencies": {
52+
"@types/tapable": "1.0.2",
5253
"html-minifier": "^3.2.3",
5354
"loader-utils": "^1.1.0",
5455
"lodash": "^4.17.10",

index.d.ts renamed to typings.d.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/**
33
* The plugin options
44
*/
5-
type HtmlWebpackPluginOptions = {
5+
interface HtmlWebpackPluginOptions {
66
/**
77
* The title to use for the generated HTML document
88
*/
@@ -22,7 +22,7 @@ type HtmlWebpackPluginOptions = {
2222
templateParameters:
2323
false // Pass an empty object to the template function
2424
| ((compilation: any, assets, options: HtmlWebpackPluginOptions) => {})
25-
| Object
25+
| {[option: string]: any}
2626
/**
2727
* The file to write the HTML to.
2828
* Defaults to `index.html`.
@@ -76,6 +76,12 @@ type HtmlWebpackPluginOptions = {
7676
* Enforce self closing tags e.g. <link />
7777
*/
7878
xhtml: boolean
79+
80+
/**
81+
* In addition to the options actually used by this plugin, you can use this hash to pass arbitrary data through
82+
* to your template.
83+
*/
84+
[option: string]: any;
7985
}
8086

8187
/**

0 commit comments

Comments
 (0)