我们已经分析过 依赖预构建的意义以及使用,对于其底层的实现并没有作过多的介绍。而在 Vite 依赖预构建的底层实现中,大量地使用到了 Esbuild 这款构建工具,实现了比较复杂的 Esbuild 插件,同时也应用了诸多 Esbuild 使用技巧。相信在理解这部分的源码之后,你将会对 Vite 预构建以及 Esbuild 本身有更加深入的认识。
接下来,我就来带你揭开 Vite 预构建神秘的面纱,从核心流程到依赖扫描、依赖打包的具体实现,带你彻底理解预构建背后的技术,学习 Vite 是如何灵活运用 Esbuild,将 Esbuild 这个打包工具玩出花来的。
# 预构建核心流程
关于预构建所有的实现代码都在optimizeDeps函数当中,也就是在仓库源码的 packages/vite/src/node/optimizer/index.ts 文件中,你可以对照着来学习。
# 缓存判断
首先是预构建缓存的判断。Vite 在每次预构建之后都将一些关键信息写入到了_metadata.json文件中,第二次启动项目时会通过这个文件中的 hash 值来进行缓存的判断,如果命中缓存则不会进行后续的预构建流程,代码如下所示:
// _metadata.json 文件所在的路径
const dataPath = path.join(cacheDir, "_metadata.json");
// 根据当前的配置计算出哈希值
const mainHash = getDepHash(root, config);
const data: DepOptimizationMetadata = {
hash: mainHash,
browserHash: mainHash,
optimized: {},
};
// 默认走到里面的逻辑
if (!force) {
let prevData: DepOptimizationMetadata | undefined;
try {
// 读取元数据
prevData = JSON.parse(fs.readFileSync(dataPath, "utf-8"));
} catch (e) {}
// 当前计算出的哈希值与 _metadata.json 中记录的哈希值一致,表示命中缓存,不用预构建
if (prevData && prevData.hash === data.hash) {
log("Hash is consistent. Skipping. Use --force to override.");
return prevData;
}
}
@前端进阶之旅: 代码已经复制到剪贴板
值得注意的是哈希计算的策略,即决定哪些配置和文件有可能影响预构建的结果,然后根据这些信息来生成哈希值。这部分逻辑集中在getHash函数中,我把关键信息放到了注释中:
const lockfileFormats = ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"];
function getDepHash(root: string, config: ResolvedConfig): string {
// 获取 lock 文件内容
let content = lookupFile(root, lockfileFormats) || "";
// 除了 lock 文件外,还需要考虑下面的一些配置信息
content += JSON.stringify(
{
