Vite 构建打包和拆分
Vite 默认拆包策略
vite 默认拆包策略总体与 webpack 相似,就是会分为同步chunk(Initital Chunk)和异步chunk(Async Chunk),然后分开打包各自 chunk 代码。
这样会造成几个问题:
- Initital Chunk 和 Async Chunk 引入相同模块的话,会重复打包进各自的 chunk
- chunk 的体积过大,修改一行代码就会整个 chunk 重新生成。不利与缓存
基于以上的问题所有就有了拆包策略
TIP
Vite 2.9 版本之前,vite 会对于 Initital Chunk 的业务代码和第三方包代码(node_modeules)分别打包为单独的 chunk
自定义拆包策略
针对更细粒度的拆包,Vite 的底层打包引擎 Rollup 提供了manualChunks,让我们能自定义拆包策略,它属于 Vite 配置的一部分,示例如下。
js
export default {
build: {
rollupOptions: {
output: {
// manualChunks 配置
manualChunks: {},
},
}
},
}
manualChunks 主要有两种配置的形式,可以配置为一个对象或者一个函数。我们先来看看对象的配置,也是最简单的配置方式,你可以在上述的示例项目中添加如下的manualChunks配置代码。
js
{
...
output: {
// manualChunks 配置
manualChunks: {
// 将 React 相关库打包成单独的 chunk 中
'react-vendor': ['react', 'react-dom'],
// 将 Lodash 库的代码单独打包
'lodash': ['lodash-es'],
// 将组件库的代码打包
'library': ['antd', '@arco-design/web-react'],
},
},
}
在对象格式的配置中,key代表 chunk 的名称,value为一个字符串数组,每一项为第三方包的包名。
js
manualChunks(id) {
if (id.includes('antd') || id.includes('@arco-design/web-react')) {
return 'library';
}
if (id.includes('lodash')) {
return 'lodash';
}
if (id.includes('react')) {
return 'react';
}
}
在函数格式中,Rollup 会对每一个模块调用 manualChunks 函数,在 manualChunks 的函数入参中你可以拿到模块 id 及模块详情信息,经过一定的处理后返回 chunk 文件的名称,这样当前 id 代表的模块便会打包到你所指定的 chunk 文件中。
更详细的配置推荐去 Rollup 官网查看
循环引用问题
Rollup 上会存在循环引用问题
(但本人目前并没有遇到,所以只是记下解决方案)
js
// 确定 react 相关包的入口路径
const chunkGroups = {
'react-vendor': [
require.resolve('react'),
require.resolve('react-dom')
],
}
// Vite 中的 manualChunks 配置
function manualChunks(id, { getModuleInfo }) {
for (const group of Object.keys(chunkGroups)) {
const deps = chunkGroups[group];
if (
id.includes('node_modules') &&
// 递归向上查找引用者,检查是否命中 chunkGroups 声明的包
isDepInclude(id, deps, [], getModuleInfo)
) {
return group;
}
}
}
// 缓存对象
const cache = new Map();
function isDepInclude (id: string, depPaths: string[], importChain: string[], getModuleInfo): boolean | undefined {
const key = `${id}-${depPaths.join('|')}`;
// 出现循环依赖,不考虑
if (importChain.includes(id)) {
cache.set(key, false);
return false;
}
// 验证缓存
if (cache.has(key)) {
return cache.get(key);
}
// 命中依赖列表
if (depPaths.includes(id)) {
// 引用链中的文件都记录到缓存中
importChain.forEach(item => cache.set(`${item}-${depPaths.join('|')}`, true));
return true;
}
const moduleInfo = getModuleInfo(id);
if (!moduleInfo || !moduleInfo.importers) {
cache.set(key, false);
return false;
}
// 核心逻辑,递归查找上层引用者
const isInclude = moduleInfo.importers.some(
importer => isDepInclude(importer, depPaths, importChain.concat(id), getModuleInfo)
);
// 设置缓存
cache.set(key, isInclude);
return isInclude;
};
如果存在引用循环问题,推荐使用第三方库来解决 vite-plugin-chunk-split