preface

Earlier, we learned how the core webPack functionality enables source code transformation and packaging.

  1. Based on the@babel/parserConvert the string toASTAbstract syntax tree.
  2. rightASTFor modification, both to the source code modification.
  3. usebabeltheAPIAnd then put the modifiedASTConvert to a new string (desired code).
  4. And understandcss-loaderandstyle-loaderThe basic implementation of.

Let’s take a look at the usage scenarios and how the Plugin works.

Lead the problem

  1. The basic code structure of a plug-in
  2. The webPack build process
  3. What does Tapable do by itself, and how does it link webPack plug-ins together?
  4. compilerAs well ascompilationObject and the main API

The basic structure of the plug-in

Here is a simple version of the MD file into an HTML file a case

The use of plug-in

const {resolve} = require('path')
const MdToHtmlPlugin = require('./plugins/md-to-html-plugin')

module.exports = {
  mode: 'development'.entry: resolve(__dirname, 'src/app.js'),
  output: {
    path: resolve(__dirname, 'dist'),
    filename: 'app.js'
  },
  plugins: [
    new MdToHtmlPlugin({
      template: resolve(__dirname, 'test.md'),
      filename: 'test.html'}})]Copy the code

Define the plugin

const fs = require('fs')
const path = require('path')

function mdToHtml(mdStr) {
  let html = ' '
  // Filter out the empty ones
  let mdArrTemp = mdStr.split('\n').filter(item= >item ! = ="")
  //ol 1. 
  //ul - 
  //h2 ## 
  let olReg = /\d\.\s/,
      ulReg = /\-\s/,
      h2Reg = /\#{2}\s/

  mdArrTemp.forEach((item, index) = > {
    // There is no previous element, or the previous element is not the current traversal element
    let prev = mdArrTemp[index - 1];
    let next = mdArrTemp[index + 1];

    if (h2Reg.test(item)) {
      html += item.replace(h2Reg, "<h2>");
      html += "</h2>\n";
    } else if (ulReg.test(item)) {
      if(! prev || ! ulReg.test(prev)) { html +="<ul>\n";
      }
      html += item.replace(ulReg, " 
  • "
  • ); html += "</li>\n"; if(! next || ! ulReg.test(next)) { html +="</ul>\n"; }}else if (olReg.test(item)) { if(! prev || ! olReg.test(prev)) { html +="<ol>\n"; } html += item.replace(olReg, "
  • "
  • ); html += "</li>\n"; if(! next || ! olReg.test(next)) { html +="<ol>\n"; }}})return html } class MdToHtmlPlugin { // You can pass instance arguments inside the constructor constructor({template, filename}) { if(! template) {throw new Error('"template" must be configured')}this.template = template this.filename = filename } // Webpack calls the Apply method of the HelloPlugin instance to pass in the Compiler object to the plug-in. apply(compiler) { compiler.hooks.emit.tap("md-to-html-plugin".(compilation) = > { let _assets = compilation.assets // Read the original md file. let mdContent = fs.readFileSync(this.template, "utf-8"); let htmlTemplateContent = fs.readFileSync(path.resolve(__dirname, './template.html')).toString() let html = mdToHtml(mdContent) // Read the template raw file. // Replace the format of the MD file with HTML. // Insert the HTML generated by the substitution into the HTML template file. let genHtml = htmlTemplateContent.replace("<! --md-->", html); fs.writeFileSync(path.resolve(__dirname, '.. /.. /dist/'+this.filename), genHtml, 'utf-8')}); }}module.exports = MdToHtmlPlugin; Copy the code

    How does the plug-in work?

    1. We use it first when reading the WebPack configurationnew MdToHtmlPlugin(options)I initialized oneMdToHtmlPluginThe instance
    2. It’s also initialized in other codecompilerObject, calledMdToHtmlPlugin.apply(compiler)Method passed to the plug-incompilerObject.
    3. The plug-in getscompilerObject can be calledcompiler.hooks.<hook name>.call. Specific events are documented.

    node events && tabable

    node

    The Events module provides only one object: Events.EventEmitter. The core of EventEmitter is the encapsulation of event triggering and event listener functions.

    var EventEmitter = require('events').EventEmitter; 
    var event = new EventEmitter(); 
    event.on('some_event'.function() { 
        console.log('some_event event triggered '); 
    }); 
    setTimeout(function() { 
        event.emit('some_event'); 
    }, 1000); 
    Copy the code

    tapable

    github tapable

    const { 
    SyncHook,
    SyncBailHook, 
    SyncWaterfallHook,
    SyncLoopHook,
    AsyncParallelHook,
    AsyncParallelBailHook,
    AsyncSeriesHook, 
    AsyncSeriesBailHook,
    AsyncSeriesWaterfallHook
    } = require("tapable");
    
    Copy the code

    Implement a simple tapable

    class Hook{
        constructor(args){
            this.taps = []
            this._args = args 
        }
        tap(name,fn){
            this.taps.push({name,fn})
        }
    }
    class SyncHook extends Hook{
        call(name,fn){
            try {
                this.taps.forEach(tap= > tap.fn(name))
                fn(null,name)
            } catch (error) {
                fn(error)
            }
    
        }
    }
    
    / / use
    
    let$=new SyncHook()
    $.tap('xx'.() = > {console.log('xx')})
    $.call('xx'.() = > {console.log('xx called')})
    Copy the code

    Understand sync-type hooks

    1, SyncHook

    const {SyncHook} = require('tapable')
    
    // Create an instance
    const syncHook = new SyncHook(['name'.'age'])
    
    // Register events
    
    syncHook.tap('1'.(name, age) = > {
      console.log('1' ,name , age)
    })
    
    syncHook.tap("2".(name, age) = > {
      console.log("2", name, age);
    });
    
    syncHook.tap("3".(name, age) = > {
      console.log("3", name, age);
    });
    
    // Trigger the event to execute the callback function
    
    syncHook.call('morning'.10)
    Copy the code

    Understand Async hooks

    1, AsyncSeriesHook

    const { AsyncSeriesHook } = require("tapable");
    
    // Create a real column
    const asyncSeriesHook = new AsyncSeriesHook(["name"."age"]);
    
    // Register events
    asyncSeriesHook.tapAsync("1".(name, age, done) = > {
      setTimeout(() = > {
        console.log("1", name, age, new Date());
        done();
      }, 1000);
    });
    
    asyncSeriesHook.tapAsync("2".(name, age, done) = > {
      setTimeout(() = > {
        console.log("2", name, age, new Date());
        done();
      }, 2000);
    });
    
    asyncSeriesHook.tapAsync("3".(name, age, done) = > {
      setTimeout(() = > {
        console.log("3", name, age, new Date());
        done();
      }, 3000);
    });
    
    // Trigger the event to make the listener function execute
    asyncSeriesHook.callAsync("Moon".10.() = > {
      console.log("Execution completed");
    });
    Copy the code

    How do Tapable and Webpack relate

    Compiler.js

    const { AsyncSeriesHook ,SyncHook } = require("tapable");
    / / create the class
    class Compiler {
      constructor(options) {
        // Take the plugin from options and pass in the apply method
        this.hooks = {
          run: new SyncHook(["run"]), // Synchronize the hook
          compile: new AsyncSeriesHook(["name"."age"]), // Asynchronous hooks
          done: new SyncHook(['done'])}; options.plugins[0].apply(this);
      }
      run() {
        // Execute asynchronous hooks
        this.hooks.run.call();
        this.compile()
      }
      compile() {
        // Execute the sync hook and pass the parameter
        this.hooks.compile.callAsync("Moon".10.(err) = > {
          this.done();
        });
      }
      done() {
        this.hooks.done.call(); }}module.exports = Compiler
    Copy the code
    • MyPlugin.js
    const Compiler = require("./Compiler");
    
    class MyPlugin {
      apply(compiler) {
        // This is the hook,
        // Wait for the hook callback function to trigger within webpack at some point and add your own logic to it
        // Accept the compiler argument
        compiler.hooks.run.tap("MyPlugin".() = > console.log("Start compiling..."));
        // Trigger the asynchronous hook
        compiler.hooks.compile.tapAsync("MyPlugin".(name, age, done) = > {
          setTimeout(() = > {
            console.log('Compiling... Received parameter name:${name}-age:${age}Compilation of... `);
            done();
          }, 3000);
        });
        // Synchronize the hook
        compiler.hooks.done.tap("MyPlugin".() = > console.log("Finish compiling...")); }}This is similar to the plugins configuration for webpack.config.js
    // Pass a new instance to the plugins property
    
    const myPlugin = new MyPlugin();
    
    const options = {
      plugins: [myPlugin],
    };
    let compiler = new Compiler(options);
    compiler.run();
    
    Copy the code

    After running the code above, you get the following output.

    By the way, make fun of the code in the original text, and see several versions of the code.

    Webpack build process

    1. Verify configuration files: read command line incoming orwebpack.config.jsFile that initializes the configuration parameters for this build
    2. generateCompilerObject: Executes the plug-in instantiation statement in the configuration filenew MyWebpackPlugin()forwebpackEvent flow hang customhooks
    3. Enter theentryOptionPhase:webpackStart reading configurationEntries, recursively traverses all entry files
    4. run/watch: If run inwatchMode executeswatchMethod, otherwise executerunmethods
    5. compilation: createCompilationObject callbackcompilationRelated hooks to enter each entry file in turn (entry), use loader to compile the file. throughcompilationI can read itmoduletheresource(Resource path),loaders(Loader used). Then use the compiled file contentsacornParsing generates an AST static syntax tree. This process is then recursively and repeatedly executed after all modules and dependencies are analyzedcompilationsealMethod To sort, optimize and encapsulate each chunk__webpack_require__To simulate modular operations.
    6. emit: All files have been compiled and converted, containing the final output resources that we can call back to in the incoming eventcompilation.assetsGet the required data, including the resources to be exported, chunks of code, and so on.
    // Modify or add the compilation. Assets ['new-file.js'] = {source() {return 'var a=1'; }, size() { return this.source().length; }}; Copy the codeCopy the code
    1. afterEmit: The file has been written to the disk
    2. doneFinish compiling

    Again, with a picture

    Compiler (is responsible for compiling)

    The Compiler module is the main engine for WebPack and creates a Compilation instance through all the options passed through the CLI or Node API. It extends from the Tapable class to register and invoke plug-ins. Most user-oriented plug-ins are registered with Compiler first.

    When developing plug-ins for WebPack, you may need to know where each hook function is called. To learn about this, search for hooks.

    .call in the Webpack source code. Refer to plugins in webpack

    Compilation (Responsible for creating bundles)

    In a nutshell,Compilation is about building modules and chunks and optimizing the build process with plug-ins.

    Commonly used API

    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

    Webpack document link

    reference

    Cnblogs nuggets

    series

    Getting started with Webpack to Mastering one (AST, Babel, Dependencies)

    Getting started with Webpack to Mastering 2 (Core Principles)

    Webpack entry to Master three (Loader principle)

    Webpack entry to Master four (Plugin principle)

    Webpack Getting started to Mastering 5 (Common Configuration)