《二十七》自定义 Plugin

Webpack 源码中有两个非常重要的类:Compiler 和 Compilation,它们通过注入插件的方式来监听 Webpack 所有的生命周期,插件的注入离不开各种各样的 Hook,它们中的 Hook 其实是创建了 Tapable 库中的各种 Hook 实例。
请添加图片描述
请添加图片描述
请添加图片描述

Tapable:

Tapable 是官方编写和维护的一个库,管理着需要的 Hook,这些 Hook 可以被应用到插件中。

Tapable 中的 Hook:

以 sync 开头的是同步的 Hook;以 async 开头的是异步的 Hook,也就是说,如果有两个事件处理回调,不会等待上一次处理回调结束后才执行下一次回调。
在这里插入图片描述

  1. 新建 src/index.js 文件,并编写代码。
    // 从 tapable 中导入需要的 hook 类
    const {SyncHook} = require('tapable')
    
    class CustomPlugin {
      constructor() {
        this.hooks = {
          // 通过 new SyncHook() 来实例化一个 hook,赋值给自定义的 hook。接受一个字符串数组作为参数,数组的值可以任意写,重要的是个数需要大于等于调用自定义 hook 注册的事件时传入的参数的个数
          syncHook: new SyncHook(['arg1', 'arg2'])
        }
    
        // 给自定义 hook 注册事件。
        // 同步 Hook 可以使用 tap 监听事件。其中,第一个参数是事件名,可以用作区分事件的标识符;第二个参数是调用自定义 hook 注册的事件时会被执行的回调函数。可以给一个 hook 注册多个事件,当调用自定义 hook 注册的事件时,都会被触发。
        this.hooks.syncHook.tap('event1', (name, age) => {
          console.log(`event1:${name}、${age}`)
        })
        this.hooks.syncHook.tap('event2', (name) => {
          console.log(`event2:${name}、${age}`)
        })
      }
    
      emit() {
        // 调用自定义 hook 注册的事件。可以多次调用
        this.hooks.syncHook.call('Lee', 18)
        this.hooks.syncHook.call('Mary', 20)
      }
    }
    
    const customPlugin = new CustomPlugin()
    customPlugin.emit()
    
  2. 运行 node ./src/index.js 命令运行该文件,可以看到效果。
    在这里插入图片描述

不同关键字的作用:

bail :

bail:在某个事件监听的函数中,如果有返回值的话,后续监听的事件不会再被执行了。

const {SyncBailHook} = require('tapable')

class CustomPlugin {
  constructor() {
    this.hooks = {
      syncBailHook: new SyncBailHook(['arg1', 'arg2'])
    }

    this.hooks.syncBailHook.tap('event1', (name, age) => {
      console.log(`event1:${name}、${age}`)
      return `${name}、${age}`
    })
    this.hooks.syncBailHook.tap('event2', (name, age) => {
      console.log(`event2:${name}、${age}`)
    })
  }

  emit() {
    this.hooks.syncBailHook.call('Lee', 18)
  }
}

const customPlugin = new CustomPlugin()
customPlugin.emit()

在这里插入图片描述
可以看到,在 event1 中返回值之后,event2 没有再被执行了。

loop:

loop:在某个事件监听的函数中,当返回值为 true,就会反复执行该事件;当返回值为 undefined 或者不返回内容,就退出事件。

waterfail:

waterfail:当有不为 undefined 的返回值时,会将这次返回的结果作为下次事件的第一个参数。

const {SyncWaterfallHook} = require('tapable')

class CustomPlugin {
  constructor() {
    this.hooks = {
      syncWaterfallHook: new SyncWaterfallHook(['arg1', 'arg2'])
    }

    this.hooks.syncWaterfallHook.tap('event1', (name, age) => {
      console.log(`event1:${name}、${age}`)
      return 'event1'
    })
    this.hooks.syncWaterfallHook.tap('event2', (name, age) => {
      console.log(`event2:${name}、${age}`)
    })
  }

  emit() {
    this.hooks.syncWaterfallHook.call('Lee', 18)
  }
}

const customPlugin = new CustomPlugin()
customPlugin.emit()

在这里插入图片描述
可以看到,在 event1 中返回的值被作为了 event2 的第一个参数。

series:

series:在一个 Hook 中,监听了多次事件,其回调函数会串行执行,只有上一个监听事件的回调函数执行完,才会执行下一个监听事件的回调函数。

const {AsyncSeriesHook} = require('tapable')

