Skip to content
字数
2179 字
阅读时间
9 分钟

一、Loader(加载器)

作用:Webpack 本质上只能处理 JavaScript 和 JSON 文件,Loader 的作用是将非 JavaScript 模块(如 CSS、图片、TypeScript 等)转换为 Webpack 可识别的模块,以便进行后续处理(如打包、依赖分析)。
工作原理:Loader 是一个函数,接收源文件内容作为参数,经过处理后返回新的内容(或转换后的模块代码)。Loader 支持链式调用,上一个 Loader 的输出会作为下一个 Loader 的输入,执行顺序是从右到左(或从下到上)。

底层实现逻辑
  1. Webpack 在解析模块时,根据配置的 test 规则匹配文件类型,找到对应的 Loader。
  2. 对匹配的文件,按顺序执行 Loader 链:每个 Loader 接收 content(文件内容)、map(source-map 信息)、meta(元数据)三个参数,处理后返回新的 content{ content, map, meta } 对象。
  3. 最终经过所有 Loader 处理后,输出 Webpack 可识别的 JavaScript 代码。
手写一个简单的 Loader(例如:替换文本中的特定字符串)

需求:将文件中所有的 替换为 webpack

  1. 创建 Loader 文件 replace-loader.js

    javascript
    // loader 是一个函数,接收源文件内容作为参数
    module.exports = function (content) {
      // 处理逻辑:替换字符串
      const result = content.replace(/{{name}}/g, 'webpack');
      // 返回处理后的内容(必须是字符串或 Buffer)
      return result;
    };
  2. 在 Webpack 配置中使用:

    javascript
    // webpack.config.js
    module.exports = {
      module: {
        rules: [
          {
            test: /\.txt$/, // 匹配 .txt 文件
            use: './replace-loader.js' // 使用自定义 Loader
          }
        ]
      }
    };
  3. 测试:创建 test.txt,内容为 Hello !,打包后会被转换为 Hello webpack!

二、Plugin(插件)

作用:Plugin 用于扩展 Webpack 的功能,解决 Loader 无法处理的问题(如打包优化、资源管理、环境变量注入等)。Plugin 可以介入 Webpack 打包的整个生命周期(如编译开始、模块解析、输出文件等)。
工作原理:Plugin 是一个带有 apply 方法的类(或对象),通过在 Webpack 生命周期的钩子(Hook)中注册回调函数,实现对打包过程的干预。

底层实现逻辑
  1. Webpack 运行时会创建一个 Compiler 实例,代表整个打包过程,包含所有配置和生命周期钩子。
  2. Plugin 通过 apply 方法接收 Compiler 实例,在合适的钩子(如 emitdone 等)上注册回调。
  3. 当 Webpack 运行到对应阶段时,触发钩子回调,Plugin 即可执行自定义逻辑(如修改输出文件、添加资源等)。
手写一个简单的 Plugin(例如:打包完成后输出提示信息)

需求:在 Webpack 打包完成后,在控制台输出“打包成功!”和耗时。

  1. 创建 Plugin 文件 notify-plugin.js

    javascript
    class NotifyPlugin {
      // apply 方法是 Plugin 的入口,接收 compiler 实例
      apply(compiler) {
        // 注册 'done' 钩子(打包完成后触发)
        compiler.hooks.done.tap('NotifyPlugin', (stats) => {
          const time = stats.endTime - stats.startTime; // 计算耗时
          console.log(`\n打包成功!耗时 ${time}ms`);
        });
      }
    }
    
    module.exports = NotifyPlugin;
  2. 在 Webpack 配置中使用:

    javascript
    // webpack.config.js
    const NotifyPlugin = require('./notify-plugin.js');
    
    module.exports = {
      plugins: [
        new NotifyPlugin() // 实例化 Plugin 并添加到配置
      ]
    };
  3. 测试:运行打包命令,完成后控制台会输出提示信息和耗时。

三、Loader 与 Plugin 的核心区别

维度LoaderPlugin
作用对象处理特定类型的文件(模块)扩展 Webpack 整体功能(介入生命周期)
实现形式函数(接收内容,返回处理后内容)类(带 apply 方法,注册钩子)
执行时机模块解析阶段(加载文件时)整个打包周期(任意钩子阶段)

通过上述示例可以看出,Loader 专注于“文件转换”,Plugin 专注于“流程扩展”,二者配合实现 Webpack 的灵活打包能力。

