Skip to content

Webpack 源代码导览

项目结构

webpack-course/
├── 04-core-feature/          # 核心功能实现
│   ├── src/
│   │   ├── compiler.js       # 编译器
│   │   ├── compilation.js    # 编译对象
│   │   ├── module.js         # 模块
│   │   ├── parser.js         # 解析器
│   │   ├── loader.js         # 加载器
│   │   └── chunk.js          # 代码块
│   ├── test/
│   │   ├── compiler.test.js
│   │   ├── compilation.test.js
│   │   ├── module.test.js
│   │   └── parser.test.js
│   ├── package.json
│   └── README.md
├── 05-lesson-plan.md         # 课程计划
├── 01-intro.md              # 背景研究
├── 02-arch.md              # 架构分析
└── 03-code-walkthrough.md   # 源代码导览

核心文件解析

1. compiler.js - 编译器

文件路径: src/compiler.js

核心功能:

  • 初始化配置和插件
  • 启动编译流程
  • 管理构建状态

关键代码:

javascript
// 创建编译器
export class Compiler {
  constructor(options = {}) {
    this.options = options
    this.hooks = {
      run: new AsyncSeriesHook(['compiler']),
      compile: new SyncHook(['params']),
      compilation: new SyncHook(['compilation', 'params']),
      make: new AsyncSeriesHook(['compilation']),
      afterCompile: new AsyncSeriesHook(['compilation']),
      done: new AsyncSeriesHook(['stats'])
    }
    
    // 注册插件
    this.plugins = options.plugins || []
    this.plugins.forEach(plugin => {
      if (plugin.apply) {
        plugin.apply(this)
      }
    })
  }
  
  // 运行编译器
  async run(callback) {
    try {
      await this.hooks.run.callAsync(this)
      
      const compilation = this.newCompilation()
      await this.hooks.make.callAsync(compilation)
      await this.hooks.afterCompile.callAsync(compilation)
      
      await this.hooks.done.callAsync(compilation.getStats())
      callback(null, compilation.getStats())
    } catch (error) {
      this.hooks.failed.call(error)
      callback(error)
    }
  }
}

设计要点:

  • 使用 Tapable 实现事件驱动
  • 插件通过 apply 方法注册
  • 钩子支持同步和异步

2. compilation.js - 编译对象

文件路径: src/compilation.js

核心功能:

  • 构建模块图
  • 管理模块依赖
  • 生成最终代码

关键代码:

javascript
// 编译对象
export class Compilation {
  constructor(compiler, params) {
    this.compiler = compiler
    this.options = compiler.options
    this.modules = new Map()
    this.chunks = new Map()
    this.assets = new Map()
  }
  
  // 添加模块
  addModule(module) {
    this.modules.set(module.id, module)
  }
  
  // 添加代码块
  addChunk(chunk) {
    this.chunks.set(chunk.id, chunk)
  }
  
  // 封装编译结果
  seal(callback) {
    // 生成最终代码
    this.generateAssets()
    callback()
  }
  
  // 生成资源
  generateAssets() {
    for (const [id, chunk] of this.chunks) {
      const code = this.generateChunkCode(chunk)
      this.assets.set(id, code)
    }
  }
  
  // 生成代码块代码
  generateChunkCode(chunk) {
    const modules = chunk.modules.map(m => m.source).join('\n')
    return `(function(modules) {
      // webpack bootstrap
    })(${modules})`
  }
}

设计要点:

  • 管理模块和代码块
  • 提供资源生成接口
  • 支持多种输出格式

3. module.js - 模块

文件路径: src/module.js

核心功能:

  • 表示单个模块
  • 管理模块依赖
  • 存储模块源代码

关键代码:

javascript
// 模块节点
export class Module {
  constructor(options = {}) {
    this.id = options.id
    this.source = options.source
    this.dependencies = new Set()
    this.blocks = []
  }
  
  // 添加依赖
  addDependency(module) {
    this.dependencies.add(module)
  }
  
  // 添加代码块
  addBlock(block) {
    this.blocks.push(block)
  }
  
  // 获取依赖列表
  getDependencies() {
    return Array.from(this.dependencies)
  }
}

设计要点:

  • 使用 Set 存储依赖
  • 支持代码块管理
  • 提供依赖查询接口

4. parser.js - 解析器

文件路径: src/parser.js

核心功能:

  • 解析模块源代码
  • 提取依赖关系
  • 生成 AST

关键代码:

javascript
// 解析器
export function parse(source, options = {}) {
  const ast = acorn.parse(source, {
    sourceType: 'module',
    ecmaVersion: 'latest',
    ...options
  })
  
  const dependencies = extractDependencies(ast)
  
  return {
    ast,
    dependencies
  }
}

// 提取依赖
function extractDependencies(ast) {
  const dependencies = new Set()
  
  traverse(ast, {
    ImportDeclaration(path) {
      dependencies.add(path.node.source.value)
    },
    CallExpression(path) {
      if (path.node.callee.name === 'require') {
        dependencies.add(path.node.arguments[0].value)
      }
    }
  })
  
  return Array.from(dependencies)
}

