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提供了完整的测试覆盖,确保代码质量。理解源代码有助于更好地使用和优化 Webpack。
参考资源
Webpack 源码,Tapable 源码,Webpack 贡献指南。

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