class CustomPlugin {
  constructor() {
    this.hooks = {
      asyncSeriesHook: new AsyncSeriesHook(['arg1', 'arg2'])
    }

    // 串行,只有上一个监听事件的回调函数执行完,才会执行下一个监听事件的回调函数
    // 异步 Hook 可以使用 tapAsync 异步监听事件。最后一个参数是一个回调函数,当所有的注册事件都执行完成后,才会执行该回调函数
    this.hooks.asyncSeriesHook.tapAsync('event1', (name, age, callback) => {
      setTimeout(() => {
        console.log(`event1:${name}、${age}`)
        callback()
      }, 3000)
    })
    this.hooks.asyncSeriesHook.tapAsync('event2', (name, age, callback) => {
      setTimeout(() => {
        console.log(`event2:${name}、${age}`)
        callback()
      }, 2000)
    })
  }

  emit() {
    this.hooks.asyncSeriesHook.callAsync('Lee', 18, () => {
      console.log('所有事件监听都执行完了')
    })
  }
}

const customPlugin = new CustomPlugin()
customPlugin.emit()

在这里插入图片描述
可以看到,event1 执行完,才执行 event2

parallel:

parallel:并行。在一个 Hook 中,监听了多次事件,其回调函数会并行执行。

const {AsyncParallelHook} = require('tapable')

class CustomPlugin {
  constructor() {
    this.hooks = {
      asyncParallelHook: new AsyncParallelHook(['arg1', 'arg2'])
    }

    // 并行,会同时执行多个回调函数
    // 异步 Hook 也可以使用 tapPromise 异步监听事件
    this.hooks.asyncParallelHook.tapPromise('event1', (name, age) => {
      // 需要返回一个 Promise
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log(`event1:${name}、${age}`)
          resolve()
        }, 3000)
      })
    })
    this.hooks.asyncParallelHook.tapPromise('event2', (name, age) => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log(`event2:${name}、${age}`)
          resolve()
        }, 2000)
      })
    })
  }

  emit() {
    this.hooks.asyncParallelHook.promise('Lee', 18).then(() => {
      console.log('所有事件监听都执行完了')
    })
  }
}

const customPlugin = new CustomPlugin()
customPlugin.emit()

在这里插入图片描述
可以看到,由于 event2 异步任务的事件更短,会最先完成。

自定义 Plugin:

Plugin 如何注册到 Webpack 的生命周期中?

  1. 在 Webpack 函数的 createCompiler 方法中,注册了所有的插件。
  2. 在注册插件时,会调用插件函数或者插件对象的 apply 方法。
  3. 插件方法会接收到 compiler 对象,就可以通过 compiler 对象来注册 Hook 事件。
    请添加图片描述

自定义 Plugin 的代码实现:

Webpack 中的大部分插件都是一个类,在类中需要实现一个叫做 apply 的方法;Webpack 会调用插件的 apply 方法,并将 compiler 编译器对象作为参数传入;在插件的 apply 方法中就可以通过 compiler 编译器对象获取到 Hook 并为其注册事件;当 Webpack 运行到对应的生命周期时会调用对应 Hook 注册的事件,其中回调函数就会被执行。

  1. 新建 src/index.js ,并编写代码。
    // src/index.js
    console.log('Hello World')
    
  2. 新建 src/AutoUploadPlugin.js,并编写自定义插件的代码。
    // 自定义一个插件:功能是将静态文件自动上传到服务器中
    class AutoUploadPlugin {
      // new AutoUploadPlugin() 时传入的参数
      constructor(options) {
        this.options = options
        console.log(this.options)
      }
    
      // Webpack 在注册插件时,都会调用插件的 apply 方法,并且传入 compiler 编译器对象作为参数
      apply(compiler) {
        // 为 afterEmit Hook 注册事件,当 Webpack 运行到这个生命周期时调用这个 Hook 注册的事件,回调函数就会被执行
        // afterEmit 是 Webpack 资源输出完成之后的 Hook,可以获取到 compilation 对象作为其回调函数的参数。
        compiler.hooks.afterEmit.tapAsync('AutoUploadPlugin', (compilation, callback) => {
          console.log('在此处进行静态文件上传服务器的操作')
          callback()
        })
      }
    }
    module.exports = AutoUploadPlugin
    
  3. webpack.config.js 配置文件中使用自定义插件。
    const AutoUploadPlugin = require('./src/AutoUploadPlugin')
    module.exports = {
      mode: 'development',
      plugins: [
        new AutoUploadPlugin({
          host: '123.123.123',
          port: '8080',
          username: '用户名',
          passowrd: '密码',
          remotePath: '/dist'
        })
      ]
    }
    
  4. 运行 webpack 命令进行打包,会发现,插件被执行了。
    在这里插入图片描述

HtmlWebpackPlugin 源码查看:

可以看到,HtmlWebpackPlugin 插件也是在 HtmlWebpackPlugin 类中实现了 apply 方法。
请添加图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值