Skip to content
文章目录

vite依赖预构建

打印出依赖预构建相关的构建信息

shell
pnpm add cross-env -D

cross-env DEBUG=vite:deps vite

json
{
  "name": "project-template",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "cross-env DEBUG=vite:deps vite"
  }
}

预构建的发生了什么

为什么要预构建

  • CommonJS 和 UMD 兼容性: 开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。因此,Vite 必须先将作为 CommonJS 或 UMD 发布的依赖项转换为 ESM。
  • 性能: Vite 将有许多内部模块的 ESM 依赖关系转换为单个模块,以提高后续页面加载性能。

一些包将它们的 ES 模块构建作为许多单独的文件相互导入。例如,lodash-es 有超过 600 个内置模块!当我们执行 import { debounce } from 'lodash-es' 时,浏览器同时发出 600 多个 HTTP 请求!尽管服务器在处理这些请求时没有问题,但大量的请求会在浏览器端造成网络拥塞,导致页面的加载速度相当慢。通过预构建 lodash-es 成为一个模块,我们就只需要一个 HTTP 请求了!

js
// 在 Chrome console 运行以下代码,体验一次拉取 600+ 个请求
import('https://unpkg.com/lodash-es/lodash.js')

600+ 的请求,单单就拉取一个 lodash-es ,这不是我们想要的。需要有一种手段能将这种多文件的关联依赖导致的多个网络请求问题解决。这个手段就是依赖预构建

依赖扫描

一个项目中,存在非常多的模块,并不是所有模块都会被预构建。只有 bare import(裸依赖)会执行依赖预构建

依赖扫描的目的,就是找出所有的这些第三方依赖,依赖扫描的结果如下:

json
{
  "lodash-es": "D:/tencent/app/vite/node_modules/.pnpm/lodash-es@4.17.21/node_modules/lodash-es/lodash.js",
  "vue": "D:/tencent/app/vite/node_modules/.pnpm/vue@3.2.37/node_modules/vue/dist/vue.runtime.esm-bundler.js"
}

依赖扫描函数 discoverProjectDependencies 会返回一个对象:

  • key:第三方依赖的名字
  • value:模块的入口文件的本地真实路径

入口扫描

如果用户没有指定入口文件,Vite 会扫描项目目录下的所有 HTML 文件(**/*.html、node_modules 除外)

扫描结果如下:

json
[
  "D:/tencent/app/vite/playground/vue/index.html",
  "D:/tencent/app/vite/playground/vue/setup-import-template/template.html",
  "D:/tencent/app/vite/playground/vue/src-import/template.html"
]

依赖扫描

先看一下项目中模块的依赖关系:

从入口的 HTML 文件开始,根据模块的 import 依赖关系,可以连接成一颗模块依赖树。

要扫描出所有的 bare import,就需要遍历整个依赖树,这就涉及到了树的深度遍历

我们只需要深度遍历所有树节点,找出所有 import 语句,把 import 的模块记录下来即可

打包依赖

依赖扫描已经拿到了所有需要预构建的依赖信息,那接下来直接使用 esbuild 进行打包即可。

最终会有如下的调用:

js
import { build } from 'esbuild'

const result = await build({
  absWorkingDir: process.cwd(),
  entryPoints: ['vue', 'lodash-es'],
  bundle: true,
  format: 'esm',
  target: ['es2020', 'edge88', 'firefox78', 'chrome87', 'safari13'],
  splitting: true, // 该参数会自动进行代码分割
  plugins: [
    /* some plugin */
  ],
  // 省略其他配置
})

打包的产物如下:

  • vue.js
  • lodash-es.js

打开 lodash-es.js 文件,可以看到,所有的代码都被打包到一个文件中了

如果打包的依赖间,存在依赖的关系/有公共的依赖,这要如何处理?

例如:

  • lodash-es 和 lodash-es/merge,lodash-es 中包含 lodash-es/merge 的代码
  • vue 和 ant-design-vue, ant-design-vue 中使用到了 vue 中的 API,依赖 vue

公共依赖的问题,esbuild 会自动处理。

依赖路径替换

依赖打包完之后,最后就是路径替换了。

js
- import { createApp, defineCustomElement } from 'vue'
+ import { createApp, defineCustomElement } from '/node_modules/.vite/deps/vue.js?v=b92a21b7'

由于 import vue 这种模块引入方式,使用的是 Nodejs 特有的模块查找算法(到 node_modules 中取查找),浏览器无法使用,因此 Vite 会将 vue 替换成 /node_modules/.vite/deps/vue.js?v=b92a21b7,当浏览器解析到这行 import 语句时,会发送一个 /node_modules/.vite/deps/vue.js?v=b92a21b7 的请求。 所有请求都会在 Vite dev server 的中间件处理,而这个请求,会被 static 中间件处理:用于访问静态文件,到会到该目录下,查找文件并返回。

相关文章

no-bundle 构建

参考资料

快速理解 Vite 的依赖预构建

npm 常用模块之 cross-env 使用

Vite 依赖预构建,缩短数倍的冷启动时间