Analysis of webpack plug-in principle that you must know

Time:2021-2-27

This article is included in GitHubgithub.com/Michael-lzg…

Demo source code addressgithub.com/Michael-lzg…

In webpack, a function module that focuses on a specific task in the compilation process of webpack can be called a plug-in. It andloaderThere are the following differences:

  1. loaderIs a converter, a file will be compiled into B files, such as: theA.lessConvert toA.css, simple file conversion process. Webpack itself only supports JS and JSON files. For other files, you need toloaderAfter it is converted to a file of commonjs specification, webpack can parse to.
  2. pluginIs an extender that enriches the webpack itself forloaderAfter the completion of the whole process of webpack packaging, it does not directly operate the file, but works based on the event mechanism. It will listen to some nodes in the process of webpack packaging and perform a wide range of tasks.

Features of plugin

The webpack plug-in has the following features

  • It’s a separate module.
  • The module exposes a JS function.
  • Prototype of function(prototype)An injection is defined oncompilerObjectapplymethod.
  • applyThe function needs to have a passcompilerObject. The callback of the hook can get the current compiledcompilationObject. If the plug-in is compiled asynchronously, you can get the callback.
  • Complete the self defined sub compilation process and processcomplitionObject.
  • If the plug-in is compiled asynchronously, the callback is executed after the data processing is completed.
class HelloPlugin {
  //Get the configuration passed in by the user to the plug-in in the constructor
  constructor(options) {}
  //Webpack will call the apply method of helloplugin instance to pass compiler object to plug-in instance
  apply(compiler) {
    //In the emit phase, a hook function is inserted to handle additional logic at a specific time;
    compiler.hooks.emit.tap('HelloPlugin', (compilation) => {
      //After the function flow is completed, the callback function provided by webpack can be called;
    })
    //If the event is asynchronous, it takes two parameters. The second parameter is the callback function,
    compiler.plugin('emit', function (compilation, callback) {
      //After processing, execute a callback to notify webpack
      //If the callback is not executed, the running process will always be stuck here
      callback()
    })
  }
}

module.exports = HelloPlugin
  1. Webpack reads the configuration firstnew HelloPlugin(options)Initialize aHelloPluginGet its instance.
  2. initializationcompilerCall after objectHelloPlugin.apply(compiler)Pass in the compiler object to the plug-in instance.
  3. The plug-in instance gets thecompilerObject, you can use thecompiler.plugin(event name, callback function) listens to the event broadcast by webpack. And it can be passedcompilerObject to operate webpack.

Event flow mechanism

Webpack is essentially a mechanism of event flow. Its workflow is to connect all plug-ins in series, and the core of all this is to realize itTapable

Webpack’sTapableThe event flow mechanism ensures the order of plug-ins. When all plug-ins are connected in series, webpack will broadcast events in the process of running. Plug ins only need to listen to the events they care about, and then they can join this webapck mechanism to change the operation of webapck, so that the whole system has good scalability.

TapableIt is also a small library and a core tool of webpack. Similar to the events Library in node, the core principle is oneSubscription publishing mode. The purpose is to provide a similar plug-in interface. The method is as follows

//Broadcast events
compiler.apply('event-name', params)
compilation.apply('event-name', params)

//Monitoring events
compiler.plugin('event-name', function (params) {})
compilation.plugin('event-name', function (params) {})

Let’s take a look at tapable

function Tapable() {
  this._plugins = {}
}
//Post name message
Tapable.prototype.applyPlugins = function applyPlugins(name) {
  if (!this._plugins[name]) return
  var args = Array.prototype.slice.call(arguments, 1)
  var plugins = this._plugins[name]
  for (var i = 0; i < plugins.length; i++) {
    plugins[i].apply(this, args)
  }
}
//FN subscribe to name message
Tapable.prototype.plugin = function plugin(name, fn) {
  if (!this._plugins[name]) {
    this._plugins[name] = [fn]
  } else {
    this._plugins[name].push(fn)
  }
}
//Given a plug-in array, call the plug-in's own apply method for each plug-in to register the plug-in
Tapable.prototype.apply = function apply() {
  for (var i = 0; i < arguments.length; i++) {
    arguments[i].apply(this)
  }
}

