UzumakiItachi
首页
  • JavaSript
  • Vue

    • Vue2
    • Vue3
  • React

    • React_18
  • WebPack
  • 浏览器相关
  • 工程化相关
  • 工作中遇到的问题以及解决方案
  • Git
  • 面试
  • 学习
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
  • 个人产出
  • 实用工具
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

UzumakiItachi

起风了,唯有努力生存。
首页
  • JavaSript
  • Vue

    • Vue2
    • Vue3
  • React

    • React_18
  • WebPack
  • 浏览器相关
  • 工程化相关
  • 工作中遇到的问题以及解决方案
  • Git
  • 面试
  • 学习
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
  • 个人产出
  • 实用工具
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • WebPack

    • WebPack热更新原理
    • 实现一个通用的WebPack配置包
    • WebPack打包多页面应用
    • WebPack构建速度以及体积优化策略
    • 手写一个简易的WebPack
    • 实现一个简易的webpack-loader
    • 实现一个简易的Webpack-plugin
      • 前言
      • 基本结构
      • compile和compilation
      • 实践
      • 总结
    • 深入WebPack code splitting
    • 实现一个合成雪碧图的loader
    • 实现一个压缩文件的plugin
    • webpack5都升级了哪些东西
    • 实现一个自动收集路由信息的webpack插件
  • 浏览器相关

  • 工程化相关

  • 工作中遇到的问题以及解决方案

  • Git

  • Vite

  • 一些小工具

  • 算法

  • 服务器

  • HTTP

  • 技术
  • WebPack
hanhanbuku
2023-05-18
目录

实现一个简易的Webpack-plugin

# 前言

plugin在webpack中也是非常重要的一环,他可以提供给了用户在webpack执行的任意周期的钩子,让用户可以在任意地方去穿插自己的想法,从而改变构建结果。下面就让我们一起来学习一下一个plugin吧

# 基本结构

一个最基本的 plugin 需要包含这些部分:

  • 一个 JavaScript 类
  • 一个 apply 方法,apply 方法在 webpack 装载这个插件的时候被调用,并且会传入 compiler 对象。
  • 使用不同的 hooks 来指定自己需要发生的处理行为
  • 在异步调用时最后需要调用 webpack 提供给我们的 callback 或者通过 Promise 的方式(后续异步编译部分会详细说)
/**
 * hookName 需要订阅的hook的名称
 * PluginName 你的插件名称,字符串格式
 */
class HelloPlugin{
  apply(compiler){
    compiler.hooks[hookName].tap(PluginName,(params)=>{
      /** do some thing */
    })
  }
}
module.exports = HelloPlugin

1
2
3
4
5
6
7
8
9
10
11
12
13

# compile和compilation

在学习plugin之前首先要来学习一下 compile和compilation,这两个对象到底是什么呢? 用官方的话来说

  • Compiler模块是 webpack 的主要引擎,它通过CLI或者Node API传递的所有选项创建出一个 compilation 实例。 它扩展(extends)自Tapable类,用来注册和调用插件。 大多数面向用户的插件会首先在Compiler上注册。
  • Compilation 模块会被 Compiler 用来创建新的 compilation 对象(或新的 build 对象)。 compilation 实例能够访问所有的模块和它们的依赖(大部分是循环依赖)。 它会对应用程序的依赖图中所有模块, 进行字面上的编译(literal compilation)。 在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、 分块(chunk)、哈希(hash)和重新创建(restore)。

通俗一点将就是compiler是webpack的实例,他贯穿了整个webpack的一生,他负责指挥整个打包过程,而Compilation就像是compiler的小兵一样,负责实施构建这一过程。同时他们都是继承自Tapable这一事件流机制。所以他们提供了大量的钩子给用户订阅,订阅不同的钩子就可以传入不同的回调函数,webpack回去逐一执行这些回调。然后改变构建结果

而plugin的hook也是有同步异步之分的。不同的情况下我们需要用不同的方法去订阅。同步的情况下采用tap方法订阅。

    compiler.hooks[hookName].tap(PluginName,(params)=>{
      /** do some thing */
    })
