依赖预构建

    这就是 Vite 执行的所谓的“依赖预构建”。这个过程有两个目的:

    1. CommonJS 和 UMD 兼容性: 开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。因此,Vite 必须先将作为 CommonJS 或 UMD 发布的依赖项转换为 ESM。

      当转换 CommonJS 依赖时,Vite 会执行智能导入分析,这样即使导出是动态分配的(如 React),按名导入也会符合预期效果:

      1. // 符合预期
      2. import React, { useState } from 'react'
    2. 性能: Vite 将有许多内部模块的 ESM 依赖关系转换为单个模块,以提高后续页面加载性能。

      通过预构建 lodash-es 成为一个模块,我们就只需要一个 HTTP 请求了!

    如果没有找到相应的缓存,Vite 将抓取你的源码,并自动寻找引入的依赖项(即 “bare import”,表示期望从 node_modules 解析),并将这些依赖项作为预构建包的入口点。预构建通过 esbuild 执行,所以它通常非常快。

    在服务器已经启动之后,如果遇到一个新的依赖关系导入,而这个依赖关系还没有在缓存中,Vite 将重新运行依赖构建进程并重新加载页面。

    在一个 monorepo 启动中,该仓库中的某个依赖可能会成为另一个包的依赖。Vite 会自动侦测没有从 解析的依赖项,并将链接的依赖视为源码。它不会尝试打包被链接的依赖,而是会分析被链接依赖的依赖列表。

    当你遇到不能直接在源码中发现的 import 时,optimizeDeps.includeoptimizeDeps.exclude 就是典型的用例。例如,import 可能是插件转换的结果。这意味着 Vite 无法在初始扫描时发现 import —— 它只能在浏览器请求文件时转换后才能发现。这将导致服务器在启动后立即重新打包。

    includeexclude 都可以用来处理这个问题。如果依赖项很大(包含很多内部模块)或者是 CommonJS,那么你应该包含它;如果依赖项很小,并且已经是有效的 ESM,则可以排除它,让浏览器直接加载它。

    Vite 会将预构建的依赖缓存到 node_modules/.vite。它根据几个源来决定是否需要重新运行预构建步骤:

    • 包管理器的 lockfile,例如 package-lock.json, yarn.lock,或者 pnpm-lock.yaml
    • 可能在 vite.config.js 相关字段中配置过的

    只有在上述其中一项发生更改时,才需要重新运行预构建。

    浏览器缓存

    解析后的依赖请求会以 HTTP 头 max-age=31536000,immutable 强缓存,以提高在开发时的页面重载性能。一旦被缓存,这些请求将永远不会再到达开发服务器。如果安装了不同的版本(这反映在包管理器的 lockfile 中),则附加的版本 query 会自动使它们失效。如果你想通过本地编辑来调试依赖项,你可以:

    1. 通过浏览器调试工具的 Network 选项卡暂时禁用缓存;
    2. 重启 Vite dev server,并添加 命令以重新构建依赖;
    3. 重新载入页面。