TapableIt provides a unified plug-in interface (hook) type definition for webpack, which is the core function library of webpack. There are ten kinds of hooks in webpack, which can be seen in tapable source code

exports.SyncHook = require('./SyncHook')
exports.SyncBailHook = require('./SyncBailHook')
exports.SyncWaterfallHook = require('./SyncWaterfallHook')
exports.SyncLoopHook = require('./SyncLoopHook')
exports.AsyncParallelHook = require('./AsyncParallelHook')
exports.AsyncParallelBailHook = require('./AsyncParallelBailHook')
exports.AsyncSeriesHook = require('./AsyncSeriesHook')
exports.AsyncSeriesBailHook = require('./AsyncSeriesBailHook')
exports.AsyncSeriesLoopHook = require('./AsyncSeriesLoopHook')
exports.AsyncSeriesWaterfallHook = require('./AsyncSeriesWaterfallHook')

TapableThree methods are exposed to plug-ins to inject different types of custom build behaviors

  • Tap: can register synchronous hook and asynchronous hook.
  • Tapasync: registers asynchronous hooks in callback mode.
  • Tappromise: register asynchronous hooks in promise mode.

Several very important objects in webpack,Compiler, CompilationandJavascriptParserIt’s all inheritedTapableClass, they have plenty of hooks.

Write a plug-in

A webpack plug-in consists of the following components:

  • A JavaScript named function.
  • Define an apply method on the prototype of the plug-in function.
  • Specifies an event hook bound to the webpack itself.
  • Process specific data for internal instances of webpack.
  • The callback provided by webpack is called after the function is completed.

The following is the simplest plug-in

class WebpackPlugin1 {
  constructor(options) {
    this.options = options
  }
  apply(compiler) {
    compiler.hooks.done.tap('MYWebpackPlugin', () => {
      console.log(this.options)
    })
  }
}

module.exports = WebpackPlugin1

Then register in the configuration of webpack to use itwebpack.config.jsYou can introduce and instantiate it in

const WebpackPlugin1 = require('./src/plugin/plugin1')

module.exports = {
  entry: {
    index: path.join(__dirname, '/src/main.js'),
  },
  output: {
    path: path.join(__dirname, '/dist'),
    filename: 'index.js',
  },
  plugins: [new WebpackPlugin1({ msg: 'hello world' })],
}

Now let’s do itnpm run buildYou can see the effect

Analysis of webpack plug-in principle that you must know

Compiler object (responsible for compiling)

CompilerObject contains the configuration of the currently running webpack, includingentryoutputloadersThis object is instantiated when starting webpack and is globally unique.PluginThe configuration information of webpack can be obtained through this object for processing.

Some common hooks exposed on compiler:

Analysis of webpack plug-in principle that you must know

Here’s an example

class WebpackPlugin2 {
  constructor(options) {
    this.options = options
  }
  apply(compiler) {
    compiler.hooks.run.tap('run', () => {
      console.log ('Start compiling... ')
    })

    compiler.hooks.compile.tap('compile', () => {
      console.log('compile')
    })

    compiler.hooks.done.tap('compilation', () => {
      console.log('compilation')
    })
  }
}

module.exports = WebpackPlugin2

Now let’s do itnpm run buildYou can see the effect

Analysis of webpack plug-in principle that you must know

Some of the steps in compiling plug-ins are asynchronous, so you need to pass in a callback function in addition and execute the callback function at the end of the plug-in run

class WebpackPlugin2 {
  constructor(options) {
    this.options = options
  }
  apply(compiler) {
    compiler.hooks.beforeCompile.tapAsync('compilation', (compilation, cb) => {
      setTimeout(() => {
        console.log ('compiling... ')
        cb()
      }, 1000)
    })
  }
}

module.exports = WebpackPlugin2

Compilation object

CompilationObject represents a resource version build. When running the middleware of webpack development environment, whenever a file change is detected, a new one will be createdcompilationTo generate a new set of compiled resources. OneCompilationObject represents the current module resources, compiled resources, changed files, and the state information that is tracked and depended on. In short, it stores the contents of this package and compilation in memory.CompilationObject also provides callbacks that plug-ins need to customize functions, so that plug-ins can choose to use them when they do custom processing.

