Preface
In wabpack, the core functions, besides loader, should be the plugins plugin. It will broadcast a series of events during webpack execution. The plugin will listen to these events and process the output files accordingly through the webpack API. For example, htmlt-webpack-plugin is to copy the template magic sword to the dist directory.
know
Let’s first understand the basic structure of plugins through source code
/webpack/webpack/blob/webpack-4/lib/Line 551
// Create a compilercreateChildCompiler( compilation, compilerName, compilerIndex, outputOptions, plugins // There are plug-ins included) { // new a compiler const childCompiler = new Compiler(); // Find all plugins that exist if ((plugins)) { for (const plugin of plugins) { // If it exists, call the plugin's apply method (childCompiler); } } // traversal to find the hooks corresponding to plugin for (const name in ) { if ( ![ "make", "compile", "emit", "afterEmit", "invalid", "done", "thisCompilation" ].includes(name) ) { // Find the corresponding hooks and call them. if ([name]) { [name].taps = [name].(); } } } // .... Omitted .... return childCompiler; }
From the above source code, we can see that plugin is essentially a class. First, it is new compiler class, pass it into the current context, and then determine whether it exists. If it exists, it directly calls the apply method of the corresponding plugin, and then finds the hooks event stream called by the corresponding plugin and transmits it to the corresponding hooks event.
Where did hooks come from?
/webpack/webpack/blob/webpack-4/lib/Lines 42
// The above Compiler class inherits from the Tapable class, and Tapable defines these hooks event streamsclass Compiler extends Tapable { constructor(context) { super(); = { /** @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 = ; // Call the corresponding comiler compiler through tab and pass in a callback function this._pluginCompat.tap("Compiler", options => { switch () { case "additional-pass": case "before-run": case "run": case "emit": case "after-emit": case "before-compile": case "make": case "after-compile": case "watch-run": = true; break; } }); // Omitted below... }
OK, after understanding the basic structure, you can infer the basic structure and usage of plugin, which is the following
// Define a plugins classclass MyPlugins { // It is said above that new compiler instance will execute the apply method of the instance and pass in the corresponding comicer instance apply (compiler) { // Call new to exit the hooks event stream under compiler instance, triggered through tab, and receive a callback function ('Generally a plug-in nickname', (Default receive parameters) => { ('Enter the Execution'); }) } } // Export = MyPlugins
OK, the above is a simple template. Let's try the internal hook function to see if it will be called and triggered as expected.
Configure webpack
let path = require('path') let DonePlugin = require('./plugins/DonePlugins') let AsyncPlugins = require('./plugins/AsyncPlugins') = { mode: 'development', entry: './src/', output: { filename: '', path: (__dirname, 'dist') }, plugins: [ new DonePlugin(), // Internal synchronization hooks new AsyncPlugins() // Internal asynchronous hooks ] }
Synchronous plugin mock calls
class DonePlugins { apply (compiler) { ('DonePlugin', (stats) => { ('Execution: Compilation is complete'); }) } } = DonePlugins
Asynchronous plugin mock calls
class AsyncPlugins { apply (compiler) { ('AsyncPlugin', (complete, callback) => { setTimeout(() => { ('Execution: File is emitted'); callback() }, 1000) }) } } = AsyncPlugins
Finally, when compiling webpack, you can see the compilation console, print it separately. Execute: Compilation is completed, execution: the file is emitted, which means that this can be called to the hooks event stream and can be triggered.
Practice to create true knowledge
I have learned about the basic structure and how to use it. Now I will write a plugin by hand. Well, let’s get a file description plugin. We can pack a file into the dist directory to make a packaging description, which is to implement such a small function.
File description plugin
class FileListPlugin { // Initialize, get the file name constructor ({filename}) { = filename } // In the same template form, define the apply method apply (compiler) { ('FileListPlugin', (compilation) => { // assets static resources, you can print out compilation parameters, and there are many methods and properties let assets = ; // Define the output document structure let content = `## File name Resource size\r\n` // traverse static resources and dynamically combine output content (assets).forEach(([filename, stateObj]) => { content += `- ${filename} ${()}\r\n` }) // Output resource object assets[] = { source () { return content; }, size () { return } } }) } } // Export = FileListPlugin
webpack configuration
let path = require('path') let HtmlWebpackPlugin = require('html-webpack-plugin') // The plugins directory is the same as node_modules, custom plugins, similar to loaderlet FileListPlugin = require('./plugins/FileListPlugin') = { mode: 'development', entry: './src/', output: { filename: '', path: (__dirname, 'dist') }, plugins: [ new HtmlWebpackPlugin({ template: './src/', filename: '' }), new FileListPlugin({ filename: '' }) ] }
OK, through the above configuration, we can see when we package it again. Every time we package it in the dist directory, a file will appear, and the content of this file is the content above us.
This is the end of this article about briefly discussing the implementation principle of Webpack4 plugins. For more related Webpack4 plugins content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!