2025年11月28日10 分钟

Webpack 5 源码核心模块解析:从入口到产物

拆解 Webpack 5 源码中的核心模块,理解 Compiler、Compilation、Module、Chunk、Plugin 等关键概念的职责和关系

Webpack 5 的模块全景

                    Webpack 核心模块关系图

  ┌─────────────────────────────────────────────────────┐
  │                     Compiler                         │
  │  (全局唯一,管理整个构建生命周期)                        │
  │                                                      │
  │  ┌────────────────────────────────────────────────┐  │
  │  │                Compilation                      │  │
  │  │  (每次构建的上下文,HMR 时会创建新的)              │  │
  │  │                                                 │  │
  │  │  ┌──────────┐  ┌──────────┐  ┌──────────────┐  │  │
  │  │  │ Module    │  │ Module   │  │ Module       │  │  │
  │  │  │ (entry)  │──│ (dep A) │──│ (dep B)      │  │  │
  │  │  └──────────┘  └──────────┘  └──────────────┘  │  │
  │  │       │                                         │  │
  │  │       ▼ seal(封装阶段)                          │  │
  │  │  ┌──────────┐  ┌──────────┐                     │  │
  │  │  │ Chunk     │  │ Chunk    │                     │  │
  │  │  │ (main)   │  │ (vendor) │                     │  │
  │  │  └──────────┘  └──────────┘                     │  │
  │  │       │                                         │  │
  │  │       ▼ emit(输出阶段)                          │  │
  │  │  ┌──────────┐  ┌──────────┐                     │  │
  │  │  │ Asset     │  │ Asset    │                     │  │
  │  │  │ main.js  │  │ vendor.js│                     │  │
  │  │  └──────────┘  └──────────┘                     │  │
  │  └────────────────────────────────────────────────┘  │
  │                                                      │
  │  ┌──────────────────────────┐                        │
  │  │ Plugin System (Tapable)   │                        │
  │  │ 贯穿整个流程的钩子系统       │                        │
  │  └──────────────────────────┘                        │
  └─────────────────────────────────────────────────────┘

1. Compiler — 构建的总指挥

职责:全局唯一的编译器实例,管理整个构建的生命周期。

// Webpack 内部创建 Compiler 的流程
const webpack = require('webpack')
const config = require('./webpack.config.js')

// webpack(config) 内部:
function webpack(config) {
  // 1. 创建 Compiler 实例
  const compiler = new Compiler(config.context)

  // 2. 注册所有内置插件
  new WebpackOptionsApply().process(config, compiler)

  // 3. 注册用户配置的插件
  for (const plugin of config.plugins) {
    plugin.apply(compiler)  // 每个插件调用 apply 注册钩子
  }

  return compiler
}

Compiler 的关键钩子

class Compiler extends Tapable {
  hooks = {
    // 构建开始前
    beforeRun:    new AsyncSeriesHook(['compiler']),
    run:          new AsyncSeriesHook(['compiler']),

    // 编译阶段
    beforeCompile: new AsyncSeriesHook(['params']),
    compile:       new SyncHook(['params']),
    thisCompilation: new SyncHook(['compilation', 'params']),
    compilation:   new SyncHook(['compilation', 'params']),
    make:          new AsyncParallelHook(['compilation']),  // 核心:触发模块构建

    // 完成阶段
    afterCompile:  new AsyncSeriesHook(['compilation']),
    emit:          new AsyncSeriesHook(['compilation']),    // 输出文件前
    afterEmit:     new AsyncSeriesHook(['compilation']),    // 输出文件后
    done:          new AsyncSeriesHook(['stats']),          // 构建完成
  }
}

2. Compilation — 单次构建的上下文

职责:管理一次完整的模块构建过程。开发模式下每次 HMR 热更新都会创建新的 Compilation。

