Skip to content

Webpack 课程计划

课程概述

本课程将带你从零开始实现一个简化版的 Webpack,理解 Webpack 的核心工作原理。

课程目标

  • 理解模块打包器的工作原理
  • 掌握依赖管理和模块解析
  • 学习加载器系统的设计和实现
  • 理解代码块和代码分割
  • 掌握插件系统的设计模式

课程结构

10 节课,每节 20-40 分钟,总时长约 90-120 分钟。


第 1 课:Webpack 基础概念

目标

  • 了解 Webpack 的历史和设计目标
  • 理解模块打包器的概念
  • 掌握 Webpack 的核心术语

内容

  1. Webpack 简介

    • 什么是 Webpack
    • 为什么需要 Webpack
    • Webpack 的设计哲学
  2. 核心概念

    • Entry(入口)
    • Output(输出)
    • Loader(加载器)
    • Plugin(插件)
    • Mode(模式)
  3. Webpack 的工作流程

    • 初始化
    • 编译
    • 输出
    • 监听

实践步骤

bash
# 1. 创建项目目录
mkdir webpack-course
cd webpack-course

# 2. 初始化项目
npm init -y

# 3. 安装依赖
npm install webpack webpack-cli --save-dev

预期输出

✓ 项目初始化完成
✓ 依赖安装完成

总结

  • ✅ 理解了 Webpack 的基本概念
  • ✅ 掌握了核心术语
  • ✅ 了解了工作流程

第 2 课:编译器架构

目标

  • 理解编译器的设计
  • 实现基本的编译器
  • 学习钩子系统

内容

  1. 编译器架构

    • 编译器的职责
    • 生命周期钩子
    • Tapable 的使用
  2. 实现编译器

    • 创建 Compiler 类
    • 实现钩子系统
    • 实现编译流程
  3. 测试编译器

    • 单元测试
    • 集成测试

实践步骤

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 课:模块系统

目标

  • 理解模块的概念
  • 实现模块类
  • 实现依赖解析

内容

  1. 模块概念

    • 什么是模块
    • 模块的类型
    • 模块的属性
  2. 实现模块

    • 创建 Module 类
    • 实现依赖解析
    • 实现模块构建
  3. 测试模块

    • 模块创建测试
    • 依赖解析测试

实践步骤

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 解析

内容

  1. 依赖解析概念

    • 什么是依赖解析
    • 解析算法
    • 解析策略
  2. 实现解析器

    • 使用 Acorn 解析 AST
    • 提取 import/require
    • 解析路径
  3. 测试解析器

    • 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 课:加载器系统

目标

  • 理解加载器的作用
  • 实现加载器运行器
  • 实现常用加载器

内容

  1. 加载器概念

    • 什么是加载器
    • 加载器的作用
    • 加载器的执行顺序
  2. 实现加载器

    • 创建加载器运行器
    • 实现常用加载器
    • 实现加载器上下文
  3. 测试加载器

    • 加载器执行测试
    • 链式调用测试

实践步骤

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 课:代码块

目标

  • 理解代码块的概念
  • 实现代码块类
  • 实现代码分割

内容

  1. 代码块概念

    • 什么是代码块
    • 代码块的类型
    • 代码块的优化
  2. 实现代码块

    • 创建 Chunk 类
    • 实现代码分割
    • 实现代码块优化
  3. 测试代码块

    • 代码块创建测试
    • 代码分割测试

实践步骤

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 课:编译对象

目标

  • 理解编译对象的作用
  • 实现编译对象
  • 实现资产生成

内容

  1. 编译对象概念

    • 什么是编译对象
    • 编译对象的职责
    • 编译对象的生命周期
  2. 实现编译对象

    • 创建 Compilation 类
    • 实现模块管理
    • 实现资产生成
  3. 测试编译对象

    • 编译对象创建测试
    • 资产生成测试

实践步骤

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 课:插件系统

目标

  • 理解插件的设计模式
  • 实现插件接口
  • 创建示例插件

内容

  1. 插件概念

    • 什么是插件
    • 插件的作用
    • 插件的钩子
  2. 实现插件

    • 创建插件基类
    • 实现插件接口
    • 实现钩子注册
  3. 创建示例插件

    • 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 课:完整集成

目标

  • 集成所有功能
  • 创建完整的打包流程
  • 测试完整流程

内容

  1. 功能集成

    • 整合编译器
    • 整合模块系统
    • 整合加载器
    • 整合插件
  2. 完整流程

    • 创建配置文件
    • 运行打包
    • 生成输出
  3. 测试完整流程

    • 端到端测试
    • 性能测试

实践步骤

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 课:总结与扩展

目标

  • 总结所有课程
  • 回顾关键概念
  • 提供扩展建议

内容

  1. 课程总结

    • 回顾所有功能
    • 总结关键概念
    • 展示完整代码
  2. 扩展建议

    • 添加更多加载器
    • 优化性能
    • 支持更多特性
  3. 下一步

    • 学习 Webpack 源码
    • 参与开源项目
    • 构建自己的工具

总结

通过本课程,你学会了:

  1. ✅ 编译器架构
  2. ✅ 模块系统
  3. ✅ 依赖解析
  4. ✅ 加载器系统
  5. ✅ 代码块
  6. ✅ 编译对象
  7. ✅ 插件系统
  8. ✅ 完整集成

下一步

  • 学习 Webpack 源码
  • 添加更多插件
  • 参与开源项目
  • 构建自己的工具

参考资源

架构师AI杜公众号二维码

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