As a bug who can type code for 5 minutes and debug for 2 hours, I have to deal with console.log a lot every day. As I am getting lazy in middle age, I want to change console.log(‘bug: ‘, bug) into log.bug to make my lazy cancer more thorough. So I took a hard look at the writing method of webpack plug-in and recorded the study notes of Webpack system here.

Follow webpack source code to cross the river

Go to! Pikachu!!

  1. Webpack initialization
// webpack.config.js

const webpack = require('webpack');
const WebpackPlugin = require('webpack-plugin');

const options = {
  entry: 'index.js'.output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index.bundle.js'
  },
  module: {
    rules: []},plugins: [
    new WebpackPlugin()
  ]
  // ...
};

webpack(options);
Copy the code
// webpack.js

// Introduce the Compiler class
const Compiler = require("./Compiler");

// Webpack entry function
const webpack = (options, callback) = > {

  // Create an instance of Compiler
  compiler = new Compiler(options.context);
  
  // The instance holds the webPack configuration object
  compiler.options = options
  
  // Call the Apply method of the Webpack plug-in in turn, passing in the Compiler reference
  for (const plugin of options.plugins) {
    plugin.apply(compiler);
  }
  
  // Execute the instance's run method
  compiler.run(callback)
  
  // Return the instance
  return compiler
}

module.exports = webpack
Copy the code
  • Initially, we call the Webpack function (webpack) whenever we type the Webpack directive on the console or use the Node.js API.The source code), and passed in the configuration options for WebPack to create onecompilerInstance.
  • compilerWhat is? – Apparently compiler saves the full webPack configuration parameter options. So officials say:

The Compiler object represents the complete Configuration of the WebPack environment. This object is created once when webPack is started, and all the actionable Settings are configured, including options, Loader, and plugin. You can use compiler to access the main webPack environment.

  • allwebpack-pluginAlso here by offering a calledapplyMethod to webpack to complete the initialization and receive the newly createdcompilerThe reference.
  1. Compiler, Compilation and Tapable
// Compiler.js

const {
  Tapable,
  SyncHook,
  SyncBailHook,
  AsyncParallelHook,
  AsyncSeriesHook
} = require("tapable");

const Compilation = require("./Compilation");

class Compiler extends Tapable {

  hooks = [
    hook1: new SyncHook(),
    hook2: new AsyncSeriesHook(),
    hook3: new AsyncParallelHook()
  ]

  run () {
    // ...
    // Trigger hook callback execution
    this.hooks[hook1].call()
    this.hooks[hook2].callAsync((a)= > {
      this.hooks[hook4].call()
    })

    // Enter the compilation process
    this.compile()
    // ...
    this.hooks[hook3].promise((a)= > {})
  }

  compile () {
    // Create a Compilation instance
    const compilation = new Compilation(this)}}Copy the code
  • Explore the compiler.js file, which introduces a library of tapable classes that provide Hook classes that internally implement a similar event subscription/publishing system.

  • Where is the Hook class used? — There are many interesting hooks in the Compiler class (compiler.js source code). These hooks represent each key event node in the whole compilation process. They are inherited from the hook class, so they all support listening/subscription. When the compilation process reaches the event point, it will execute the event callback we provided and do what we want.

  • How do I subscribe? There is a apply method in each webpack-plugin above. In fact, the registration code is hidden inside, through code similar to the following. Any Webpack plug-in can subscribe to HOOk1, so Hook1 maintains a TAPS array that holds all the callbacks.

    Compiler.hook1.tap (name, callback) // Register/subscribe

    Compiler. Hooks. Hook1. Call () / / trigger/release

  • Once the preparation is complete, compilation begins when the run() method is called. In this method, when events such as Call /callAsync/ Promise are executed (they are managed by the taps plugins that are triggered within WebPack, including some official WebPack plugins), the corresponding hook executes all of its taps functions once. The general logic is as follows.

  • Where the hooks are executed in the compiled process order, the hooks are dependent on each other.

  • The Compilation instance is also created here, which represents a resource version build. Whenever a file change is detected, a new compilation is created to generate a new set of compilation resources. A compilation object represents the current module resources, the compile-generated resources, the changing files, and the state information of the dependencies being tracked. The compilation, like compiler, has many hooks, so it also provides many event nodes to subscribe to.

  1. The use of the Webpack plugin system
