Skip to content

Webpack 深入原理

Webpack 架构、编译流程、插件系统深度解析

六、Webpack 深入原理

6.1 完整编译流程

javascript
/*
┌─────────────────────────────────────────────────────────────────┐
│                    Webpack 编译流程详解                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  1. 初始化阶段                                                    │
│     │                                                             │
│     ├─ 读取配置文件 (webpack.config.js)                           │
│     ├─ 合并 CLI 参数                                              │
│     ├─ 创建 Compiler 对象                                         │
│     └─ 加载所有配置的 Plugin                                      │
│                                                                   │
│  2. 编译阶段 (make)                                               │
│     │                                                             │
│     ├─ 从 entry 开始                                              │
│     ├─ 调用 Loader 转换模块                                       │
│     ├─ 使用 acorn 解析为 AST                                      │
│     ├─ 遍历 AST 找到 require/import                               │
│     ├─ 递归处理依赖模块                                           │
│     └─ 构建模块依赖图 (ModuleGraph)                               │
│                                                                   │
│  3. 生成阶段 (seal)                                               │
│     │                                                             │
│     ├─ 根据依赖图生成 Chunk                                       │
│     ├─ 对 Chunk 进行优化                                          │
│     │   ├─ Tree Shaking                                          │
│     │   ├─ Scope Hoisting                                        │
│     │   └─ Code Splitting                                        │
│     └─ 生成最终代码                                               │
│                                                                   │
│  4. 输出阶段 (emit)                                               │
│     │                                                             │
│     ├─ 生成文件内容                                               │
│     ├─ 写入文件系统                                               │
│     └─ 触发 done 钩子                                             │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘
*/

6.2 Tapable 钩子系统

javascript
// Webpack 的插件系统基于 Tapable
// Tapable 提供各种钩子类型

const { 
  SyncHook,           // 同步串行
  SyncBailHook,       // 同步串行,返回非 undefined 时终止
  SyncWaterfallHook,  // 同步串行,上一个返回值传给下一个
  AsyncParallelHook,  // 异步并行
  AsyncSeriesHook     // 异步串行
} = require('tapable');

// Compiler 核心钩子
class Compiler {
  constructor() {
    this.hooks = {
      // 编译开始
      run: new AsyncSeriesHook(['compiler']),
      // 编译完成
      done: new AsyncSeriesHook(['stats']),
      // 编译失败
      failed: new SyncHook(['error']),
      // 输出前
      emit: new AsyncSeriesHook(['compilation']),
      // 输出后
      afterEmit: new AsyncSeriesHook(['compilation']),
    };
  }
}

// 插件注册方式
class MyPlugin {
  apply(compiler) {
    // 同步钩子用 tap
    compiler.hooks.done.tap('MyPlugin', (stats) => {
      console.log('编译完成');
    });
    
    // 异步钩子用 tapAsync 或 tapPromise
    compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
      setTimeout(() => {
        console.log('异步操作完成');
        callback();
      }, 1000);
    });
  }
}

6.3 Loader 执行机制

javascript
// Loader 链式调用 (从右到左,从下到上)
module: {
  rules: [{
    test: /\.scss$/,
    use: [
      'style-loader',    // 3. 将 CSS 注入 DOM
      'css-loader',      // 2. 处理 CSS 中的 @import 和 url()
      'sass-loader'      // 1. 将 SCSS 编译为 CSS
    ]
  }]
}

// Loader 执行流程
/*
scss 源码

    ▼ sass-loader (normal)

css 代码

    ▼ css-loader (normal)

JS 模块代码 (包含 CSS 依赖)

    ▼ style-loader (normal)

最终 JS 代码 (运行时注入样式)
*/

// Pitch 阶段 (从左到右)
// 可以跳过后续 loader
module.exports = function(source) {
  return transform(source);
};

module.exports.pitch = function(remainingRequest) {
  // 如果 pitch 返回值,跳过后续 loader
  if (shouldSkip) {
    return 'module.exports = "cached content"';
  }
};

6.4 手写简易 Webpack

javascript
const fs = require('fs');
const path = require('path');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const babel = require('@babel/core');

// 1. 解析单个模块
function parseModule(filePath) {
  const content = fs.readFileSync(filePath, 'utf-8');
  
  // 解析为 AST
  const ast = parser.parse(content, {
    sourceType: 'module'
  });
  
  // 收集依赖
  const dependencies = [];
  traverse(ast, {
    ImportDeclaration({ node }) {
      dependencies.push(node.source.value);
    }
  });
  
  // 转换代码
  const { code } = babel.transformFromAstSync(ast, null, {
    presets: ['@babel/preset-env']
  });
  
  return { filePath, dependencies, code };
}

// 2. 构建依赖图
function buildDependencyGraph(entry) {
  const entryModule = parseModule(entry);
  const modules = [entryModule];
  const moduleMap = new Map();
  
  // BFS 遍历所有依赖
  for (const module of modules) {
    moduleMap.set(module.filePath, module);
    
    const dirname = path.dirname(module.filePath);
    module.dependencies.forEach(dep => {
      const depPath = path.resolve(dirname, dep);
      if (!moduleMap.has(depPath)) {
        modules.push(parseModule(depPath));
      }
    });
  }
  
  return modules;
}

// 3. 生成 Bundle
function generateBundle(modules, entry) {
  let modulesCode = '';
  
  modules.forEach(module => {
    modulesCode += `
      "${module.filePath}": function(require, module, exports) {
        ${module.code}
      },
    `;
  });
  
  return `
    (function(modules) {
      const cache = {};
      
      function require(moduleId) {
        if (cache[moduleId]) {
          return cache[moduleId].exports;
        }
        
        const module = cache[moduleId] = { exports: {} };
        modules[moduleId](require, module, module.exports);
        return module.exports;
      }
      
      require("${entry}");
    })({${modulesCode}});
  `;
}

// 使用
const modules = buildDependencyGraph('./src/index.js');
const bundle = generateBundle(modules, './src/index.js');
fs.writeFileSync('./dist/bundle.js', bundle);

前端面试知识库