In short,CompilationIt’s your responsibility to build modules and chunks, and use plug-ins to optimize the build process.

CompilerIt represents the whole life cycle of webpack from Startup to shutdownCompilationIt just represents a new compilation, as long as the file is changed,compilationIt will be recreated.

CompilationSome common hooks exposed on the surface:

Analysis of webpack plug-in principle that you must know

CompilerandCompilationThe difference between

  • CompilerIt represents the whole life cycle of webpack from Startup to shutdown
  • CompilationIt just represents a new compilation, as long as the file is changed,compilationIt will be recreated.

Handwriting plugin 1: document list

After each webpack package, a list of markdown files is automatically generated to record some information of all the files in the folder dist after package.

Thinking:

  1. adoptcompiler.hooks.emit.tapAsync()To trigger the hook before generating resources to the output directory
  2. adoptcompilation.assetsGet the number of files
  3. Define the content of the markdown file and write the file information into the markdown file
  4. Add a variable named filelistname to the dist folder
  5. Write the content and file size of the resource
  6. Execute the callback and let the webpack continue
class FileListPlugin {
  constructor(options) {
    //Get plug-in configuration item
    this.filename = options && options.filename ? options.filename : 'FILELIST.md'
  }

  apply(compiler) {
    //Register the emit hook on the compiler
    compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, cb) => {
      //Through compilation.assets  Get the number of files
      let len = Object.keys(compilation.assets).length

      //Add statistics
      let content = `# ${len} file${len > 1 ? 's' : ''} emitted by webpacknn`

      //Through compilation.assets  Get file name list
      for (let filename in compilation.assets) {
        content += `- ${filename}n`
      }

      //To compilation.assets  Add manifest file to
      compilation.assets[this.filename] = {
        //Write the contents of the new file
        source: function () {
          return content
        },
        //New file size (for webapck output display)
        size: function () {
          return content.length
        },
      }

      //Execute the callback and let the webpack continue
      cb()
    })
  }
}

module.exports = FileListPlugin

Handwriting plugin 2: remove comments

Developing a plug-in can remove the comments of the packaged code, so that ourbundle.jsIt will be easier to read

Thinking:

  1. adoptcompiler.hooks.emit.tap()To trigger the hook after generating the file
  2. adoptcompilation.assetsGet the files after production, and then traverse the files
  3. adopt.source()Get the text of the build product, and then use regular replace to call the annotated code
  4. Update build product object
  5. Execute the callback and let the webpack continue
class RemoveCommentPlugin {
  constructor(options) {
    this.options = options
  }
  apply(compiler) {
    //Remove annotation regularization
    const reg = /("([^"]*(.)?)*")|('([^']*(.)?)*')|(/{2,}.*?(r|n))|(/*(n|.)*?*/)|(/******/)/g

    compiler.hooks.emit.tap('RemoveComment', (compilation) => {
      //Traverse the build product. Assets contains the file name of the build product
      Object.keys(compilation.assets).forEach((item) => {
        //. source() is the text to get the build product
        let content = compilation.assets[item].source()
        content = content.replace(reg, function (word) {
          //Uncommented text
          return /^/{2,}/.test(word) || /^/*!/.test(word) || /^/*{3,}//.test(word) ? '' : word
        })
        //Update build product object
        compilation.assets[item] = {
          source: () => content,
          size: () => content.length,
        }
      })
    })
  }
}

module.exports = RemoveCommentPlugin

Recommended articles

Asynchronous loading principle and subcontracting strategy of webpack
Summarize 18 webpack plug-ins, there will always be what you want!
Build a mobile framework of vue-cli4 + webpack (out of the box)
From zero construction to optimization of a scaffold similar to Vue cli
Encapsulate a toast and dialog component and publish it to NPM
Building a webpack project from scratch
Summarize several methods of Web pack optimization
Summarize the advanced application of Vue knowledge system
Summarize the practical skills of Vue knowledge system
Summarize the foundation of Vue knowledge system
Summarize the common skills of mobile H5 development (full of dry goods!)