class MyPlugin {
  apply(compiler) {
    // Set the callback to access the compilation object:
    compiler.hooks.compilation.tap('myPlugin', (compilation) => {
      // Now set the callback to access task points in compilation:
      compilation.hooks.optimize.tap('myPlugin', () = > {console.log('Hello compilation! '); }); }); }}module.exports = MyPlugin;
Copy the code
  • To subscribe tocompilercomplationAll event nodes are in the Webpack-pluginapplyWrite in the method, as shown above. Of course, if you want to get a resource in the compiler process, you should first subscribe to the corresponding Compiler event node that provides the resource reference (God created Adam first, then Eve). What parameters can be provided by each Compiler time node, as described in the hook instantiation (below)The source code
this.hooks = {
  // Get compilation and params
  compilation: new SyncHook(["compilation"."params"]),
  / / get the stats
  done: new AsyncSeriesHook(["stats"]),
  / / get a compiler
  beforeRun: new AsyncSeriesHook(["compiler"])}Copy the code

Comb through the Webpack process

After the above preliminary exploration, we should have a general grasp of several knowledge points to write webPack plug-in:

  1. The plugin providesapplyMethods forwebpackCall to initialize
  2. usetapRegistration method hook incompilercompilationCompiling process of
  3. usewebpackTo provide theapiPersonalize resources.

