07-Vite预构建上手
# 0.前言
本节博客代码仓库 (opens new window),主要简述了预构建存在的问题,并且提前介绍了可能存在的一些坑点。
# 1.概念
# 问题1:预构建解决了什么问题?
主要解决了两个问题:
解决 请求瀑布流问题 。
正所谓有舍必有得,享受现代浏览器按需加载的好处的同时,也有弊端,即庞大的第三方依赖所导致的网络请求。
将
cjs包转化为esm格式。由于
vite强制要求所有模块依赖必须为esm格式,但是npm库并不是所有的包都提供这种格式。比如说大名鼎鼎的react就只提供cjs格式。需要注意的是,上述的模块依赖不仅指的是直接依赖,间接依赖也必须为
esm格式。下面会举例@loadable/component这个包,其本身具有ESM格式产物,但是其间接依赖hoist-non-react-statics及其子依赖react-is为cjs格式产物。
# 问题2:构建结果存放位置?以及如何重新预构建?
预构建的产物会被存放在 node_module/.vite/deps 目录下。
通过浏览器加载模块的时候就可以发现模块均从上述文件夹下引入的。
通过 network 面板发现,开发时可以利用到浏览器缓,第二次请求时会从缓存中读取。

重新触发构建的条件:
手动删除
node_modules/.vite目录。在
vite.config.js中配置optimizeDeps中将force设置为true脚手架中使用
npx vite --force加上--force参数。上述参数为合成指令:底层的话其实是先通过
npx vite optimize对依赖进行预构建;再就是vite dev启动服务器。
# 2.进阶
# 问题3:vite 中的预构建可能存在什么问题?如何优化?
当 vite 使用异步模块导入时,会存在无法分析第三方依赖的问题。
由于 vite 是异步按需加载。因此不会提前对异步模块中的第三方模块进行依赖扫描。如果此时异步包中引入第三方包时会触发 二次依赖构建。
优化方案:手动将可能会扫描的包提前配置到 vite.config.js 中,避免开发阶段二次预构建的情况。
来看一个例子,在仓库代码中,App.tsx 中通过 importModule 动态引入了 zh_CN.ts 这个包
/* 预构建优化:动态导入 zh_CN.ts 模块,无法对其内部的模块进行分析 */
const importModule = (m: string) => import(`./locales/${m}.ts`);
function App() {
importModule("zh_CN");
return <div className="App"></div>
}
// zh_CN
import objectAssign from "object-assign";
2
3
4
5
6
7
8
9
10
而这个动态模块中又依赖了一个第三方包:"object-assign",若直接启动项目,则会在控制台提示:
➜ Local: http://127.0.0.1:5174/
➜ Network: use --host to expose
23:18:40 [vite] ✨ new dependencies optimized: object-assign
23:18:40 [vite] ✨ optimized dependencies changed. reloading
2
3
4
在 vite.config.ts 的 optimizeDeps.include 配置可手动提前预构建。
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
optimizeDeps: {
include: [
"object-assign",
],
},
});
2
3
4
5
6
7
8
9
# 问题4:预构建不想将某个包打进去,可不可以?
可以,但是 不推荐 。
前面已经介绍预构建其中一个作用就是由 esbuild 提供模块规范转化功能(后续博客也会详细这部分机制,到时可以在项目中手动插件实现)。
以 @loadable/componets 举例,这个包本身支持 ESM 格式。
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
optimizeDeps: {
exclude: ["@loadable/component"],
},
});
2
3
4
5
6
7
如果直接 excludes 会报错,控制台打印如下:
Uncaught SyntaxError: The requested module '/@fs/Users/jiashengwang/Project/Learn-vite/node_modules/.pnpm/hoist-non-react-statics@3.3.2/node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js?v=5d0e453c' does not provide an export named 'default' (at loadable.esm.js?v=5d0e453c:7:8)
提示 hoist-non-react-statics 这个包缺少一个 default 导出。什么情况下会存在这个报错,就是当包为cjs 格式时会报这个错误。因为 cjs 就没有 default。如果真实项目中 esm 要导入 cjs 包的话,需要在 ts 中开启 esModuleInterop 这个参数,嵌入一些辅助代码。
解决方案:就是将不满足要求的间接依赖抽出来。
export default defineConfig({
plugins: [react()],
optimizeDeps: {
include: [
"@loadable/component > hoist-non-react-statics",
"@loadable/component > hoist-non-react-statics > react-is",
],
exclude: ["@loadable/component"],
},
});
2
3
4
5
6
7
8
9
10
新版 @loadable/component 还会多依赖个 react-is 库。
使用 exclude 属性的前提是需要自己提前分析出包依赖的格式规范,可以发现这个操作是非常困难的。
下面顺便分析下 exclude 前后模块依赖:
exclude前:

@lodable_component 请求头为:http://127.0.0.1:5174/node_modules/.vite/deps/@loadable_component.js?v=9c67aeed
exclude后:

直接导入的是 loadable/component 提供的 esm 模块,并且请求地址为:http://127.0.0.1:5174/@fs/.../node_modules/.pnpm/@loadable+component@5.15.3/node_modules/@loadable/component/dist/loadable.esm.js?v=27562b28
是从 node_modules/.pnpm 仓库中导出的。
并且会多出两个请求是上面配置的 include 中的内容,因为这两个请求是从 .vite/deps 中取出给 loadasble.esm.js 引用的。
# 问题5:遇到有些第三方包就是个坑,用不了怎么解决?
举例:react-virtualized 包
由于无法保证第三方包的质量,有些包产物就是有问题的,对于此类问题就是具体问题具体分析,找到报错点。
首先说这个包的坑点在于下面这段 错语句,WindowScroller.js 并没有导出这个模块。
import { bpfrpt_proptype_WindowScroller } from "../WindowScroller.js";
好在用不到这段代码,因此第一种方案是直接到 node_modules 中删除这端代码。
为了将 node_modules 的更改记录保留下来,可以使用到 patch 技术,新版 pnpm 已支持 patch 操作,否则需要安装 patch-package。
使用 pnpm 新增 patch 步骤如下:
pnpm patch react-virtualized@9.22.3注意需要明确指定出版本号,此时在终端会显示如下:
$ pnpm patch react-virtualized@9.22.3 You can now edit the following folder: /private/var/folders/j6/c5rrdls52jg_9crpkxt01tym0000gn/T/37cc1d7e255fd6737dd810e542253644 Once you're done with your changes, run "pnpm patch-commit /private/var/folders/j6/c5rrdls52jg_9crpkxt01tym0000gn/T/37cc1d7e255fd6737dd810e542253644"1
2
3然后就可以直接在
node_modules中修改了,修改结束后,根据上述提示输入:pnpm patch-commit /private/var/folders/j6/c5rrdls52jg_9crpkxt01tym0000gn/T/37cc1d7e255fd6737dd810e5422536441修改结束后,会对应生成
patches文件夹,在package.json新增:"pnpm": { "patchedDependencies": { "react-virtualized@9.22.3": "patches/react-virtualized@9.22.3.patch" } }1
2
3
4
5
第二种方案,则是使用 ESBuild 插件的方式完成删除操作
插件开发也很简单就两步:
- 找到对应的模块入口。
- 使用
replace删除语句。
// vite.config.ts
const esbuildPatchPlugin = {
name: "react-virtualized-patch",
setup(build) {
build.onLoad(
{
filter: /* 过滤插件 */
/react-virtualized\/dist\/es\/WindowScroller\/utils\/onScroll.js$/,
},
async (args) => {
const text = await fs.promises.readFile(args.path, "utf8");
return {
contents: text.replace(
'import { bpfrpt_proptype_WindowScroller } from "../WindowScroller.js";',
""
),
};
}
);
},
};
// 插件加入 Vite 预构建配置
{
optimizeDeps: {
esbuildOptions: {
plugins: [esbuildPatchPlugin];
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30