class Compilation extends Tapable {
  constructor(compiler) {
    this.compiler = compiler
    this.modules = new Set()        // 所有模块
    this.chunks = new Set()         // 所有代码块
    this.assets = {}                // 最终输出的文件
    this.entries = new Map()        // 入口点
    this.dependencyFactories = new Map()  // 依赖工厂
    this.moduleGraph = new ModuleGraph()  // 模块依赖图
    this.chunkGraph = new ChunkGraph()    // Chunk 关系图
  }

  // 核心方法
  addModule(module) { }    // 添加模块到构建
  buildModule(module) { }  // 执行 Loader 转换
  processModuleDependencies(module) { }  // 解析模块依赖
  seal() { }               // 封装:Module → Chunk
  emitAsset(file, source) { }  // 输出产物
}

Compilation 的关键钩子

hooks = {
  buildModule:       new SyncHook(['module']),       // 开始构建模块
  succeedModule:     new SyncHook(['module']),       // 模块构建成功
  seal:              new SyncHook([]),                // 开始封装
  optimizeModules:   new SyncBailHook(['modules']),  // 优化模块
  optimizeChunks:    new SyncBailHook(['chunks']),   // 优化 Chunk
  optimizeTree:      new AsyncSeriesHook(['chunks', 'modules']), // 优化依赖树
  processAssets:     new AsyncSeriesHook(['assets']), // 处理产物(Webpack 5 新增)
}

3. Module — 模块的抽象

职责:每个文件对应一个 Module 实例,记录文件的源码、依赖和转换结果。

// Webpack 的 Module 家族
Module (基类)
  ├── NormalModule      // 普通模块(.js, .ts, .css 等文件)
  ├── RawModule         // 无需处理的原始模块
  ├── ContextModule     // require.context() 动态导入
  ├── ExternalModule    // externals 外部模块
  └── ConcatenatedModule // Scope Hoisting 合并后的模块

NormalModule 的构建流程

class NormalModule extends Module {
  constructor({ resource, loaders, parser, generator }) {
    this.resource = resource    // 文件路径:/src/App.tsx
    this.loaders = loaders      // 需要执行的 Loader 列表
    this._source = null         // Loader 转换后的源码
    this.dependencies = []      // 该模块的依赖列表
  }

  // 构建流程
  async build(options, compilation) {
    // 1. 执行 Loader 链(从右到左)
    //    App.tsx → swc-loader → JavaScript 源码
    const source = await this.runLoaders()
    this._source = source

    // 2. 使用 Parser 解析源码,提取依赖
    //    分析 import/require 语句 → 生成依赖列表
    const ast = parser.parse(source)
    this.dependencies = parser.extractDependencies(ast)
    // dependencies: ['react', './components/Header', './styles.css']

    // 3. 递归处理每个依赖 → 再次 build → 形成依赖图
  }
}

4. ModuleFactory — 模块创建工厂

职责:根据依赖类型创建对应的 Module 实例。

// NormalModuleFactory:创建普通模块
class NormalModuleFactory extends Tapable {
  create(data, callback) {
    const { dependency } = data

    // 1. resolve:解析文件路径
    //    'react' → /node_modules/react/index.js
    //    './Header' → /src/components/Header.tsx
    this.hooks.resolve.callAsync(data, (err, result) => {
      const { resource, loaders } = result

      // 2. 创建 NormalModule 实例
      const module = new NormalModule({
        resource,       // /src/components/Header.tsx
        loaders,        // [swc-loader, css-loader, ...]
        parser: this.getParser(result.type),
        generator: this.getGenerator(result.type),
      })

      callback(null, module)
    })
  }
}

5. Chunk & ChunkGroup — 代码分块

职责:把模块组织成可输出的代码块。

// Module → Chunk 的映射关系
class Chunk {
  constructor(name) {
    this.name = name          // 'main', 'vendors', 'async-page'
    this.files = new Set()    // 最终输出的文件名
    this.idNameHints = new Set()
  }
}

// 代码分割的三种方式:
// 1. Entry:每个入口生成一个 Chunk
entry: {
  main: './src/index.js',     // → main chunk
  admin: './src/admin.js',    // → admin chunk
}

// 2. 动态导入:每个 import() 生成一个 Chunk
const Page = lazy(() => import('./Page'))  // → async chunk

