The above dynamic import cannot be analyzed by Vite.
🙋🏼♂️ 问题现象
今天写一个项目菜单管理,遇到一个警告信息:
# vscode 终端输出:
10:00:36 [vite] warning:
D:/my-project/src/router/dynamicRouter.ts
10 | const routeRecord = {
11 | ...rest,
12 | component: () => import(`${component}`),| ^^^^^^^^^^^^^^
13 | children: []
14 | // Initialize children here
The above dynamic import cannot be analyzed by Vite.
See https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations for supported dynamic import formats. If this is intended to be left as-is, you can use the /* @vite-ignore */ comment inside the import() call to suppress this warning.Plugin: vite:import-analysisFile: D:/my-project/src/router/dynamicRouter.ts
🔎 寻找解决方案
🧩 @rollup/plugin-dynamic-import-vars
根据错误信息,查阅了https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations
资料。
他是一个rollup插件,支持动态导入中的变量。
🛠️ 安装
npm install @rollup/plugin-dynamic-import-vars --save-dev
✍️ 使用
// vite.config.ts
import dynamicImportVars from '@rollup/plugin-dynamic-import-vars'
export default {plugins: [dynamicImportVars({// options})]
}
📓 选项
include
// 要包含在此插件中的文件(默认全部)。
Type: String | Array[...String]
Default: []
exclude
// 要在此插件中排除的文件(默认为无)。
Type: String | Array[...String]
Default: []
errorWhenNoFilesFound
// 默认情况下,当没有找到目标文件时,插件不会抛出错误。将此选项设置为true将导致在遇到不存在的文件时抛出错误。
Type: Boolean
Default: false
warnOnError
⚠️ 重要:当设置为启用时,此选项将会生产出警告而不是错误
warnOnError true
// 默认情况下,插件在遇到错误退出构建过程时。如果将此选项设置为 true,它将引发警告,并使代码保持不变。
Type: Default: Booleanfalse
🕹️ 使用方式
当动态导入包含串联字符串时,字符串的变量将替换为 glob 模式。在构建过程中评估此 glob 模式,找到的任何文件都将添加到汇总包中。在运行时,将为完整的串联字符串返回正确的导入。
一些示例模式及其生成的 glob:
`./locales/${locale}.js` -> './locales/*.js'
`./${folder}/${name}.js` -> './*/*.js'
`./module-${name}.js` -> './module-*.js'
`./modules-${name}/index.js` -> './modules-*/index.js'
'./locales/' + locale + '.js' -> './locales/*.js'
'./locales/' + locale + foo + bar + '.js' -> './locales/*.js'
'./locales/' + `${locale}.js` -> './locales/*.js'
'./locales/' + `${foo + bar}.js` -> './locales/*.js'
'./locales/'.concat(locale, '.js') -> './locales/*.js'
'./'.concat(folder, '/').concat(name, '.js') -> './*/*.js'
代码如下所示:
function importLocale(locale) {return import(`./locales/${locale}.js`)
}
变成:
function __variableDynamicImportRuntime__(path) {switch (path) {case './locales/en-GB.js':return import('./locales/en-GB.js')case './locales/en-US.js':return import('./locales/en-US.js')case './locales/nl-NL.js':return import('./locales/nl-NL.js')default:return new Promise(function (resolve, reject) {queueMicrotask(reject.bind(null, new Error('Unknown variable dynamic import: ' + path)))})}
}function importLocale(locale) {return __variableDynamicImportRuntime__(`./locales/${locale}.js`)
}
🔰 导入断言
此插件将保持动态导入语句中的导入断言完好无损。
// Refer to rollup-plugin-import-css https://github.com/jleeson/rollup-plugin-import-css
function importLocale(sheet) {return import(`./styles/${sheet}.css`, { assert: { type: 'css' } })
}
这很重要,例如在处理 CSS 导入的 rollup-plugin-import-css
上下文中, 由于仍然有一个断言,它将把 CSS 导入解析为 CSSStyleSheet
,类似于本机浏览器行为。
🙅♂️ 局限性
要知道要在 rollup bundle 中注入什么,我们必须能够对代码进行一些静态分析,并对可能的导入做出一些假设。例如,如果您只使用一个变量,理论上可以从整个文件系统中导入任何内容。
function importModule(path) {// who knows what will be imported here?return import(path)
}
为了帮助静态分析,并避免可能的乱用,我们仅限于以下几条规则:
➡️ 导入必须以 ./
开头。
所有导入都必须相对于导入文件
开始。导入不应以变量、绝对路径或裸导入开头:
// Not allowed
import(bar)
import(`${bar}.js`)
import(`/foo/${bar}.js`)
import(`some-library/${bar}.js`)
📁 必须指定文件名
如果你导入自己的目录,你最终可能会得到你不打算导入的文件,包括你自己的模块。因此,需要给出更具体的文件名模式:
// not allowed
import(`./${foo}.js`)
// allowed
import(`./module-${foo}.js`)
🌎 glob 只深入一层
生成 glob 时,字符串中的每个变量都会转换为每个目录深度最多一个星号的 glob。这样可以避免无意中将许多目录中的文件添加到导入中。*
在下面的示例中,这将生成而不是 。./foo//.js./foo/*/.js
import(`./foo/${x}${y}/${z}.js`)
🤔 方案是否可行?
结合上面的@rollup/plugin-dynamic-import-vars
方案以及自身项目
🎲 处理问题评估
目录结构:
/my-project/
├── node_modules/
├── public/
├── src/
│ ├── router/
│ │ ├── dynamicData.json
│ │ └── dynamicRouter.ts
│ ├── layouts/
│ └── views/
├── ...
├── package.json
└── vite.config.ts
静态菜单数据:
[{"id": 1,"path": "/","component": "../layouts/default.vue","children": [{"id": 2,"path": "/","name": "home","mate": { "name": "首页", "icon": "Menu" },"component": "../views/Home/HomeIndex.vue"},{"id": 3,"path": "/photo","name": "photo","mate": { "name": "图片信息", "icon": "document" },"component": "../views/Photo/PhotoIndex.vue"},{"id": 4,"path": "/personal","name": "personal","inside": true,"mate": { "name": "用户中心", "icon": "user" },"component": "../views/PersonalCenter/IndexPage.vue"}]}
]
dynamicRouter.ts
主要代码段
const routeRecord: RouteRecordRaw = {...rest,component: () => import(`${component}`),children: [] // Initialize children here
};
根据以上代码,执行后会产生vite
编译警告:
10:00:36 [vite] warning:
D:/my-project/src/router/dynamicRouter.ts
10 | const routeRecord = {
11 | ...rest,
12 | component: () => import(`${component}`),| ^^^^^^^^^^^^^^
13 | children: []
14 | // Initialize children here
The above dynamic import cannot be analyzed by Vite.
经过实际验证:
-
将
json
中../
剔除,将.vue
剔除后,大致代码:[{"id": 1,"path": "/","component": "layouts/default","children": [{"id": 2,"path": "/","name": "home","mate": { "name": "首页", "icon": "Menu" },"component": "views/Home/HomeIndex"}]} ]
-
修改引用方式
// dynamicRouter.ts const routeRecord: RouteRecordRaw = {...rest,component: () => import(`./${component}.vue`), };
这明显不行的,跟实际页面路径不匹配。
📚️ 总结
-
可以看到我们想要实现的是
../**/*.vue
文件引入,但是@rollup/plugin-dynamic-import-vars
方案并不友好,他只允许使用./
,即相对于导入文件来使用。 -
该
@rollup/plugin-dynamic-import-vars
插件不支持动态计算的路径或其他形式的动态导入。
🧱 另辟蹊径
通过一开始的错误提示,或许不应该使用rollup
插件,应该直接从vite
插件入手。
正好vite
有一款插件vite-plugin-dynamic-import
。
🧩 vite-plugin-dynamic-import
或许你会疑问,既然已经有了解决方案,为什么一开头还要讲@rollup/plugin-dynamic-import-vars
呢?
其实vite-plugin-dynamic-import
是兼容@rollup/plugin-dynamic-import-vars
的限制./
–>相对于导入文件使用。
📢 介绍
直击要害,vite插件能够为我们解决的问题:
-
用不了别名
// router.js ❌ import(`@/views/${variable}.js`)
-
必须相对路径
// router.js ❌ import(`/User/project-root/src/views/${variable}.js`)
-
必须含文件尾缀
// router.js ❌ import(`./views/${variable}`)
🛠️ 安装
npm i vite-plugin-dynamic-import -D
✍️ 使用
import dynamicImport from 'vite-plugin-dynamic-import'export default {plugins: [dynamicImport(/* options */)]
}
📓 选项
export interface Options {filter?: (id: string) => boolean | void/*** ```* 1. `true` - 尽量匹配所有可能场景, 功能更像 `webpack`* 链接 https://webpack.js.org/guides/dependency-management/#require-with-expression** 2. `false` - 功能更像rollup的 `@rollup/plugin-dynamic-import-vars`插件* 链接 https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#how-it-works** default true* ```*/loose?: boolean/*** 如果你想排除一些文件* 举俩🌰 `type.d.ts`, `interface.ts`*/onFiles?: (files: string[], id: string) => typeof files | void/*** 自定义 importee** e.g. - 在 importee 前面插入 `\/*@vite-ignore*\/` 绕过 Vite*/onResolve?: (rawImportee: string, id: string) => typeof rawImportee | void
}
🕹️ 实际操作
通过上面的介绍,业务代码可以调整为:
component: () => import(`../${component}.vue`)
当然,如果设置了别名
// vite.config.ts
resolve: {alias: {"@": resolve(__dirname, "./src")}
}
可以这么使用
component: () => import(`@/${component}.vue`)
以上就是实现动态加载路由的方案,如果你有更好的主意可以在评论区留言~