Several ways to develop and modify the source code of webpack plug-in

Posted by apoc- on Tue, 21 Sep 2021 11:49:05 +0200

preface

Webpack is an indispensable tool in our daily work. It is inevitable that sometimes we need to personalize the code compiled by webpack, so it needs to be completed by webpack plug-in or modifying its code. This article focuses on some ways of developing and modifying the source code of webpack plug-in. I wrote it a long time ago webpack related articles For example: Summary of webpack front end technology , more webpack knowledge, welcome to haorooms Front end blog.

Plug in use

We must have used the webpack plug-in in our work. The usage is as follows:

// webpack.config.js
var HelloWorldPlugin = require('hello-world');
 module.exports = {  
// ... config settings here ...  
plugins: [new HelloWorldPlugin({ options: true })]
};

Plug in development

webpack plug-in development is mainly based on   Tapable   The plug-in mechanism provides rich custom API s and life cycle events, which can control each process of webpack compilation,

Compiler contain webpack All configuration information for the environment

Compilation It includes the methods corresponding to all links in the whole compilation process


class CustomPlugin {
  constructor(options) {
      ...
  }
  apply(compiler) {
    compiler.hooks.compilation.tap('CustomPlugin', (compilation) => {
      compilation.hooks.optimizeChunkAssets.tap('CustomPlugin', (chunks) => {
        console.log(chunks)
      });
    });
  }
};

Compiler represents the whole life cycle of webpack from Startup to shutdown, while Compilation only represents one Compilation, and the time to modify the source code just needs to be modified during the Compilation process. In the above example, you can get all the chunks information through the hook of optimizeChunkAssets. For specific chunks, you can modify the corresponding source code, such as adding the source code of the head and tail:

// Processing source code splicing Library
const ConcatSource = require('webpack-sources').ConcatSource;
class CustomPlugin {
  constructor(options) {
      ...
  }
  apply(compiler) {
    compiler.hooks.compilation.tap('CustomPlugin', (compilation) => {
      compilation.hooks.optimizeChunkAssets.tap('CustomPlugin', (chunks) => {
        chunks.forEach((chunk) => {
          chunk.files.forEach((fileName) => {
            // To judge the specific file to be modified, let's simply judge the entry through the file name of chunk
            if (filename.indexOf('index') > -1) {
              // Add content at the beginning and end of the source code
              compilation.assets[fileName] = new ConcatSource(
                `console.log('code before')`,
                compilation.assets[fileName],
                `console.log('code after')`,
              );
            }
          });
        });
      });
    });
  }
};

Plug ins provide a rich life cycle. In the process of modifying the source code, pay special attention to the impact of the plug-in life cycle. For example, in the optimizeChunkAssets stage, the chunk resources obtained in this stage have completed the processing of various loaders. At this time, if the newly added source code content is ES6, it will not be converted.

Introduction to webpack hooks

webpack official website address: Compiler Hooks | webpack

Introduce several commonly used hooks

    // tap synchronization
    compiler.hooks.emit.tap("tap", (compilation) => {
      console.log("***** tap *****")
    })
    // The process pauses before the tapAsync parameter cb is called
    compiler.hooks.emit.tapAsync("tapAsync", (compilation,cb) => {
      start(0);
      function start(index){
          console.log(index);
          if(index<=3){
              setTimeout(() => {
                  start(++index);
              }, 1000);
          }else{
              cb()
          }
      }
    })
    // tapPromise is called through promise
    compiler.hooks.emit.tapPromise("tapPromise", (compilation)=>{
        return new Promise((resolve,reject)=>{
            console.log("start tap-promise");
            setTimeout(()=>{
                resolve()
            },2000)
        })
    })

   compiler.hooks.afterCompile.tapAsync({
      name: 'haoroomstest',
    }, (compilation, callback) => {

      callback()
    })

Plug in development demo

Implement a plug-in that prevents code from being debugged. Thinking, inject debugger into the production environment code

const { ConcatSource } = require('webpack-sources')

