vite依赖预构建(源码分析)

vite主张的是no bundle, 但是在开发环境,对于第三方依赖会基于esbuild进行依赖预构建,为什么选择esbuild,根据测试,发现esbuild构建速度对比rollup webpack非常快,开发环境注重效率。

依赖为什么需要预构建

性能方面考虑

  1. 如果依赖不构建,那么加载依赖的时候,比如lodash,有数百个模块,加载多次,影响性能,所以将依赖构建后,统一一个出口透出。

统一成ES Module

  1. 有些第三方包对外透出的是CommonJS和UMD等格式,需要将其转换成ESM格式,然后才能在浏览器中正常加载

vite预构建产物

构建产物会缓存在node_modules/.vite目录下

依赖预构建源码流程解析

主流程如下,主要实现在createDepsOptimizer()中

第一步:缓存判断

  1. 主要根据metadata中的lock文件的hash值和lock文件的生成的hash比对是否一致, metadata中的configHash值和config内容生成的hash比对是否一致;主要判断optimizeDeps上的配置,是否有变更;
js 复制代码
const lockfileFormats = [
  { name: "package-lock.json", checkPatches: true, manager: "npm" },
  { name: "yarn.lock", checkPatches: true, manager: "yarn" },
  // Included in lockfile for v2+
  { name: "pnpm-lock.yaml", checkPatches: false, manager: "pnpm" },
  // Included in lockfile
  { name: "bun.lockb", checkPatches: true, manager: "bun" }
].sort((_, { manager }) => {
  return process.env.npm_config_user_agent?.startsWith(manager) ? 1 : -1;
});
js 复制代码
      optimizeDeps: {
        include: optimizeDeps2?.include ? unique(optimizeDeps2.include).sort() : void 0,
        exclude: optimizeDeps2?.exclude ? unique(optimizeDeps2.exclude).sort() : void 0,
        esbuildOptions: {
          ...optimizeDeps2?.esbuildOptions,
          plugins: optimizeDeps2?.esbuildOptions?.plugins?.map((p) => p.name)
        }
      }
lua 复制代码
    if (cachedMetadata) {
      if (cachedMetadata.lockfileHash !== getLockfileHash(config)) {
        config.logger.info(
          "Re-optimizing dependencies because lockfile has changed"
        );
      } else if (cachedMetadata.configHash !== getConfigHash(config, ssr)) {
        config.logger.info(
          "Re-optimizing dependencies because vite config has changed"
        );
      } else {
        log?.("Hash is consistent. Skipping. Use --force to override.");
        return cachedMetadata;
      }
    }

第二步:发现依赖&依赖收集

扫描入口判断

判断是否有指定的optimizeDeps的entries和input等配置,找到依赖扫描的入口,如果没有入口就指定根路径的html文件为入口;

js 复制代码
async function computeEntries(config) {
  let entries = [];
  const explicitEntryPatterns = config.optimizeDeps.entries;
  const buildInput = config.build.rollupOptions?.input;
  if (explicitEntryPatterns) {
    entries = await globEntries(explicitEntryPatterns, config);
  } else if (buildInput) {
    const resolvePath = (p) => path$n.resolve(config.root, p);
    if (typeof buildInput === "string") {
      entries = [resolvePath(buildInput)];
    } else if (Array.isArray(buildInput)) {
      entries = buildInput.map(resolvePath);
    } else if (isObject$1(buildInput)) {
      entries = Object.values(buildInput).map(resolvePath);
    } else {
      throw new Error("invalid rollupOptions.input value.");
    }
  } else {
    entries = await globEntries("**/*.html", config);
  }
  entries = entries.filter(
    (entry) => isScannable(entry, config.optimizeDeps.extensions) && fs__default.existsSync(entry)
  );
  return entries;
}

准备扫描器

主要基于esbuild.context(), 生成依赖扫描器,并把自定义的插件esbuildScanPlugin传递进去;

生成依赖, 存储在deps

js 复制代码
return context.rebuild().then(() => {
  return {
    // Ensure a fixed order so hashes are stable and improve logs
    deps: orderedDependencies(deps),
    missing
  };

第三步:打包依赖

将上一步收集到的depsInfo依赖列表,传递给esbuild.context()来进行分析,然后调用rebuild()这一步依赖就生成了。

依赖路径重写

我们在源码中的写的Import, 最后被重写成esbuild预构建后的路径

实现原理

在createServer阶段,就注册了多个中间件,当浏览器请求main.ts时,会触发transformMiddleware

主要流程

vite:import-analysis插件

  1. 通过es-module-lexer提取出所有的import数据;
  2. 通过上面依赖预构建生成的dep,可以获取到esbuild生成的路径,interopNamedImports()中替换源码中的路径生成新的code;
相关推荐
学嵌入式的小杨同学1 天前
从零打造 Linux 终端 MP3 播放器!用 C 语言实现音乐自由
linux·c语言·开发语言·前端·vscode·ci/cd·vim
芝士爱知识a1 天前
2026年AI面试软件推荐
人工智能·面试·职场和发展·大模型·ai教育·考公·智蛙面试
weixin_425543731 天前
TRAE CN3.3.25 构建的Electron简易DEMO应用
前端·typescript·electron·vite·nestjs
Mr Xu_1 天前
【Vue3 + ECharts 实战】正确使用 showLoading、resize 与 dispose 避免内存泄漏
前端·信息可视化·vue·echarts
0思必得01 天前
[Web自动化] Selenium设置相关执行文件路径
前端·爬虫·python·selenium·自动化
石去皿1 天前
大模型面试通关指南:28道高频考题深度解析与实战要点
人工智能·python·面试·职场和发展
雯0609~1 天前
hiprint:实现项目部署与打印1-官网提供普通html版本
前端·html
C雨后彩虹1 天前
CAS与其他并发方案的对比及面试常见问题
java·面试·cas·同步·异步·
美团程序员1 天前
80道经典常见测试面试题
软件测试·面试·职场和发展·软件测试面试
测试秃头怪1 天前
面试大厂就靠这份软件测试八股文了【含答案】
自动化测试·软件测试·python·功能测试·面试·职场和发展·单元测试