A brief discussion on the implementation principle of Webpack4 plugins

A brief discussion on the implementation principle of Webpack4 plugins

Preface

In wabpack, the core function is plugins, besides loader. It broadcasts a series of events during the execution of webpack. Plugins listen to these events and process the output files through webpack API. For example, hmlt-webpack-plugin copies the template index.html to the dist directory.

know

Let's first understand the basic structure of plugins through the source code
https://github.com/webpack/webpack/blob/webpack-4/lib/Compiler.js line 551

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

   // new a compiler const childCompiler = new Compiler(this.context);
  // Find all existing plugins if (Array.isArray(plugins)) {
    for (const plugin of plugins) {
       // If it exists, call plugin's apply method plugin.apply(childCompiler);
    }
  }
  
  // Traverse and find the hooks corresponding to the plugin
  for (const name in this.hooks) {
    if (
      ![
        "make",
        "compile",
        "emit",
        "afterEmit",
        "invalid",
        "done",
        "thisCompilation"
      ].includes(name)
    ) {
    
      // Find the corresponding hooks and call them, 
      if (childCompiler.hooks[name]) {
        childCompiler.hooks[name].taps = this.hooks[name].taps.slice();
      }
    }
  }
 
 // .... omitted....

  return childCompiler;
}

From the above source code, we can see that plugin is essentially a class. First, a compiler class is created, the current context is passed in, and then it is determined whether it exists. If it exists, the apply method of the corresponding plugin is directly called, and then the hooks event stream called by the corresponding plugin is found and emitted to the corresponding hooks event.
Where do hooks come from?

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

// The Compiler class above inherits from the Tapable class, and Tapable defines these hooks event flows 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;
               
            // Call the corresponding compiler through tab and pass 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;
                    }
            });
            // Omitted below......
  }

Well, after understanding the basic structure, you can infer the basic structure and usage of the plugin, which is as follows

// Define a plugins class class MyPlugins {
    // As mentioned above, a new compiler instance will be created, and the instance's apply method will be executed, passing in the corresponding compiler instance apply (compiler) {
        // Call the hooks event flow under the new compiler instance, trigger it through tab, and receive a callback function compiler.hooks.done.tap('usually the plugin nickname', (default receiving parameters) => {
            console.log('Enter execution body');
        })
    }
}
// Exportmodule.exports = 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')

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
    ]
}

Synchronous plugin plugin simulation call

class DonePlugins {
    apply (compiler) {
        compiler.hooks.done.tap('DonePlugin', (stats) => {
            console.log('Execution: Compilation completed');
        })
    }
}

module.exports = DonePlugins

Asynchronous plugin plugin simulation call

class AsyncPlugins {
    apply (compiler) {
        compiler.hooks.emit.tapAsync('AsyncPlugin', (complete, callback) => {
            setTimeout(() => {
                console.log('Execution: file emitted');
                callback()
            }, 1000)
        })
    }
}

module.exports = AsyncPlugins

Finally, compile webpack and you can see the compilation console, which prints and executes: compilation completed, execution: the file is emitted, indicating that the hooks event flow can be called and triggered.

Practice makes perfect

Now that we have understood the basic structure and how to use it, let's write a plugin. Well, let's write a file description plugin. In our daily packaging, we can package a xxx.md file into the dist directory and make a packaging description to achieve such a small function.

File Description Plugin

class FileListPlugin {
    // Initialization, get the file name constructor ({filename}) {
        this.filename = filename
    }
    // Same template format, define the apply method apply (compiler) {
        compiler.hooks.emit.tap('FileListPlugin', (compilation) => {
            // assets static resources, can print out compilation parameters, and there are many methods and properties let assets = compilation.assets;
            
            // Define the output document structure let content = `## file name resource size\r\n`
            
            // Traverse static resources and dynamically combine 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 is at the same level as node_modules, custom 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'
        })
    ]
}

OK, through the above configuration, we can see that when we package again, a xxx.md file will appear in the dist directory each time it is packaged, and the content of this file is the content above.

This is the end of this article about the implementation principle of Webpack4 plugins. For more related Webpack4 plugins content, please search for previous articles on 123WORDPRESS.COM or continue to browse the following related articles. I hope everyone will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Web development js string concatenation placeholder and conlose object API detailed explanation
  • Web project development JS function anti-shake and throttling sample code
  • Multiple solutions for cross-domain reasons in web development
  • js to realize web message board function
  • JavaScript article will show you how to play with web forms
  • JavaScript web page entry-level development detailed explanation

<<:  Detailed explanation of the use of MySQL comparison operator regular expression matching REGEXP

>>:  Summary of common commands for Ubuntu servers

Recommend

Vue+thinkphp5.1+axios to realize file upload

This article shares with you how to use thinkphp5...

How to use the realip module in Nginx basic learning

Preface There are two types of nginx modules, off...

Top 10 Js Image Processing Libraries

Table of contents introduce 1. Pica 2. Lena.js 3....

Detailed explanation of CSS3 elastic expansion box

use Flexible boxes play a vital role in front-end...

Nginx solves cross-domain issues and embeds third-party pages

Table of contents Preface difficulty Cross-domain...

How to retrieve password for mysql 8.0.22 on Mac

Mac latest version of MySQL 8.0.22 password recov...

Understanding and solutions of 1px line in mobile development

Reasons why the 1px line becomes thicker When wor...

SSH port forwarding to achieve intranet penetration

The machines in our LAN can access the external n...

Native JS to implement hover drop-down menu

JS implements a hover drop-down menu. This is a s...

What is the base tag and what does it do?

The <base> tag specifies the default addres...

What to do if the container started by docker run hangs and loses data

Scenario Description In a certain system, the fun...

Comprehensive summary of Vue3.0's various listening methods

Table of contents Listener 1.watchEffect 2.watch ...

User experience analysis of facebook dating website design

<br />Related article: Analysis of Facebook&...

How to detect Ubuntu version using command line

Method 1: Use the lsb_release utility The lsb_rel...

A brief discussion on HTML table tags

Mainly discuss its structure and some important pr...