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

一、前言

最近在学习许多框架的底层插件化实现,如ice.jsumi.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实例作为参数传递,这一步成功将Compilerthis指向到了所有插件,让插件的函数绑定在了钩子上。

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

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

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

这里给一个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插件化的原理,也是市面上很多应用框架插件化的底层原理。

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

四、结尾

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

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

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

相关推荐
Ace_317508877617 小时前
拼多多商品详情接口深度解析:从加密参数破解到数据全量获取
前端·数据库·github
yuejich17 小时前
命名规范snake_case
服务器·前端·数据库
天平17 小时前
开发了几个app后,我在React Native用到的几个库的推荐
android·前端·react native
q***766617 小时前
显卡(Graphics Processing Unit,GPU)架构详细解读
大数据·网络·架构
消失的旧时光-194317 小时前
Kotlinx.serialization 对多态对象(sealed class )支持更好用
java·服务器·前端
少卿18 小时前
React Compiler 完全指南:自动化性能优化的未来
前端·javascript
广州华水科技18 小时前
水库变形监测推荐:2025年单北斗GNSS变形监测系统TOP5,助力基础设施安全
前端
广州华水科技18 小时前
北斗GNSS变形监测一体机在基础设施安全中的应用与优势
前端
七淮18 小时前
umi4暗黑模式设置
前端
8***B18 小时前
前端路由权限控制,动态路由生成
前端