Appearance
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 的源代码体现了以下设计原则:
- 插件化架构:通过 Tapable 实现灵活的扩展
- 模块化设计:每个组件职责单一
- 事件驱动:通过钩子控制流程
- 可测试性:提供完整的测试覆盖
理解源代码有助于更好地使用和优化 Webpack。
参考资源

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