class HaoroomssAddDebugger {
  /**
   * @param options.min Minimum interval seconds
   * @param options.max Maximum interval seconds
   */
  constructor (options = { min: 1, max: 20 }) {
    this.min = options.min && options.min > 0 ? options.min : 1
    this.max = options.max && options.max <= 600 ? options.max : 600
  }

  apply (compiler) {
    compiler.hooks.afterCompile.tapAsync({
      name: 'HaoroomssAddDebugger',
    }, (compilation, callback) => {
      let assetNames = Object.keys(compilation.assets)
      for (const name of assetNames) {
        if (name.endsWith('.js')) { // Skip non js files
          let seconds = Math.ceil(Math.random() * (this.max - this.min)) +
            this.min
           let appendContent = `(() => {
        function block() {
            if (
                window.outerHeight - window.innerHeight > 200 ||
                window.outerWidth - window.innerWidth > 200
            ) {
                document.body.innerHTML =
                    "Illegal debugging detected,Please close and refresh and try again!";
            }
            setInterval(() => {
                (function () {
                    return false;
                }
                    ["constructor"]("debugger")
                    ["call"]());
            }, ${seconds});
        }
        try {
            block();
        } catch (err) {}
    })();`
          compilation.updateAsset(
            name,
            old => new ConcatSource(old, '\n', appendContent),
          )
        }
      }
      callback()
    })
  }
}

module.exports = HaoroomssAddDebugger

Modify the webpack source code

1, webpack Loader injection code

After babel 7.4.0, the method of adding polyfills is recommended. It is recommended to import core JS / stable and regenerator Runtime / runtime at the entry and automatically import the corresponding packages in combination with the useBuiltIns attribute of @ Babel / preset env. We hope that the entry injection can be completed automatically in the project, rather than adding or deleting manually every time. The content processing based on the specified file can be handed over to the webpack Loader.

As one of the core capabilities of webpack, the basic work of webpack Loader is to read a file in the form of string, analyze and convert it, and then hand it over to the next link for processing.

Since it is read in the form of string, it becomes very simple to modify the source code. Combined with the test matching of webpack when specifying the Loader, you can quickly locate the specified type of file

module: {
  rules: [
    {
      // The specific rules for matching files are to match the src/index.js file
      test: /src\/index\.js/,
      use: [
        {
          loader: './customLoader.js'
        }
      ]
    }
  ]
}

Inject code into customLoader:

module.exports = function(source) {
    return `
import "core-js/stable";
import "regenerator-runtime/runtime";
${content}
  `;
}

2, Modify the source code with AST

Most of the above examples of webpack Plugin and Loader modifying the source code are through simple string splicing. If you want to specifically modify the logic of the source code, you need to modify the source code in combination with AST.

For the processing of specified file content, we can learn from the three board axe of source code processing:

Convert the source code obtained in Plugin / Loader into AST

After modification of AST, it is converted into source code

Return or write the newly generated source code to the corresponding resource file

Take Babel AST related operations as an example:

const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const parser = require('@babel/parser');
const t = require('@babel/types');
...
// Convert AST
const ast = parser.parse(source, {
    // Add sourceType and plugins according to the source code content
    sourceType: 'module',
    plugins: ['jsx', 'typescript']
});
traverse(ast, {
    Program(path) {
        // Operate and modify on the corresponding AST node
    }
});
// Generate source code
const newSource = generate(ast, {});
...

It can also be modified through compilation in the webpack plug-in.

const asset = compilation.assets[fileName];
let input = asset.source();
// After getting the source code, modify it, generally based on AST
// Write back after modification
compilation.assets[fileName] = new ConcatSource(input);

Articles related to AST are written later.

3, How to modify Entry

This method is mainly used to add polyfills to the project

For example:

module.exports = {
  entry: ['@babel/polyfill', './src/index.js'],
  ...
}

Summary

The above is the main content of today's webpack. This article comes from haorooms Front end blog

Haorooms blog - front-end blog - front-end technology, recording the technology blog of web front-end development

Welcome to haorooms front end blog

Topics: Javascript node.js Vue.js html