preface

Plugins broadcast a series of events during the execution of Webpack. Plugin will listen to these events and process corresponding output files through webpack Api. For example, hmlT-webpack-plugin is a copy of the template index.html to the dist directory

know

Take a look at the basic structure of plugins from the source code

https://github.com/webpack/webpack/blob/webpack-4/lib/Compiler.js 551 lines


// Create a compiler
createChildCompiler(
  compilation,
  compilerName,
  compilerIndex,
  outputOptions,
  plugins // Include plugins
) {

   // new a compiler
  const childCompiler = new Compiler(this.context);
  // Find all existing plugins
  if (Array.isArray(plugins)) {
    for (const plugin of plugins) {
       // If so, call plugin's apply methodplugin.apply(childCompiler); }}// Iterate to find the hooks corresponding to plugin
  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(); }}}/ /... Omit...

  return childCompiler;
}

Copy the code

It can be seen from the above source code that plugin is essentially a class. The first is to create a compiler class, pass in the current context, and then judge whether it exists. If it does, the apply method of the corresponding plugin will be directly called. It then finds the hooks event stream corresponding to the plugin call and fires it to the hooks event

Where do hooks come from?

https://github.com/webpack/webpack/blob/webpack-4/lib/Compiler.js 42 line

// The above Compiler class inherits from the Tapable class, which defines these hooks event streams
class Compiler extends Tapable {
	constructor(context) {
            super(a);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;
               
            // Call the corresponding comiler compiler via TAB, passing in a 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; }});// below omit......
  }
Copy the code

Now that you know the basic structure, you can deduce the basic structure and usage of plugin, as follows

// Define a plugins class
class MyPlugins {
    // A new instance of the compiler will execute the apply method of the instance and pass in the corresponding comiler instance
    apply (compiler) {
        // Calls the hooks event stream under the New compiler instance, fired by TAB, and receives a callback function
        compiler.hooks.done.tap('Generally a plug-in nickname'.(Default receive parameter) = > {
            console.log('Enter the executor'); }}})/ / export
module.exports = MyPlugins
Copy the code

Ok, this is a simple template. Let’s test the internal hook function to see if it is called and fired as expected

Configuration 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]}Copy the code

The synchronous Plugin plugin simulates the invocation

class DonePlugins {
    apply (compiler) {
        compiler.hooks.done.tap('DonePlugin'.(stats) = > {
            console.log('Execute: Compile complete'); }}})module.exports = DonePlugins
Copy the code

The asynchronous plugin plugin simulates the invocation

class AsyncPlugins {
    apply (compiler) {
        compiler.hooks.emit.tapAsync('AsyncPlugin'.(complete, callback) = > {
            setTimeout(() = > {
                console.log('Execute: File is emitted');
                callback()
            }, 1000)}}}module.exports = AsyncPlugins
Copy the code

If you build webpack, you can see the build console, which prints executables: compile complete, executables: file is emitted, which means that you can call the hooks event stream and trigger it.

Practice is the mother of wisdom

Having understood the basic structure and the way to use it, now let’s write a plugin by hand. Well, let’s write a file description plug-in. We can package a xxx.md file to the dist directory to do a package description

File description plug-in

class FileListPlugin {
    // Initialize to get the file name
    constructor ({filename}) {
        this.filename = filename
    }
    // Define the apply method in the same template form
    apply (compiler) {
        compiler.hooks.emit.tap('FileListPlugin'.(compilation) = > {
            // Static assets can print the compilation parameters, as well as many methods and properties
            let assets = compilation.assets;
            
            // Define the output document structure
            let content = '## file name resource size \r\n'
            
            // Iterate over static resources and combine output dynamically
            Object.entries(assets).forEach(([filename, stateObj]) = > {
                content += ` -${filename}    ${stateObj.size()}\r\n`
            })
            
            // Outputs the resource object
            assets[this.filename] = {
                source () {
                    return content;
                },
                size () {
                    return content.length
                }
            }
            
        })
    }
}
/ / export
module.exports = FileListPlugin
Copy the code

Webpack configuration

let path = require('path')
let HtmlWebpackPlugin = require('html-webpack-plugin')
// The plugins directory is the same as node_modules to customize plugins, similar to loader
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'}})]Copy the code

Ok, through the above configuration, when we package again, we can see that every time we package in the dist directory, there will be an xxx.md file, and the content of this file is our content

Simple learning of the plugin plugin implementation principle, more understanding, more blowing water for a while, today to share here

A little encouragement, great growth, welcome to like collection