一、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
四、核心优化策略
- 哈希策略优化:
javascript
output: {
filename: '[contenthash:8].js'
}
- 缓存分段策略:
javascript
cache: {
version: `${process.env.NODE_ENV}-v1`
}
- 构建信息追踪:
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}`);
});
});
});
五、调试技巧
- 缓存调试输出:
javascript
// 在配置中增加
stats: {
cachedModules: true,
cachedAssets: true
}
- 源码断点位置:
FileSystemInfo.js: checkSnapshotValid
Compiler.js: emitAssets
CachePlugin.js: restoreCacheData
总结
Webpack 的持久化缓存系统通过以下机制实现高效构建:
- 文件级快照:结合文件元数据和内容哈希
- 依赖追踪:模块级别的依赖关系图谱
- 增量写入:仅修改变化的输出文件
- 多级校验:时间戳 + 哈希双重验证
通过合理配置缓存策略,可使二次构建速度提升 60%-80%。建议根据项目特点调整缓存版本策略和哈希长度,平衡构建速度与缓存有效性。