emit 阶段写入文件系统以及持久化缓存机制

一、emit 阶段源码解析

1.1 核心流程时序图

scss 复制代码
Compiler.emitAssets
  |- Compilation.createAssets
  |- Compiler.outputFileSystem.writeFile
  |- Compilation.emitAsset (触发各种 Hook)

1.2 关键源码分析

1.2.1 Compiler.js 核心方法

javascript 复制代码
// lib/Compiler.js
emitAssets(compilation, callback) {
  const emitFiles = (err) => {
    // 创建输出目录
    const outputPath = compilation.getPath(this.outputPath);
    this.outputFileSystem.mkdirp(outputPath, emitFiles);
    
    // 写入文件
    asyncLib.forEachLimit(
      compilation.getAssets(),
      15,
      ({ name, source }, callback) => {
        const targetPath = this.outputPath;
        const writeOut = (err) => {
          if (err) return callback(err);
          const content = source.source();
          this.outputFileSystem.writeFile(
            path.join(targetPath, name),
            content,
            callback
          );
        };
        this.hooks.assetEmitted.callAsync(file, content, callback);
      },
      err => {/* 处理完成 */}
    );
  };
}

1.2.2 文件写入扩展点

通过 processAssets 钩子干预输出:

javascript 复制代码
// webpack.config.js
compilation.hooks.processAssets.tap('ExamplePlugin', (assets) => {
  assets['new-file.txt'] = {
    source: () => 'Custom Content',
    size: () => 12
  };
});

二、持久化缓存机制

2.1 架构设计

复制代码
FileSystemInfo
  |- 管理文件快照
  |- 跟踪文件变化

Snapshot
  |- 文件哈希
  |- 时间戳
  |- 依赖关系

2.2 核心源码解析

2.2.1 FileSystemInfo 类

javascript 复制代码
// lib/FileSystemInfo.js
class FileSystemInfo {
  // 创建文件快照
  createSnapshot(stat, filePath, timestamp) {
    return {
      timestamp,
      hash: createHash(stat.size + stat.mtimeMs),
      dependencies: new Set()
    };
  }

  // 检查快照有效性
  checkSnapshotValid(snapshot) {
    const currentStat = fs.statSync(filePath);
    return snapshot.hash === createHash(currentStat.size + currentStat.mtimeMs);
  }
}

2.2.2 缓存存储结构

bash 复制代码
node_modules/.cache/webpack
  |- default
    |- data.json      # 模块依赖信息
    |- timestamps.txt # 文件时间戳快照
    |- hashes.txt     # 文件内容哈希

2.3 缓存判定流程图

复制代码
开始构建
  |- 加载上次缓存
  |- 检查 Snapshot 有效性
  |   |- 文件哈希对比
  |   |- 时间戳对比
  |- 有效?使用缓存 : 重新构建

三、完整示例演示

3.1 配置持久化缓存

javascript 复制代码
// webpack.config.js
module.exports = {
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename] // 配置文件变更时使缓存失效
    }
  },
  output: {
    filename: '[contenthash].bundle.js'
  }
};

3.2 自定义缓存策略

javascript 复制代码
// lib/CachePlugin.js
class CachePlugin {
  apply(compiler) {
    compiler.hooks.thisCompilation.tap('CachePlugin', (compilation) => {
      compilation.hooks.processAssets.tap('CachePlugin', (assets) => {
        Object.keys(assets).forEach(name => {
          if (name.endsWith('.js')) {
            assets[name] = new ReplaceSource(
              assets[name],
              '/* Cached Version */\n'
            );
          }
        });
      });
    });
  }
}

3.3 缓存验证方法

bash 复制代码
# 查看缓存文件
find node_modules/.cache/webpack -type f -exec ls -l {} \;

# 强制清除缓存
rm -rf node_modules/.cache/webpack

四、核心优化策略

  1. 哈希策略优化
javascript 复制代码
output: {
  filename: '[contenthash:8].js'
}
  1. 缓存分段策略
javascript 复制代码
cache: {
  version: `${process.env.NODE_ENV}-v1`
}
  1. 构建信息追踪
javascript 复制代码
compiler.hooks.compilation.tap('CacheDebug', (compilation) => {
  compilation.hooks.finishModules.tap('CacheDebug', modules => {
    modules.forEach(module => {
      console.log(`Module ${module.identifier()} cacheable: ${module.buildInfo.cacheable}`);
    });
  });
});

五、调试技巧

  1. 缓存调试输出
javascript 复制代码
// 在配置中增加
stats: {
  cachedModules: true,
  cachedAssets: true
}
  1. 源码断点位置
  • FileSystemInfo.js: checkSnapshotValid
  • Compiler.js: emitAssets
  • CachePlugin.js: restoreCacheData

总结

Webpack 的持久化缓存系统通过以下机制实现高效构建:

  1. 文件级快照:结合文件元数据和内容哈希
  2. 依赖追踪:模块级别的依赖关系图谱
  3. 增量写入:仅修改变化的输出文件
  4. 多级校验:时间戳 + 哈希双重验证

通过合理配置缓存策略,可使二次构建速度提升 60%-80%。建议根据项目特点调整缓存版本策略和哈希长度,平衡构建速度与缓存有效性。

相关推荐
1024小神8 分钟前
在GitHub action中使用添加项目中配置文件的值为环境变量
前端·javascript
齐尹秦17 分钟前
CSS 列表样式学习笔记
前端
Mnxj21 分钟前
渐变边框设计
前端
糯糯机器21 分钟前
Webpack 打包未使用组件的原因
webpack
用户76787977373224 分钟前
由Umi升级到Next方案
前端·next.js
快乐的小前端25 分钟前
TypeScript基础一
前端
北凉温华26 分钟前
UniApp项目中的多服务环境配置与跨域代理实现
前端
源柒27 分钟前
Vue3与Vite构建高性能记账应用 - LedgerX架构解析
前端
Danny_FD27 分钟前
常用 Git 命令详解
前端·github
stanny28 分钟前
MCP(上)——function call 是什么
前端·mcp