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'
    })
  ]
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM