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/37cc1d7e255fd6737dd810e542253644
1修改结束后,会对应生成
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