Now that you know how to write plug-ins, it’s just a matter of finding the right hooks and modifying resources. In a Webpack system, hooks are processes, which are the life cycle of the compile build effort. Of course, in order to understand the specific functions of hooks of all Tapable instance objects, we need to explore how all internal plug-ins of Webpack use these hooks and what work they do to summarize. It is complicated to think about, so we can only extract important processes to summarize ideas and borrow a classic diagram from Taobao. ! [webpack_flow. JPG] (file:///Users/Ando/Documents/webpack-plugin/webpack_flow.jpg) the whole compilation process roughly divided into three stages, to tidy it up now:

  1. Preparation stageInitialization of webPack
  • Webpack, in turn, calls the developer’s custom plug-inapplyMethod to allow plug-ins to register events.
  • WebpackOptionsApplyReceiver combinationThe command linewebpack.config.jsIs responsible for the initialization of the plug-in and Compiler context environment used by the underlying process within WebPack. (*PluginAll plug-ins for use within Webpack)
      // webpack.js
      // Developer-defined plug-in initialization
      if (options.plugins && Array.isArray(options.plugins)) {
        for (const plugin of options.plugins) {
          if (typeof plugin === "function") {
            plugin.call(compiler, compiler)
          } else {
            plugin.apply(compiler)
          }
        }
      }
      // ...
      // Init the plugin used within webpack
      compiler.options = new WebpackOptionsApply().process(options, compiler)
    
      // WebpackOptionsApply.js
      class WebpackOptionsApply extends OptionsApply {
        process (options, compiler) {
          // ...
          new WebAssemblyModulesPlugin({
              mangleImports: options.optimization.mangleWasmImports
          }).apply(compiler);
    
          new EntryOptionPlugin().apply(compiler);
          compiler.hooks.entryOption.call(options.context, options.entry);
    
          new CompatibilityPlugin().apply(compiler);
          new HarmonyModulesPlugin(options.module).apply(compiler);
          new AMDPlugin(options.module, options.amd || {}).apply(compiler);
          // ...}}Copy the code
  • performrun / watchTrigger (once packing/listen packing mode)compilephase
  1. Compilation phaseTo generate themodule.chunkResources.

    runcompileCompile → CreatecompilationObject.compilationThe creation of is the start of a compilation that will be done on all modulesLoad (load),Sequestration (seal),Optimization (optimiz),Block (the chunk),Hash (hash)Re-create (restore).

      module.exports = class Compiler extends Tapable {
    
        run () {
          // Declare the compile-end callback
          function onCompiled () {}
          // Triggers the run hook
          this.hooks.run.callAsync(this, err => {
            this.compile(onCompiled)
          })
        }
    
        compile(callback) {
          // ...
          BeforeCompile hooks are triggered before compilation begins
          this.hooks.beforeCompile.callAsync(params, err => {
            // Compile begins, triggering the compile hook
            this.hooks.compile.call(params);
            // Create the compilation instance
            const compilation = this.newCompilation(params);
            // Trigger the make hook
            this.hooks.make.callAsync(compilation, err => {
              // After the module has been resolved, execute the Compilation finish method
              compilation.finish();
              // Execute the seal method to seal the resource
              compilation.seal(err= > {
                After compiling, execute the afterCompile hook
                this.hooks.afterCompile.callAsync(compilation, err => {
    
                  // ...}); }); }); }); }}Copy the code
  • Load module:makeHook trigger →DllEntryPluginInternal plug-in callcompilation.addEntrycompilationSome resource generation factory methods are maintainedcompilation.dependencyFactoriesIs responsible for converting the entry file and its (loop) dependencies tomodulemoduleThe parsing process of theloader). eachmoduleThe parsing process is providedbuildModule / succeedModuleWait for the hook, allmoduleTriggered when parsing is completefinishModulesHook.
  • sequestration:sealThe method containsOptimize/block/hash, the compilation stops receiving new modules and starts generating chunks. This stage relies on some internal webpack plug-ins to optimize the Module and add hash to the chunk generated by this construction. CreateChunkAssets () uses a different template for rendering depending on the chunk type. After the completion of this phase, it represents the completion of a compilation, triggeringafterCompilehook
    • To optimize the:BannerPlugin
        compilation.hooks.optimizeChunkAssets.tapAsync('MyPlugin', (chunks, callback) => {
          chunks.forEach(chunk= > {
        	chunk.files.forEach(file= > {
        	  compilation.assets[file] = new ConcatSource(
        	    '\/**Sweet Banner**\/'.'\n',
        	    compilation.assets[file]
        	  );
        	});
          });
      
          callback();
        });
      Copy the code
    • block: Used to split chunksSplitChunksPluginPlugins listenoptimizeChunksAdvancedhook
    • The hash:createHash
  1. File generation stage
  • When the compilation is complete, triggeremitTo traverse thecompilation.assetsGenerate all files.

Write a Webpack plug-in that enhances the console.log debugging experiencesimple-log-webpack-plugin

A picture of the first to respect. (contrast picture) just write log.a, and add file path to identify field A through webpack plug-in automatic completion, easily support printing color effect, log information of the same file can be folded, giving you a simple and convenient debugging environment.

The following is the source code, welcome test feedback. github npm

const ColorHash = require('color-hash')
const colorHash = new ColorHash()

const Dependency = require('webpack/lib/Dependency');

class LogDependency extends Dependency {

  constructor(module) {
    super();
    this.module = module;
  }
}

LogDependency.Template = class {
  apply(dep, source) { const before = `; console.group('${source._source._name}'); ` const after = `; console.groupEnd(); ` const _size = source.size() source.insert(0, before) source.replace(_size, _size, after) } }; module.exports = class LogPlugin { constructor (opts) { this.options = { expression: /\blog\.(\w+)\b/ig, ... opts } this.plugin = { name:'LogPlugin'}}doLog (module) {

    if(! module._source)return
    let_val = module._source.source(), _name = module.resource; Const filedColor = colorHash.hex(module._buildhash) // Determine whether to addif (this.options.expression.test(_val)) {
      module.addDependency(new LogDependency(module));
    }

    _val = _val.replace(
      this.options.expression,
      `console.log('%c$1: % C % O, % C %s'.'color: ${filedColor}'.'color: red'.The $1.'color: pink'.'${_name}')`
    )

    return_val } apply (compiler) { compiler.hooks.compilation.tap(this.plugin, (compilation) = > {/ / registered custom dependent on Template compilation. DependencyTemplates. Set (LogDependency, new LogDependency Template ()); / / modlue processing_complete hook compilation. Hooks. SucceedModule. Tap (enclosing the plugin, Module._source. _value = this.dolog (module)})}}Copy the code