vite.config.ts配置文件选项:
build.rollupOptions.output.manualChunks
作用:允许创建自定义共享的公共块。
output.mannualChunks的配置类型是:(来自官方)
{ [chunkAlias: string]: string[] } | ((id: string, {getModuleInfo, getModuleIds}) => string | void)
表示mannualChunks可以是对象字面量形式,也可以是函数形式
对象字面量形式:
manualChunks:{
"pageAndOptions":["src/components/A.js","src/components/B.js"]
}
键名pageAndOptions会出现在最终打包结果中;数组中的值是代码文件在工程中实际的目录地址
官方给出的示例是:
manualChunks: {
lodash: ['lodash']
}
数组中的值是npm 安装的依赖的名称,vite应该会从node_modules中自行查找。最终打包结果中会出现lodash单独的chunk文件
函数形式:
manualChunks(id,parentId,isResolved){
if(id.includes('src/components/A.js') || id.includes('src/components/B.js')){
console.log(parentId.getModuleIds(),parentId.getModuleInfo())
return 'pageAndOptions'
}
}
控制台会打印两次console.log中的内容,说明mannulChunks是循环遍历执行。id.includes()传入的参数和对象字面量中的一样,要么是组件在工程里的路径,要么是npm 安装的依赖名称。
getModuleIds()函数打印出所有依赖图中的文件列表:
问题:
1. 当采用对象字面量形式的时候,工程构建过程提示warn如下:
WARN (!) the splitVendorChunk plugin doesn't have any effect when using the object form of build.roll
upOptions.output.manualChunks. Consider using the function form instead.
原因是引入了import { splitVendorChunkPlugin } from 'vite' 插件,并且在plugins中进行了配置:
plugins:[splitVendorChunkPlugin()]
所以,mannualChunks还是要采用函数形式!!!
2. 副作用- side effect
Be aware that manual chunks can change the behaviour of the application if side effects are triggered before the corresponding modules are actually used.
请注意,如果在实际使用相应模块之前触发了副作用,则手动块可能会改变应用程序的行为。
通义千问的解释是如下: 意思是模块除了导出函数之外,本身还初始运行代码逻辑,这些功能被称为副作用。
这里的"副作用"(side effects)指的是模块执行时除了导出值之外还会影响全局状态或产生其他非直接输出结果的行为。这些行为可能包括但不限于:
- 修改全局变量:比如修改
window
对象上的属性,或者修改全局的Array.prototype
等。- 执行DOM操作:直接操作文档对象模型(DOM),如添加、删除元素或修改样式。
- 注册事件监听器:向文档或某个元素添加事件监听器。
- 执行异步操作:如发起网络请求、设置定时器(setTimeout、setInterval)等。
- 引入外部资源:如通过脚本标签加载额外的JS或CSS文件。
- 环境设置:使用
process.env
或其他手段改变运行时环境配置。当Rollup的
manualChunks
被用来手动分割代码块时,如果某个模块含有上述副作用,并且这个模块被分配到了一个延迟加载(比如按需加载)的chunk中,那么这些副作用的执行也会被延迟。这意味着,如果应用程序的其他部分依赖于这些副作用的即时执行(例如,期望在模块导入时立即修改全局状态或注册事件监听器),则应用的行为可能会与预期不符。
扩展:
当使用函数形式的时候,mannualChunks的第二个参数是{getModuleInfo
,getModuleIds
},getModuleIds函数能够列出依赖图里所有文件路径,通过编程方式可以解决动态依赖打包问题:(来自官网示例Configuration Options | Rollup (rollupjs.org))
// Inside the "foo" component
function getTranslatedStrings(currentLanguage) {
switch (currentLanguage) {
case 'en':
return import('./foo.strings.en.js');
case 'de':
return import('./foo.strings.de.js');
// ...
}
}
function manualChunks(id, { getModuleInfo }) {
const match = /.*\.strings\.(\w+)\.js/.exec(id);
if (match) {
const language = match[1]; // e.g. "en"
const dependentEntryPoints = [];
// we use a Set here so we handle each module at most once. This
// prevents infinite loops in case of circular dependencies
const idsToHandle = new Set(getModuleInfo(id).dynamicImporters);
for (const moduleId of idsToHandle) {
const { isEntry, dynamicImporters, importers } =
getModuleInfo(moduleId);
if (isEntry || dynamicImporters.length > 0)
dependentEntryPoints.push(moduleId);
// The Set iterator is intelligent enough to iterate over
// elements that are added during iteration
for (const importerId of importers) idsToHandle.add(importerId);
}
// If there is a unique entry, we put it into a chunk based on the
// entry name
if (dependentEntryPoints.length === 1) {
return `${
dependentEntryPoints[0].split('/').slice(-1)[0].split('.')[0]
}.strings.${language}`;
}
// For multiple entries, we put it into a "shared" chunk
if (dependentEntryPoints.length > 1) {
return `shared.strings.${language}`;
}
}
}
这段代码看上去比较复杂,但实际分析起来还是挺简单的。其实就是利用了dynamicImporters和importers。将动态引用当前组件的父组件添加到dependentEntryPoints数组中,通过importers继续分析引用了父组件的组件,再去分析是否存在动态引用。是一种从下往上分析的算法,直到分析到了入口文件isEntry