Discussion on the Implementation Principle of Webpack4 plugins
- 2021-11-23 21:49:56
- OfStack
Preface
In wabpack, the core function except loader should be plugins plug-in. It broadcasts 1 series of events during the execution of webpack, and plugin will listen to these events and process the output files correspondingly through webpack Api. For example, hmlt-webpack-plugin copies the template magic sword index. html to dist directory
Recognize
First, through the source code to understand the basic structure of plugins under 1
https://github. com/webpack/webpack/blob/webpack-4/lib/Compiler. js Line 551
// Create 1 Compiler
createChildCompiler(
compilation,
compilerName,
compilerIndex,
outputOptions,
plugins // There are plug-ins inside
) {
// new 1 A Compiler
const childCompiler = new Compiler(this.context);
// Looking for everything that exists plugins Plug-ins
if (Array.isArray(plugins)) {
for (const plugin of plugins) {
// If it exists, Call the plugin Adj. apply Method
plugin.apply(childCompiler);
}
}
// Traversal search plugin Corresponding hooks
for (const name in this.hooks) {
if (
![
"make",
"compile",
"emit",
"afterEmit",
"invalid",
"done",
"thisCompilation"
].includes(name)
) {
// Find the corresponding hooks And call,
if (childCompiler.hooks[name]) {
childCompiler.hooks[name].taps = this.hooks[name].taps.slice();
}
}
}
// .... Omission ....
return childCompiler;
}
Through the above source code, we can see that plugin is essentially a class. First, new is a class of compiler, passing in the current context, then judging whether it exists, if it exists, directly calling the apply method corresponding to plugin, and then finding the hooks event stream corresponding to plugin call, and transmitting it to the corresponding hooks event
Where did hooks come from?
https://github.com/webpack/webpack/blob/webpack-4/lib/Compiler. js line 42
// Above Compiler Class inherits from Tapable Class, and Tapable That defines these hooks Event flow
class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {
/** @type {SyncBailHook<Compilation>} */
shouldEmit: new SyncBailHook(["compilation"]),
/** @type {AsyncSeriesHook<Stats>} */
done: new AsyncSeriesHook(["stats"]),
/** @type {AsyncSeriesHook<>} */
additionalPass: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<Compiler>} */
beforeRun: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compiler>} */
run: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compilation>} */
emit: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<string, Buffer>} */
assetEmitted: new AsyncSeriesHook(["file", "content"]),
/** @type {AsyncSeriesHook<Compilation>} */
afterEmit: new AsyncSeriesHook(["compilation"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
thisCompilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
compilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<NormalModuleFactory>} */
normalModuleFactory: new SyncHook(["normalModuleFactory"]),
/** @type {SyncHook<ContextModuleFactory>} */
contextModuleFactory: new SyncHook(["contextModulefactory"]),
/** @type {AsyncSeriesHook<CompilationParams>} */
beforeCompile: new AsyncSeriesHook(["params"]),
/** @type {SyncHook<CompilationParams>} */
compile: new SyncHook(["params"]),
/** @type {AsyncParallelHook<Compilation>} */
make: new AsyncParallelHook(["compilation"]),
/** @type {AsyncSeriesHook<Compilation>} */
afterCompile: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<Compiler>} */
watchRun: new AsyncSeriesHook(["compiler"]),
/** @type {SyncHook<Error>} */
failed: new SyncHook(["error"]),
/** @type {SyncHook<string, string>} */
invalid: new SyncHook(["filename", "changeTime"]),
/** @type {SyncHook} */
watchClose: new SyncHook([]),
/** @type {SyncBailHook<string, string, any[]>} */
infrastructureLog: new SyncBailHook(["origin", "type", "args"]),
// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
/** @type {SyncHook} */
environment: new SyncHook([]),
/** @type {SyncHook} */
afterEnvironment: new SyncHook([]),
/** @type {SyncHook<Compiler>} */
afterPlugins: new SyncHook(["compiler"]),
/** @type {SyncHook<Compiler>} */
afterResolvers: new SyncHook(["compiler"]),
/** @type {SyncBailHook<string, Entry>} */
entryOption: new SyncBailHook(["context", "entry"])
};
// TODO webpack 5 remove this
this.hooks.infrastructurelog = this.hooks.infrastructureLog;
// Pass tab Call the corresponding comiler Compiler, and pass in 1 Callback function
this._pluginCompat.tap("Compiler", options => {
switch (options.name) {
case "additional-pass":
case "before-run":
case "run":
case "emit":
case "after-emit":
case "before-compile":
case "make":
case "after-compile":
case "watch-run":
options.async = true;
break;
}
});
// Omitted below ......
}
Well, after understanding the basic structure, you can deduce the basic structure and usage of plugin, which is as follows
// Definition 1 A plugins Class
class MyPlugins {
// It says above new 1 Compiler instances that execute the apply Method, passing in the corresponding comiler Instances
apply (compiler) {
// Call new Come out compiler Instance under the hooks Event flow, through tab Trigger and receive 1 Callback function
compiler.hooks.done.tap('1 It is a nickname for plug-ins ', ( Default receive parameter ) => {
console.log(' Enter the actuator ');
})
}
}
// Export
module.exports = MyPlugins
ok, the above is a simple template, let's try the internal hook function, whether it will be called and triggered as desired
Configuring webpack
let path = require('path')
let DonePlugin = require('./plugins/DonePlugins')
let AsyncPlugins = require('./plugins/AsyncPlugins')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'build.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new DonePlugin(), // Internal synchronization hooks
new AsyncPlugins() // Internal asynchronous hooks
]
}
Synchronize plugin plug-in simulation call
class DonePlugins {
apply (compiler) {
compiler.hooks.done.tap('DonePlugin', (stats) => {
console.log(' Execution: Compilation complete ');
})
}
}
module.exports = DonePlugins
Asynchronous plugin plug-in simulation call
class AsyncPlugins {
apply (compiler) {
compiler.hooks.emit.tapAsync('AsyncPlugin', (complete, callback) => {
setTimeout(() => {
console.log(' Execute: The file is emitted ');
callback()
}, 1000)
})
}
}
module.exports = AsyncPlugins
Finally compile webpack can see the compilation console, print and execute respectively: compile completed, execute: file emitted, indicating that this can be called to hooks event flow, and can be triggered.
Experience is the mother of wisdom
Understand the basic structure and the way to use, now to hand-write an plugin plug-in, well, to a file to explain the plug-in, we daily package, can package an xxx. md file to dist directory, to do a package description, to achieve such a small function
File description plug-in
class FileListPlugin {
// Initialize, get the name of the file
constructor ({filename}) {
this.filename = filename
}
// The same template form, defining the apply Method
apply (compiler) {
compiler.hooks.emit.tap('FileListPlugin', (compilation) => {
// assets Static resources, which can be printed out compilation Parameters, as well as many methods and properties
let assets = compilation.assets;
// Define the output document structure
let content = `## Filename Resource size \r\n`
// Traversing static resources and dynamically combining output content
Object.entries(assets).forEach(([filename, stateObj]) => {
content += `- ${filename} ${stateObj.size()}\r\n`
})
// Output resource object
assets[this.filename] = {
source () {
return content;
},
size () {
return content.length
}
}
})
}
}
// Export
module.exports = FileListPlugin
webpack Configuration
let path = require('path')
let HtmlWebpackPlugin = require('html-webpack-plugin')
// plugins Directory and node_modules At the same level, Customize plugins , And loader Similar
let FileListPlugin = require('./plugins/FileListPlugin')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'build.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html'
}),
new FileListPlugin({
filename: 'list.md'
})
]
}
ok, through the above configuration, when we re-package, we can see that each package in the dist directory will appear an xxx. md file, and the content of this file is the content above us