// 3. SplitChunks:自动提取共享模块
optimization: {
  splitChunks: { chunks: 'all' }  // → vendors chunk, commons chunk
}

6. Tapable — 插件系统的基石

职责:提供发布-订阅模式的钩子系统,是所有 Plugin 的通信桥梁。

const { SyncHook, AsyncSeriesHook, SyncBailHook } = require('tapable')

// 钩子类型
SyncHook         // 同步串行,不关心返回值
SyncBailHook     // 同步串行,返回非 undefined 则中断
SyncWaterfallHook // 同步串行,上一个的返回值传给下一个
AsyncSeriesHook  // 异步串行
AsyncParallelHook // 异步并行

// 用法
const hook = new SyncHook(['arg1', 'arg2'])
hook.tap('PluginA', (arg1, arg2) => { /* ... */ })  // 注册
hook.call('hello', 'world')                          // 触发

插件如何接入

class MyPlugin {
  apply(compiler) {
    // 在 emit 钩子上注册
    compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
      // compilation.assets 包含所有即将输出的文件
      const assets = compilation.assets

      // 添加一个新文件
      compilation.emitAsset('build-info.json', new RawSource(
        JSON.stringify({ buildTime: new Date().toISOString() })
      ))

      callback()
    })
  }
}

7. Resolver — 路径解析器

职责:把模块标识符解析为绝对路径。

// Resolver 的解析策略
import React from 'react'
// → 查找 node_modules/react/package.json
// → 读取 main/module/browser 字段
// → 解析为 /node_modules/react/index.js

import Header from './Header'
// → 依次尝试 ./Header.tsx → ./Header.ts → ./Header.js
// → 解析为 /src/components/Header.tsx

import '@/utils/format'
// → alias 映射 @ → /src
// → 解析为 /src/utils/format.ts

8. Parser & Generator — 解析与生成

// Parser:解析源码,提取依赖
class JavascriptParser {
  parse(source) {
    const ast = acorn.parse(source)  // 生成 AST

    // 遍历 AST,识别依赖声明
    walk(ast, {
      ImportDeclaration(node) {
        // import x from 'y' → 添加依赖 'y'
        this.addDependency(new HarmonyImportDependency(node.source.value))
      },
      CallExpression(node) {
        if (node.callee.name === 'require') {
          // require('z') → 添加依赖 'z'
          this.addDependency(new CommonJsDependency(node.arguments[0].value))
        }
        if (node.callee.type === 'Import') {
          // import('w') → 添加异步依赖 'w'
          this.addDependency(new ImportDependency(node.arguments[0].value))
        }
      },
    })
  }
}

// Generator:根据 Module 生成最终代码
class JavascriptGenerator {
  generate(module, context) {
    // 将模块代码包装成 Webpack 运行时格式
    // import → __webpack_require__
    // export → __webpack_exports__
    return new RawSource(transformedCode)
  }
}

模块关系总结

Compiler(总指挥,全局唯一)
  │
  ├── 拥有 Compilation(每次构建一个)
  │     │
  │     ├── 使用 NormalModuleFactory 创建 Module
  │     │     │
  │     │     ├── Resolver:解析路径
  │     │     ├── Loader Runner:执行 Loader 转换
  │     │     └── Parser:提取依赖
  │     │
  │     ├── 构建 ModuleGraph(模块依赖图)
  │     │
  │     ├── seal():Module → Chunk → ChunkGroup
  │     │
  │     └── emitAssets():Chunk → Asset → 写入磁盘
  │
  └── Tapable 钩子系统贯穿全程
        │
        └── Plugin 通过钩子参与各个阶段
模块职责一句话
Compiler管理构建生命周期"总指挥"
Compilation管理单次构建"施工队"
Module表示一个文件"建筑材料"
Chunk模块的分组"一车材料"
Tapable钩子系统"通信系统"
Resolver路径解析"导航员"
Parser源码分析"质检员"
NormalModuleFactory创建模块"材料工厂"