在 Webpack 中,Compiler 是核心对象之一,它代表了整个 Webpack 打包过程的生命周期管理者,包含了 Webpack 配置、插件钩子、编译状态等关键信息,是插件与 Webpack 交互的核心入口。

Compiler 的核心定位

  • Compiler 由 Webpack 初始化时创建,全局唯一,贯穿整个打包过程(从启动到结束)。
  • 它保存了 Webpack 的完整配置(如 entryoutputmoduleplugins 等),并提供了一系列生命周期钩子(Hooks),插件通过这些钩子介入打包流程。
  • 简单说:Compiler 是 Webpack 的“大脑”,负责统筹整个打包过程,而插件通过 Compiler 才能感知和干预打包的各个阶段。

二、Compiler 的核心属性

Compiler 对象包含大量属性,核心常用的有:

属性名作用
optionsWebpack 完整配置对象(即 webpack.config.js 导出的配置),包含 entryoutputmodule 等。
hooks生命周期钩子集合(最核心属性),插件通过 compiler.hooks.xxx.tap() 注册回调,干预打包过程。
context项目根目录(绝对路径),通常对应 webpack.config.js 所在目录,可通过配置 context 字段修改。
outputPath输出目录的绝对路径(由 output.path 配置决定)。
inputFileSystem / outputFileSystem文件系统接口,用于读取输入文件和写入输出文件(默认使用 Node.js 的 fs 模块,可自定义实现)。
webpack指向 Webpack 模块本身,可用于访问 Webpack 内置工具函数(如 webpack.util)。
stats打包过程的统计信息对象(仅在打包完成后可用),包含模块数量、耗时、错误信息等。

三、Compiler 的核心能力(通过 hooks 实现)

Compiler 的核心能力体现在其 hooks 属性上,这些钩子对应 Webpack 打包的各个阶段,插件通过注册钩子回调实现自定义逻辑。常用钩子分类如下:

1. 初始化阶段

  • initialize:Webpack 初始化完成时触发(最早的钩子之一)。

2. 编译准备阶段

  • entryOption:处理 entry 配置后触发(可用于修改入口)。
  • afterPlugins:所有插件初始化完成后触发。
  • afterResolvers:解析器(resolver)初始化完成后触发(解析器用于查找模块路径)。

3. 编译阶段

  • beforeRun:编译开始前触发(compiler.run() 执行前)。
  • run:编译开始时触发。
  • compile:创建 Compilation 对象前触发(Compilation 代表一次编译过程,包含模块、依赖等信息)。
  • thisCompilation:当前编译的 Compilation 对象创建后触发。
  • compilationCompilation 对象创建且初始化完成后触发(插件常用,可操作模块和依赖)。

4. 输出阶段

  • emit:生成资源(如 dist 目录下的文件)到磁盘前触发(可修改输出内容)。
  • afterEmit:资源输出到磁盘后触发。

5. 完成阶段

  • done:打包完全结束后触发(可获取统计信息,如输出成功提示)。
  • failed:打包失败时触发(可捕获错误信息)。

四、如何使用 Compiler(插件视角)

插件通过 apply 方法接收 Compiler 实例,然后注册钩子回调。例如:

javascript
class MyPlugin {
  apply(compiler) {
    // 1. 访问配置
    console.log('输出目录:', compiler.outputPath);

    // 2. 注册钩子:打包完成后输出统计信息
    compiler.hooks.done.tap('MyPlugin', (stats) => {
      console.log('打包完成,模块数量:', stats.modules.length);
    });

    // 3. 注册钩子:输出资源前修改内容
    compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
      // compilation 是当前编译的详情对象,包含输出的文件
      for (const filename in compilation.assets) {
        if (filename.endsWith('.js')) {
          // 读取原内容
          const content = compilation.assets[filename].source();
          // 添加注释
          const newContent = `/* 自定义注释 */\n${content}`;
          // 更新资源
          compilation.assets[filename] = {
            source: () => newContent,
            size: () => newContent.length
          };
        }
      }
      callback(); // 异步钩子需调用回调
    });
  }
}

总结

Compiler 是 Webpack 的“全局控制器”,它:

  • 保存了完整配置和运行时状态;
  • 提供了覆盖整个打包生命周期的钩子;
  • 是插件与 Webpack 交互的唯一入口。

理解 Compiler 的属性和钩子,是编写复杂 Webpack 插件的基础。

贡献者

The avatar of contributor named as chenjie chenjie

页面历史

撰写