zoukankan      html  css  js  c++  java
  • 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'
        })
      ]
    }
  • 相关阅读:
    lua 源码阅读 5.3.5 笔记
    lua 源码阅读 1.1 -> 2.1
    lua 1.0 源码分析 -- 总结
    lua 1.0 源码分析 -- 2 内存回收
    lua 1.0 源码分析 -- 1 lua 的虚拟指令
    protoc-c 阅读笔记
    protoc-c 安装记录
    转前端开发中常用工具函数总结
    sql 设计规范
    web.config文件详解[转]
  • 原文地址:https://www.cnblogs.com/lyraLee/p/12056653.html
Copyright © 2011-2022 走看看