Webpack中的插件流程是怎么实现的?

一、前言

最近在学习许多框架的底层插件化实现,如ice.js、umi.js,在这些框架中,我们都可以在介绍中看到支持可插拔灵活的插件系统,那插件化的本质是怎么实现的?

此时不如回归本质,在我们学习Webpack的时候,都遇到过面试题------Webpack插件是怎么实现的?当我们思考插件化的实现,不如回到Webpack,从源码看看在前端工程化领域看看插件化是怎么设计的。

二、插件化

插件化底层涉及两个核心概念。

  1. 钩子(生命周期),在每个生命周期阶段都可以触发很多插件,比如webpack开始构建、生成html文件、完成构建等等。
  2. 插件(每个钩子阶段做的事情),比如在生成html文件后再二次修改处理一些文件,这是插件做的事情。

那钩子和插件如何结合起来呢?

使用发布订阅模式。

这可以让钩子和插件充分的解耦。

一个钩子可以触发多个插件(运行时代码);

一个插件可以在多个钩子阶段处理(日志,在每个钩子打日志);

而webpack底层依赖tapable库,这个库就是提供增强版本的发布订阅模式。

webpack在初始化开始构建之前,会发布所有的生命周期钩子、初始化所有的插件。

并且将钩子和插件关联起来。之后,webpack在主流程中定期的触发钩子就行。

就像这样:

ts 复制代码
const Compiler = require("./Compiler");
const WebpackOptionsApply = require("./WebpackOptionsApply");

const webpack = (options, callback) => {
  // 1. 创建 Compiler 实例
  const compiler = new Compiler(options.context, options);

  // 2. 把所有用户配置的 plugins 应用到 compiler 上
  if (options.plugins && Array.isArray(options.plugins)) {
    for (const plugin of options.plugins) {
      plugin.apply(compiler);  // ← 注意这里!逐个执行插件的 apply
    }
  }

  // 3. 执行 Webpack 自带的一些内部插件
  new WebpackOptionsApply().process(options, compiler);

  if (callback) {
    compiler.run(callback); // 执行构建流程(会触发 hooks)
  }

  return compiler;
};

这就是插件化在webpack中的原理。

下面我们从webpack源码来看看它是如何实现的。

三、源码入手

我们从webpack源码开始,看下插件化是如何实现的。

webpack主流程代码在webpack/lib/webpack.js中。

在这里会做初始化的事情,比如Compiler初始化、所有插件初始化。

  1. 这里首先初始化了Compiler实例,后续的所有生命周期钩子都会在里面初始化。
  2. 然后读取了所有的插件,执行了插件函数,并将Compiler实例作为参数传递,这一步成功将Compiler的this指向到了所有插件,让插件的函数绑定在了钩子上。

而这些Webpack插件我们都知道它的写法类型是固定的。

  1. 是个class。
  2. 必须有apply函数。

看到这里是不是秒懂了?apply函数就是让我们写的plugin和webpack建立关系。绑定在生命周期钩子上。

这里给一个Plugin代码示例:

ts 复制代码
class MyPlugin {
  constructor(options) {
    this.options = options;
  }

  apply(compiler) {
    // 订阅 Compiler 阶段
    compiler.hooks.run.tap('MyPlugin', (compilerInstance) => {
      console.log('[MyPlugin] 开始运行,options:', this.options);
    });

    // 订阅 Compilation 阶段
    compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
      compilation.hooks.processAssets.tap(
        {
          name: 'MyPlugin',
          stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
        },
        (assets) => {
          console.log('[MyPlugin] 当前构建的资源:', Object.keys(assets));
          assets['extra.txt'] = {
            source: () => '这是 MyPlugin 生成的文件',
            size: () => Buffer.byteLength('这是 MyPlugin 生成的文件')
          };
        }
      );
    });

    // 订阅 done 阶段
    compiler.hooks.done.tap('MyPlugin', (stats) => {
      console.log('[MyPlugin] 构建完成,耗时:', stats.endTime - stats.startTime, 'ms');
    });
  }
}

使用这个插件也很简单。

在webpack.config.js中这样配置下就行:

ts 复制代码
// webpack.config.js
const path = require('path');
const MyPlugin = require('./plugins/MyPlugin');

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true
  },
  plugins: [
    new MyPlugin({ customOption: 'abc' })
  ]
};

回到webpack源码。

Compiler 本身并不关心插件注册,它只是提供 lifecycle hooks(事件源)。

然后走到webpack/lib/compiler.js中,在类初始化中基于tapable创建了webpack的所有生命周期。

OK,到这里插件化的两大要点已经结束。插件、生命周期Hook都已经读完了,接下来Webpack会做啥?

执行构建 -> 在构建中不断地触发各种Hook进而触发来自不同插件的逻辑。

这不就是插件化的原理么?

在Webpack中体现。

一句话概括。

在Webpack从源码到构建出产物这件事情注入很多的阶段,并在每个阶段触发指定的插件。

那Tapable是怎么实现的?我们同样去看下源码。

找到tapable/lib/Hook.js,这是所有Hook的子类,我们看下子类怎么监听的就行。

这是个抽象类,本质也是基于发布订阅模式的实现。

通过tap来保存订阅列表。

通过call来触发订阅回调。

当在webpack中订阅的时候

ts 复制代码
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, cb) => { ... });

实际上就是走到了这里:

ts 复制代码
tapAsync() → _tap("async", options, fn)

然后保存到this.taps中。

而当webpack发布事件的时候:

ts 复制代码
compiler.hooks.emit.callAsync(compilation, done);

tapable会遍历taps按注册顺序依次触发回调,最终实现webpack插件的代码逻辑。

这就是Webpack插件化的原理,也是市面上很多应用框架插件化的底层原理。

我们在umi、ice代码库中都可以搜到tapable,而使用姿势和Webpack是差不多的。

四、结尾

插件化是前端应用框架的基石,也是设计模式精妙的应用体验。

看到这里,相信你对Webpack原理更深了一步,同时也更加熟悉许多插件的设计理念了。

如果文章对你有帮助,欢迎在评论区探讨。

相关推荐
葡萄城技术团队2 分钟前
dsh-spreadjs-editor 当前支持什么:安装前需要了解的几件事
前端
沪上企服通15 分钟前
历史账套科目重映射与规则版本重算:旧账梳理的“口径复位”工程
笔记·架构
三*一22 分钟前
优化修整算法
前端·javascript·算法
亥时无人机管理系统1 小时前
无人机管理系统源码 | 私有化部署平台 航线规划飞行审批实时监控功能介绍
架构·私有化部署·无人机·低空经济·无人机巡检·无人机管理系统·一网统飞
weifont2 小时前
自动化抓取和上架商品:能不动手,就少动手
运维·前端·自动化
米柆2 小时前
HTML5 audio /video 完整事件监听模板
前端·html·html5
这个DBA有点耶2 小时前
时序数据库InfluxDB高基数问题深度拆解:内存索引膨胀机制与OOM完整链路
数据库·架构·dba
IT_陈寒2 小时前
Vite热更新失效?你可能也踩了这个坑
前端·人工智能·后端
程序员大阳3 小时前
副队长CSS教程(30)–CSS Grid 实战:实现复杂自适应表单布局
前端·css·自适应·grid·表单
Csvn3 小时前
微前端实现
前端