设计要点:

  • 使用 Acorn 解析 JavaScript
  • 支持 CommonJS 和 ES6 模块
  • 使用 AST 遍历提取依赖

5. loader.js - 加载器

文件路径: src/loader.js

核心功能:

  • 转换模块源代码
  • 支持链式调用
  • 处理异步转换

关键代码:

javascript
// 加载器运行器
export function createLoaderRunner() {
  return {
    async runLoaders(module, loaders) {
      let source = module.source
      
      for (const loader of loaders) {
        source = await loader.call(
          { resourcePath: module.id },
          source,
          null
        )
      }
      
      return source
    }
  }
}

// 常用加载器
export const loaders = {
  babelLoader(source) {
    // 转换 ES6+ 代码
    return babel.transform(source, {
      presets: ['@babel/preset-env']
    }).code
  },
  
  cssLoader(source) {
    // 处理 CSS
    return `module.exports = ${JSON.stringify(source)}`
  }
}

设计要点:

  • 支持链式加载器
  • 异步转换支持
  • 提供常用加载器实现

6. chunk.js - 代码块

文件路径: src/chunk.js

核心功能:

  • 管理模块组合
  • 生成最终代码
  • 支持代码分割

关键代码:

javascript
// 代码块
export class Chunk {
  constructor(options = {}) {
    this.id = options.id
    this.name = options.name
    this.modules = new Set()
    this.entry = options.entry || false
  }
  
  // 添加模块
  addModule(module) {
    this.modules.add(module)
  }
  
  // 获取模块列表
  getModules() {
    return Array.from(this.modules)
  }
  
  // 生成代码
  generate() {
    const modules = this.getModules().map(m => {
      return `${JSON.stringify(m.id)}: function(module, exports, require) {
        ${m.source}
      }`
    }).join(',\n')
    
    return `(function(modules) {
      function webpackBootstrap() {
        // webpack runtime
      }
      webpackBootstrap()
    })({${modules}})`
  }
}

设计要点:

  • 模块组合管理
  • 支持 entry chunk
  • 生成 webpack runtime

关键设计决策

1. 使用 Tapable 实现事件驱动

原因:

  • 提供灵活的插件系统
  • 支持同步和异步钩子
  • 便于扩展功能

实现:

javascript
import { SyncHook, AsyncSeriesHook } from 'tapable'

this.hooks = {
  run: new AsyncSeriesHook(['compiler']),
  compile: new SyncHook(['params'])
}

2. 模块使用 Map 存储

原因:

  • 快速查找模块
  • 避免重复模块
  • 便于依赖追踪

实现:

javascript
this.modules = new Map()

// 添加模块
this.modules.set(module.id, module)

// 查找模块
const module = this.modules.get(moduleId)

3. 依赖使用 Set 存储

原因:

  • 避免重复依赖
  • 快速判断依赖关系
  • 节省内存空间

实现:

javascript
this.dependencies = new Set()

// 添加依赖
this.dependencies.add(module)

// 检查依赖
if (this.dependencies.has(module)) {
  // 已存在
}

测试策略

单元测试

每个核心模块都有对应的测试文件:

javascript
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { Compiler } from '../src/compiler.js'

describe('Compiler 测试', () => {
  it('应该创建编译器', () => {
    const compiler = new Compiler()
    assert.ok(compiler)
  })
  
  it('应该运行编译', async () => {
    const compiler = new Compiler()
    const stats = await compiler.run()
    assert.ok(stats)
  })
})

集成测试

测试完整的编译流程:

javascript
describe('集成测试', () => {
  it('应该编译完整项目', async () => {
    const compiler = new Compiler({
      entry: './src/index.js',
      output: {
        path: './dist',
        filename: 'bundle.js'
      }
    })
    
    const stats = await compiler.run()
    assert.strictEqual(stats.errors.length, 0)
  })
})

性能优化

1. 模块缓存

javascript
const moduleCache = new Map()

function loadModule(id) {
  if (moduleCache.has(id)) {
    return moduleCache.get(id)
  }
  
  const module = parseModule(id)
  moduleCache.set(id, module)
  return module
}

2. 增量编译

javascript
function compile(compiler) {
  const changedModules = getChangedModules()
  
  // 只重新编译变化的模块
  for (const module of changedModules) {
    recompileModule(module)
  }
}

总结

Webpack 的源代码体现了以下设计原则:

  1. 插件化架构:通过 Tapable 实现灵活的扩展
  2. 模块化设计:每个组件职责单一
  3. 事件驱动:通过钩子控制流程
  4. 可测试性:提供完整的测试覆盖

理解源代码有助于更好地使用和优化 Webpack。

参考资源

架构师AI杜公众号二维码

扫描二维码关注"架构师AI杜"公众号,获取更多技术内容和最新动态