Appearance
Webpack 课程计划
课程概述
本课程将带你从零开始实现一个简化版的 Webpack,理解 Webpack 的核心工作原理。
课程目标
- 理解模块打包器的工作原理
- 掌握依赖管理和模块解析
- 学习加载器系统的设计和实现
- 理解代码块和代码分割
- 掌握插件系统的设计模式
课程结构
10 节课,每节 20-40 分钟,总时长约 90-120 分钟。
第 1 课:Webpack 基础概念
目标
- 了解 Webpack 的历史和设计目标
- 理解模块打包器的概念
- 掌握 Webpack 的核心术语
内容
Webpack 简介
- 什么是 Webpack
- 为什么需要 Webpack
- Webpack 的设计哲学
核心概念
- Entry(入口)
- Output(输出)
- Loader(加载器)
- Plugin(插件)
- Mode(模式)
Webpack 的工作流程
- 初始化
- 编译
- 输出
- 监听
实践步骤
bash
# 1. 创建项目目录
mkdir webpack-course
cd webpack-course
# 2. 初始化项目
npm init -y
# 3. 安装依赖
npm install webpack webpack-cli --save-dev预期输出
✓ 项目初始化完成
✓ 依赖安装完成总结
- ✅ 理解了 Webpack 的基本概念
- ✅ 掌握了核心术语
- ✅ 了解了工作流程
第 2 课:编译器架构
目标
- 理解编译器的设计
- 实现基本的编译器
- 学习钩子系统
内容
编译器架构
- 编译器的职责
- 生命周期钩子
- Tapable 的使用
实现编译器
- 创建 Compiler 类
- 实现钩子系统
- 实现编译流程
测试编译器
- 单元测试
- 集成测试
实践步骤
bash
# 1. 创建编译器文件
cat > src/compiler.js << 'EOF'
import { SyncHook, AsyncSeriesHook } from 'tapable'
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']),
done: new AsyncSeriesHook(['stats'])
}
}
async run(callback) {
await this.hooks.run.callAsync(this)
// 编译逻辑
await this.hooks.done.callAsync({})
callback(null, {})
}
}
EOF
# 2. 创建测试文件
cat > test/compiler.test.js << 'EOF'
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { Compiler } from '../src/compiler.js'
describe('编译器测试', () => {
it('应该创建编译器', () => {
const compiler = new Compiler({})
assert.ok(compiler)
assert.ok(compiler.hooks)
})
})
EOF
# 3. 运行测试
npm test预期输出
✓ 应该创建编译器总结
- ✅ 实现了编译器
- ✅ 理解了钩子系统
- ✅ 掌握了 Tapable 的使用
第 3 课:模块系统
目标
- 理解模块的概念
- 实现模块类
- 实现依赖解析
内容
模块概念
- 什么是模块
- 模块的类型
- 模块的属性
实现模块
- 创建 Module 类
- 实现依赖解析
- 实现模块构建
测试模块
- 模块创建测试
- 依赖解析测试
实践步骤
bash
# 1. 创建模块文件
cat > src/module.js << 'EOF'
export class Module {
constructor(options) {
this.type = options.type
this.request = options.request
this.dependencies = []
this.source = ''
}
async build() {
// 读取源代码
// 解析依赖
// 应用加载器
}
addDependency(dependency) {
this.dependencies.push(dependency)
}
}
EOF
# 2. 创建测试文件
cat > test/module.test.js << 'EOF'
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { Module } from '../src/module.js'
describe('模块测试', () => {
it('应该创建模块', () => {
const module = new Module({
type: 'javascript/auto',
request: './test.js'
})
assert.ok(module)
})
})
EOF
# 3. 运行测试
npm test预期输出
✓ 应该创建模块总结
- ✅ 实现了模块系统
- ✅ 理解了模块的概念
- ✅ 掌握了依赖解析
第 4 课:依赖解析
目标
- 理解依赖解析的原理
- 实现解析器
- 实现 AST 解析
内容
依赖解析概念
- 什么是依赖解析
- 解析算法
- 解析策略
实现解析器
- 使用 Acorn 解析 AST
- 提取 import/require
- 解析路径
测试解析器
- AST 解析测试
- 依赖提取测试
实践步骤
bash
# 1. 创建解析器文件
cat > src/parser.js << 'EOF'
import { parse } from 'acorn'
export function createParser() {
return {
parse(source, filename) {
const ast = parse(source, {
sourceType: 'module',
ecmaVersion: 'latest'
})
const dependencies = []
// 遍历 AST 提取依赖
return dependencies
}
}
}
EOF
# 2. 创建测试文件
cat > test/parser.test.js << 'EOF'
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { createParser } from '../src/parser.js'
describe('解析器测试', () => {
it('应该解析 import 语句', () => {
const parser = createParser()
const dependencies = parser.parse('import { foo } from "./bar.js"', 'test.js')
assert.ok(Array.isArray(dependencies))
})
})
EOF
# 3. 运行测试
npm test预期输出
✓ 应该解析 import 语句总结
- ✅ 实现了依赖解析
- ✅ 理解了 AST 解析
- ✅ 掌握了 Acorn 的使用
第 5 课:加载器系统
目标
- 理解加载器的作用
- 实现加载器运行器
- 实现常用加载器
内容
加载器概念
- 什么是加载器
- 加载器的作用
- 加载器的执行顺序
实现加载器
- 创建加载器运行器
- 实现常用加载器
- 实现加载器上下文
测试加载器
- 加载器执行测试
- 链式调用测试
实践步骤
bash
# 1. 创建加载器文件
cat > src/loader.js << 'EOF'
export function createLoaderRunner() {
return {
async run(source, filename) {
let result = source
for (const loader of this.loaders) {
result = await loader(result, filename)
}
return result
}
}
}
EOF
# 2. 创建测试文件
cat > test/loader.test.js << 'EOF'
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { createLoaderRunner } from '../src/loader.js'
describe('加载器测试', () => {
it('应该运行加载器', async () => {
const runner = createLoaderRunner()
const result = await runner.run('test', 'test.js')
assert.strictEqual(result, 'test')
})
})
EOF
# 3. 运行测试
npm test预期输出
✓ 应该运行加载器总结
- ✅ 实现了加载器系统
- ✅ 理解了加载器的作用
- ✅ 掌握了加载器的执行
第 6 课:代码块
目标
- 理解代码块的概念
- 实现代码块类
- 实现代码分割
内容
代码块概念
- 什么是代码块
- 代码块的类型
- 代码块的优化
实现代码块
- 创建 Chunk 类
- 实现代码分割
- 实现代码块优化
测试代码块
- 代码块创建测试
- 代码分割测试
实践步骤
bash
# 1. 创建代码块文件
cat > src/chunk.js << 'EOF'
export class Chunk {
constructor(options) {
this.id = options.id
this.name = options.name
this.modules = new Set()
}
addModule(module) {
this.modules.add(module)
}
getSize() {
return Array.from(this.modules)
.reduce((total, m) => total + m.size, 0)
}
}
EOF
# 2. 创建测试文件
cat > test/chunk.test.js << 'EOF'
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { Chunk } from '../src/chunk.js'
describe('代码块测试', () => {
it('应该创建代码块', () => {
const chunk = new Chunk({ id: 'main', name: 'main' })
assert.ok(chunk)
})
})
EOF
# 3. 运行测试
npm test预期输出
✓ 应该创建代码块总结
- ✅ 实现了代码块
- ✅ 理解了代码分割
- ✅ 掌握了代码块优化
第 7 课:编译对象
目标
- 理解编译对象的作用
- 实现编译对象
- 实现资产生成
内容
编译对象概念
- 什么是编译对象
- 编译对象的职责
- 编译对象的生命周期
实现编译对象
- 创建 Compilation 类
- 实现模块管理
- 实现资产生成
测试编译对象
- 编译对象创建测试
- 资产生成测试
实践步骤
bash
# 1. 创建编译对象文件
cat > src/compilation.js << 'EOF'
export class Compilation {
constructor(compiler) {
this.compiler = compiler
this.modules = new Map()
this.chunks = new Map()
this.assets = new Map()
}
addModule(module) {
this.modules.set(module.identifier(), module)
}
addAsset(name, source) {
this.assets.set(name, source)
}
}
EOF
# 2. 创建测试文件
cat > test/compilation.test.js << 'EOF'
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { Compilation } from '../src/compilation.js'
describe('编译对象测试', () => {
it('应该创建编译对象', () => {
const compilation = new Compilation({})
assert.ok(compilation)
})
})
EOF
# 3. 运行测试
npm test预期输出
✓ 应该创建编译对象总结
- ✅ 实现了编译对象
- ✅ 理解了编译对象的作用
- ✅ 掌握了资产生成
第 8 课:插件系统
目标
- 理解插件的设计模式
- 实现插件接口
- 创建示例插件
内容
插件概念
- 什么是插件
- 插件的作用
- 插件的钩子
实现插件
- 创建插件基类
- 实现插件接口
- 实现钩子注册
创建示例插件
- HTML 插件
- 清理插件
- 定义插件
实践步骤
bash
# 1. 创建插件文件
cat > src/plugin.js << 'EOF'
export class Plugin {
constructor(options) {
this.options = options
}
apply(compiler) {
compiler.hooks.done.tapAsync('MyPlugin', (stats, callback) => {
console.log('编译完成!')
callback()
})
}
}
EOF
# 2. 创建测试文件
cat > test/plugin.test.js << 'EOF'
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { Plugin } from '../src/plugin.js'
describe('插件测试', () => {
it('应该创建插件', () => {
const plugin = new Plugin({})
assert.ok(plugin)
})
})
EOF
# 3. 运行测试
npm test预期输出
✓ 应该创建插件总结
- ✅ 实现了插件系统
- ✅ 理解了插件的设计
- ✅ 掌握了钩子注册
第 9 课:完整集成
目标
- 集成所有功能
- 创建完整的打包流程
- 测试完整流程
内容
功能集成
- 整合编译器
- 整合模块系统
- 整合加载器
- 整合插件
完整流程
- 创建配置文件
- 运行打包
- 生成输出
测试完整流程
- 端到端测试
- 性能测试
实践步骤
bash
# 1. 创建配置文件
cat > webpack.config.js << 'EOF'
export default {
entry: './src/index.js',
output: {
path: './dist',
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader'
}
]
}
}
EOF
# 2. 运行打包
npx webpack --config webpack.config.js
# 3. 检查输出
ls -la dist/预期输出
bundle.js总结
- ✅ 集成了所有功能
- ✅ 实现了完整流程
- ✅ 测试了端到端流程
第 10 课:总结与扩展
目标
- 总结所有课程
- 回顾关键概念
- 提供扩展建议
内容
课程总结
- 回顾所有功能
- 总结关键概念
- 展示完整代码
扩展建议
- 添加更多加载器
- 优化性能
- 支持更多特性
下一步
- 学习 Webpack 源码
- 参与开源项目
- 构建自己的工具
总结
通过本课程,你学会了:
- ✅ 编译器架构
- ✅ 模块系统
- ✅ 依赖解析
- ✅ 加载器系统
- ✅ 代码块
- ✅ 编译对象
- ✅ 插件系统
- ✅ 完整集成
下一步
- 学习 Webpack 源码
- 添加更多插件
- 参与开源项目
- 构建自己的工具
参考资源

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