1
2
3

而异步的情况下又分为tapAsync和tapPromise两种方式,这两种订阅方式的写法也有细微的区别

tapAsync

使用 tapAsync 的时候,我们需要多传入一个 callback 回调,并且在结束的时候一定要调用这个回调告知 webpack 这段异步操作结束了。👇 比如:


class HelloPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync(HelloPlugin, (compilation, callback) => {
      setTimeout(() => {
        console.log('async')
        callback()
      }, 1000)
    })
  }
}
module.exports = HelloPlugin

1
2
3
4
5
6
7
8
9
10
11
12
13

tapPromise

当使用 tapPromise 来处理异步的时候,我们需要返回一个 Promise 对象并且让它在结束的时候 resolve 👇

class HelloPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapPromise(HelloPlugin, (compilation) => {
      return new Promise((resolve) => {
        setTimeout(() => {
          console.log('async')
          resolve()
        }, 1000)
      })
    })
  }
}
module.exports = HelloPlugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 实践

下面通过一个小栗子来感受一下自定义plugin

这个插件实现的功能是在打包后输出的文件夹内多增加一个 markdown 文件,文件内记录打包的时间点、文件以及文件大小的输出。

首先我们根据需求确定我们需要的 hook ,由于需要输出文件,我们需要使用 compilation 的 emitAsset 方法。 其次由于需要对 assets 进行处理,所以我们使用 compilation.hooks.processAssets ,因为 processAssets 是负责 asset 处理的钩子。 这样我们插件结构就出来了

class OutLogPlugin {
  constructor(options) {
    this.outFileName = options.outFileName
  }
  apply(compiler) {
    // 可以从编译器对象访问 webpack 模块实例
    // 并且可以保证 webpack 版本正确
    const { webpack } = compiler
    // 获取 Compilation 后续会用到 Compilation 提供的 stage
    const { Compilation } = webpack
    const { RawSource } = webpack.sources
    /** compiler.hooks.<hoonkName>.tap/tapAsync/tapPromise */
    compiler.hooks.compilation.tap('OutLogPlugin', (compilation) => {
      compilation.hooks.processAssets.tap(
        {
          name: 'OutLogPlugin',
          // 选择适当的 stage,具体参见:
          // https://webpack.js.org/api/compilation-hooks/#list-of-asset-processing-stages
          stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,
        },
        (assets) => {
          let resOutput = `buildTime: ${new Date().toLocaleString()}\n\n`
          resOutput += `| fileName  | fileSize  |\n| --------- | --------- |\n`
          Object.entries(assets).forEach(([pathname, source]) => {
            resOutput += `| ${pathname} | ${source.size()} bytes |\n`
          })
          compilation.emitAsset(
            `${this.outFileName}.md`,
            new RawSource(resOutput),
          )
        },
      )
    })
  }
}
module.exports = OutLogPlugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 总结

webpack插件主要是利用了webpack的compile实例和compilation实例继承自Tapable类,提供了一系列的钩子可供用户订阅。用户在订阅这些钩子的同时传入回调函数,webpack就会去逐一执行。从而改变构建结果。订阅方式也分为同步订阅和异步订阅。同步订阅采用tap方法,传入插件名和回调即可。异步订阅分为tapAsync和tapPromise两种方式, tapAsync的回调中多接受一个callback函数,通过调用callback来告诉webpack异步完成了。tapPromise则是在回调函数里返回一个promise,然后resolve来告诉webpack异步结束。

编辑 (opens new window)
上次更新: 2023/05/18, 18:04:33
实现一个简易的webpack-loader
深入WebPack code splitting

← 实现一个简易的webpack-loader 深入WebPack code splitting→

最近更新
01
前端检测更新,自动刷新网页
06-09
02
swiper渲染大量数据的优化方案
06-06
03
仿抖音短视频组件实现方案
02-28
更多文章>
Theme by Vdoing | Copyright © 2023-2025 UzumakiItachi | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式