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

相关推荐
石金龙44 分钟前
[译] Composition in CSS
前端·css
白水清风1 小时前
微前端学习记录(qiankun、wujie、micro-app)
前端·javascript·前端工程化
Ticnix1 小时前
函数封装实现Echarts多表渲染/叠加渲染
前端·echarts
用户22152044278001 小时前
new、原型和原型链浅析
前端·javascript
阿星做前端1 小时前
coze源码解读: space develop 页面
前端·javascript
叫我小窝吧1 小时前
Promise 的使用
前端·javascript
NBtab1 小时前
Vite + Vue3项目版本更新检查与页面自动刷新方案
前端
天天扭码1 小时前
来全面地review一下Flex布局(面试可用)
前端·css·面试
用户458203153172 小时前
CSS特异性:如何精准控制样式而不失控?
前端·css
libraG2 小时前
Jenkins打包问题
前端·npm·jenkins