Vite的依赖预构建

什么是 bare import ?

        用 名称 去访问的模块是 bare import(又称裸模块)

        用 路径 去访问的模块不是 bare import

// vue 是 bare import
import xxx from "vue"
import xxx from "vue/xxx"

// 以下不是裸依赖
import xxx from "./foo.ts" 
import xxx from "/foo.ts" 

为什么只对 bare import 进行预构建?

        Node.js 定义了 bare import 的寻址机制 —— 在当前目录下的 node_modules 下寻找,找不到则往上一级目录的 node_modules,直到目录为根路径,不能再往上为止。

        bare import 一般是 npm 安装的模块,是第三方的模块,不是我们自己写的代码,一般情况下是不会被修改的,因此对这部分的模块提前执行构建,有利于提升性能。

        相反,如果对开发者写的代码执行预构建,将项目打包成 chunk 文件,当开发者修改代码时,就需要重新执行构建,再打包成 chunk 文件,这个过程反而会影响性能。

实际上,Vite 会判断模块的实际路径,是否在 node_modules 中

        实际路径在 node_modules 的模块会被预构建。

        实际路径不在 node_modules 的模块,证明该模块是通过文件链接,链接到 node_modules 内的(monorepo 的实现方式),是开发者自己写的代码,不执行预构建。
     


Vite 依赖预构建做了什么

        流程如下:

1. 扫描入口文件(即入口 html 文件;若用户没有指定入口文件,Vite 则会扫描目录下的所有 HTML 文件);

2. 扫描所有用到的依赖(层层遍历扫描所有用到的依赖);

3. 将多个依赖进行打包;

4. 修改这些模块的引入路径(如 import vue from 'vue'变成import vue from '/node_modules/.vite/deps/vue.js?v=b92a21b7')。

        可以看到执行 npm run dev 命令后, node_module 下会多了一个 .vite 目录,依赖预构建的产物会放在 .vite/deps 目录下。

        而有时在对其它项目 npm run dev 后发现.vite 目录为空,可能原因有:

1. 该项目依赖的插件和库已经安装在 node_modules

2. 这些依赖大多已经是 ESM 格式

3. vite.config.ts 中显式配置了 optimizeDeps,优化了预构建行为

为什么要预构建

1. CommonJS 和 UMD 兼容性

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

2. 性能

        有些库可能包含多个嵌套依赖,导致最终的应用程序中存在重复的代码或者版本冲突。Vite 将有许多内部模块的 ESM 依赖关系转换为单个模块,以提高后续页面加载性能。

3. 缓存机制

         预构建的结果会被缓存,这意味着如果依赖没有发生变化,在后续的开发服务器启动过程中可以直接使用已有的缓存结果,大大减少了首次启动所需的时间。


举例:

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


 

依赖扫描

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

        依赖扫描的目的,就是找出所有的这些第三方依赖(即 npm 安装的模块)。

        依赖扫描函数 discoverProjectDependencies 会返回一个对象,该对象包括:

        key:第三方依赖的名字

        value:模块的入口文件的本地真实路径

{
  "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"
}

入口扫描

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

[
  "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 的模块记录下来即可。

        当然,在扫描前,有以下几个问题需要理清楚:

1. JS 文件中如何找到 import 语句呢?

        这个可以用正则表达式匹配,也可以先将代码解析成 AST 抽象语法树,然后找到 Import 节点。后者更准确。

找到 import 语句后:

        如果 import 的模块是第三方依赖,则记录下来。如: vue

        如果开发者自己写的项目模块,则继续递归处理该模块。如:Main.vue,这时候应该继续处理 Main.vue

import { createApp, defineCustomElement } from 'vue'
import Main from './Main.vue'
import CustomElement from './CustomElement.ce.vue'

        其中,vue 会被记录,./Main.vue./CustomElement.ce.vue 将会被继续深入地处理。

2. HTML 文件如何处理呢?

        因为 HTML 文件内,可能存在 script 标签,这部分的代码,就可能包含 import 语句。且项目本身就是把 HTML 文件当成入口的。因此必须得处理 HTML。

        由于不关心 HTML 中其他的部分,我们只需要先script 标签的内容提取出来,然后再按 JS 的处理方式处理即可

        Vue 文件,也是同 HTML 文件类似的处理方式。

3. CSS、PNG 等非 JS 模块如何处理?

        这些文件不需要任何处理,直接跳过即可,因为这些文件不可能再引入 JS 模块

        以上这几个问题实现解决比较麻烦,因此 Vite 巧妙的借助了打包工具进行处理,所以项目中可以使用打包工具处理。

打包依赖

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

依赖路径替换

        依赖打包完之后,最后就是路径替换,替换操作如下:

- 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 依赖预构建是什么、为什么要进行预构建,以及预构建的全流程:

        1. 扫描入口文件,然后通过这些入口,扫描所有用到的依赖

        2. 将多个依赖进行打包

        3. 修改这些模块的引入路径

本文整理,参考链接:

Vite预构建

Vite的依赖扫描

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值