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%。建议根据项目特点调整缓存版本策略和哈希长度,平衡构建速度与缓存有效性。

相关推荐
卸任7 小时前
Vibe coding工具使用心得
前端·ai编程
brien97 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance8 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M8 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写8 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李8 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易8 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马8 小时前
前沿视界
前端
kyriewen8 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易9 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员