webpack的plugin原理

plugin是webpack生态的重要组成,它为用户提供了一种可以直接访问到webpack编译过程的方式。它可以访问到编译过程触发的所有关键事件。

1. 基本概念

1. 如何实现一个插件

1. plugin实际是一个类(构造函数),通过在plugins配置中实例化进行调用。

// webpack.config.js
var MyExampleWebpackPlugin = require('my-example-webpack-plugin');

module.exports = {
  // ... 这里是其他配置 ...
  plugins: [new MyExampleWebpackPlugin({ options: xxx })]
};

2. 它在原型对象上指定了一个apply方法,入参是compiler对象

3. 指定一个事件钩子,并调用内部提供的API

4. 完成操作后,调用webpack 提供的callback方法

// 一个 JavaScript class
class MyExampleWebpackPlugin {
  // 将 `apply` 定义为其原型方法,此方法以 compiler 作为参数
  apply(compiler) {
    // 指定要附加到的事件钩子函数
    compiler.hooks.emit.tapAsync('MyExampleWebpackPlugin',
      (compilation, callback) => {// 使用 webpack 提供的 plugin API 操作构建结果
        compilation.addModule(/* ... */);
        callback();
      }
    );
  }
}

2. 实现插件的背景知识

由上面的步骤可知,插件功能的实现主要依赖于compiler和complation对象,而两者都是继承自Tapable对象。它暴露三种注册监听的方法Tapable对象主要是9种钩子:

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

其中同步四种,异步并行两种,异步串行3种。

同步钩子进行同步操作;异步钩子中进行异步操作。

compiler和compilation中的钩子都是自称自这9种钩子。钩子的工作机制类似于浏览器的事件监听。

1)生成的钩子可以注册监听事件,其中同步钩子通过tap方法监听,异步钩子通过tapAsync(+回调函数)和tapPromise(+返回promise)进行监听。

2)还可以进行拦截,通过intercept方法。

3)对于监听事件的触发,同步钩子通过call方法; 异步钩子通过callAsync方法和promise

示例1: -SyncHook

监听事件都是按照注册的顺序依次执行

const { SyncHook } = require('tapable');

const hook = new SyncHook(['name', 'age']);
hook.tap('任意字符1', (name, age) => {
  console.log(1);
  return undefined;
})
hook.tap('任意字符2', (name, age) => {
  console.log(2);
return true; }) hook.tap(
'任意字符3', (name, age) => { console.log(3); }) hook.call('lyra', 18); //传入的参数必须和初始化实例时传入的参数个数相同 // 执行顺序如下
1
2
3

示例2: -SyncBailHook

只要返回非undefined值,终止监听事件的调用

示例3:-SyncWaterfallHook

监听事件如果返回非undefined值,作为下个监听事件的第一个参数;如果返回undefined,返回之前的监听事件中最近的非undefined值

const { SyncWaterfallHook } = require('tapable');

const hook = new SyncWaterfallHook(['name', 'age']);
hook.tap('任意字符1', (name, age) => {
  console.log('1-->',name,age);
  return 19;
})
hook.tap('任意字符2', (name, age) => {
  console.log('2-->',name,age);
  return undefined;
})
hook.tap('任意字符3', (name, age) => {
  console.log('3-->',name,age);
  return 21;
})
hook.call('lyra', 18); //传入的参数必须和初始化实例时传入的参数个数相同
// 执行顺序如下
1--> lyra 18
2--> 19 18
3--> 19 18

示例4: -SyncLoopHook

只要监听事件返回的是非undefined值,则回到该钩子的第一个监听事件从头开始执行

示例5: -AsyncSeriesHook(complier.hooks.emit)

串行异步会等前一个事件结束再执行第二个监听事件

// 串行异步,则耗时需要3s
const { AsyncSeriesHook } = require('tapable');

const hook = new AsyncSeriesHook(['name', 'age']);
hook.tapPromise('任意字符1', (name, age) => {
  // 因为使用的是tapPromise监听,必须返回一个promise
  console.time(1)
  return new Promise(function(resolve, reject){
    setTimeout(function() {
      resolve(1);
    }, 1000)
  })
})
hook.tapPromise('任意字符2', (name, age) => {
  return new Promise((resolve, reject) => {
    setTimeout(function() {
      resolve(1);
      console.timeEnd(1)
    }, 2000)
  }); 
})
// 通过promise方法触发监听 hook.promise(
'lyra', 18).then(() => { // TODO });

示例6: - AsyncParallelHook(complier.hooks.make)

并行异步,所有监听函数同时执行

// 并行异步,耗时2秒,其实就是所有监听事件中耗时最长的事件
const { AsyncParallelHook } = require('tapable');

const hook = new AsyncParallelHook(['name', 'age']);
hook.tapAsync('任意字符1', (name, age, callback) => { 
  // 通过tapAsync方法注册监听;通过callback方法完成监听
  console.time(1)
  setTimeout(() => {
    callback();
  },1000)
})
hook.tapAsync('任意字符2', (name, age, callback) => {
  setTimeout(() => {
    callback();
    console.timeEnd(1)
  }, 2000)
})
// 通过callAsync方法触发监听;且必须有回调函数
hook.callAsync('lyra', 18, function(e) {
  //该函数必须存在
});

2. 自定义创建插件

1. 打包zip插件

const JsZip = require('jszip');

class ZipPlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    // emit是一个异步串行钩子
    compiler.hooks.emit.tapPromise('1', (compilation) => {
      const assets = compilation.assets;
      const zip = new JsZip();
      for(let filename in assets) {
        zip.file(filename, assets[filename].source())
      }
      // nodebuffer是node环境中的二进制形式;blob是浏览器环境
      return zip.generateAsync({type: 'nodebuffer'}).then((content) =>{
        console.log(this.options.name);
        assets[this.options.name] = {
          source() {return content}, 
          size() {return content.length} //可以省略
        }
        return new Promise((resolve, reject) => {
          resolve(compilation)
        })   
      })
    })
  }
}

module.exports = ZipPlugin;

在webpack.config.js中使用

const ZipPlugin = require('./plugins/ZipPlugin');

module.exports = {
  plugins: [
    new ZipPlugin({
      name: 'my.zip'
    })
  ]
}

猜你喜欢

转载自www.cnblogs.com/lyraLee/p/12056653.html