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

相关推荐
lichenyang45317 分钟前
React ajax中的跨域以及代理服务器
前端·react.js·ajax
呆呆的小草19 分钟前
Cesium距离测量、角度测量、面积测量
开发语言·前端·javascript
一 乐1 小时前
民宿|基于java的民宿推荐系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·源码
testleaf2 小时前
前端面经整理【1】
前端·面试
好了来看下一题2 小时前
使用 React+Vite+Electron 搭建桌面应用
前端·react.js·electron
啃火龙果的兔子2 小时前
前端八股文-react篇
前端·react.js·前端框架
小前端大牛马2 小时前
react中hook和高阶组件的选型
前端·javascript·vue.js
刺客-Andy2 小时前
React第六十二节 Router中 createStaticRouter 的使用详解
前端·javascript·react.js
萌萌哒草头将军4 小时前
🚀🚀🚀VSCode 发布 1.101 版本,Copilot 更全能!
前端·vue.js·react.js
GIS之路4 小时前
OpenLayers 图层叠加控制
前端·信息可视化