[debug日记]
[Vue Router warn]: Component “default” in record with path “/xxx” is a function that does not return a Promise. If you were passing a functional component, make sure to add a “displayName” to the component. This will break in production if not fixed.
【方案一】
一种可能的解决方法是更改你的导入组件的路径。
当使用vite时,使用了一款插件dynamic-import-vars
进行动态导入有只能使用相对路径的要求。
这些限制的中文翻译如下:
限制
要知道在汇总包中注入什么,我们必须能够对代码进行一些静态分析,并对可能的导入做出一些假设。比如当你只使用一个变量,理论上您可以从整个文件系统中导入任何内容。
function importModule(path) { // 鬼知道这里会导入什么? return import(path); }
为了帮助静态分析,并避免可能的脚枪,我们仅限于以下几条规则:
导入必须以 ./
或者 ../
开头
所有导入(import)必须使用相对被导入文件的路径。导入**不
允许以一个变量开头、使用绝对路径**,或者裸导入。
// 不允许 import(bar); import(`${bar}.js`); import(`/foo/${bar}.js`); import(`some-library/${bar}.js`);
导入必须带有一个文件扩展名
一个文件夹可能包含了你不想导入的东西。 所以我们要求在静态部分的导入以一个文件扩展名来结尾。
// 不允许 import(`./foo/${bar}`); // 允许 import(`./foo/${bar}.js`);
导入你自己的目录时必须指定一个文件名字符串模板
当你导入你的目录,你可能最终会得到你不想导入的文件,包括你自己的模块,基于这个原因要求指定一个文件名字符串模板
// 不允许 import(`./${foo}.js`); // 允许 import(`./module-${foo}.js`);
因此,虽然你在vite配置文件vite.config.ts(或js)中配置了defineConfig中的resolve.alias
字段以指定了别名,但是由于插件只允许使用相对路径,因此导致报错。
因此改成以./
或者以../
开头的相对路径就好。
【方案二】
可以给vue-router的 component
字段返回一个 Promise并将的组件作为value传入resolve。
请返照以下思路:
{path:..., ..., component:() => Promise.resolve(your_component)}
实际上遇到这种情况时更可能是在使用一个函数导入一个异步组件,比如这样:
import { defineAsyncComponent } from "vue" const AsyncComp = defineAsyncComponent(() => new Promise((resolve, reject) => { resolve(...) }) )
其中解决函数中可以直接指定一个描述VUE组件的对象。也可以导入,即:
const AsyncComp = defineAsyncComponent(() => import("./components/LoginPopup.vue"))
可以打开vue router源码的类型声明文件相关部分内容:
这里默认给出的为defineAsyncComponent第一个参数loader
,就是VUE文件的路径。更详细的请自行看这部分源码了解。
function defineAsyncComponent(source) { if (shared.isFunction(source)) { source = { loader: source }; } const { loader, loadingComponent, errorComponent, delay = 200, timeout, // undefined = never times out suspensible = true, onError: userOnError } = source; let pendingRequest = null; let resolvedComp; let retries = 0; const retry = () => { retries++; pendingRequest = null; return load(); }; const load = () => { let thisRequest; return (pendingRequest || (thisRequest = pendingRequest = loader() .catch(err => { err = err instanceof Error ? err : new Error(String(err)); if (userOnError) { return new Promise((resolve, reject) => { const userRetry = () => resolve(retry()); const userFail = () => reject(err); userOnError(err, userRetry, userFail, retries + 1); }); } else { throw err; } }) // !!! 当 Promise 为resolve 的时候, .then((comp) => { if (thisRequest !== pendingRequest && pendingRequest) { return pendingRequest; } if (!comp) { warn(`Async component loader resolved to undefined. ` + `If you are using retry(), make sure to return its return value.`); } // interop module default if (comp && (comp.__esModule || comp[Symbol.toStringTag] === 'Module')) { comp = comp.default; } if (comp && !shared.isObject(comp) && !shared.isFunction(comp)) { throw new Error(`Invalid async component load result: ${comp}`); } resolvedComp = comp; return comp; }))); }; return defineComponent({ name: 'AsyncComponentWrapper', __asyncLoader: load, get __asyncResolved() { return resolvedComp; }, setup() { const instance = currentInstance; // already resolved if (resolvedComp) { return () => createInnerComp(resolvedComp, instance); } const onError = (err) => { pendingRequest = null; handleError(err, instance, 13 /* ASYNC_COMPONENT_LOADER */, !errorComponent /* do not throw in dev if user provided error component */); }; // suspense-controlled or SSR. if ((suspensible && instance.suspense) || (isInSSRComponentSetup)) { return load() .then(comp => { return () => createInnerComp(comp, instance); }) .catch(err => { onError(err); return () => errorComponent ? createVNode(errorComponent, { error: err }) : null; }); } const loaded = reactivity.ref(false); const error = reactivity.ref(); const delayed = reactivity.ref(!!delay); if (delay) { setTimeout(() => { delayed.value = false; }, delay); } if (timeout != null) { setTimeout(() => { if (!loaded.value && !error.value) { const err = new Error(`Async component timed out after ${timeout}ms.`); onError(err); error.value = err; } }, timeout); } load() .then(() => { loaded.value = true; if (instance.parent && isKeepAlive(instance.parent.vnode)) { // parent is keep-alive, force update so the loaded component's // name is taken into account queueJob(instance.parent.update); } }) .catch(err => { onError(err); error.value = err; }); return () => { if (loaded.value && resolvedComp) { return createInnerComp(resolvedComp, instance); } else if (error.value && errorComponent) { return createVNode(errorComponent, { error: error.value }); } else if (loadingComponent && !delayed.value) { return createVNode(loadingComponent); } }; } }); }
附:标准的JavaScript动态导入是不要求相对路径的:
动态导入-摘录自MDN
标准导入语法是静态的,并且总是会在加载时评估导入模块中的所有代码。在您希望有条件地或按需加载模块的情况下,您可以改用动态导入。以下是您可能需要使用动态导入的一些原因:
- 静态导入会显着减慢代码的加载速度,并且您需要正在导入的代码的可能性很小,或者直到以后才需要它。
- 静态导入会显着增加程序的内存使用量,并且您需要导入的代码的可能性很小。
- 当您导入的模块在加载时不存在时
- 当需要动态构造导入说明符字符串时。(静态导入仅支持静态说明符。)
- 当导入的模块有副作用时,除非某些条件为真,否则您不想要这些副作用。(建议不要在模块中产生任何副作用,但有时您无法在模块依赖项中控制这一点。)
仅在必要时使用动态导入。静态形式更适合加载初始依赖项,并且可以更容易地从静态分析工具和tree shaking中受益。
要动态导入模块,import
可以将关键字作为函数调用。以这种方式使用时,它会返回一个承诺。
import('/modules/my-module.js') .then((module) => { // Do something with the module. });
复制到剪贴板
这种形式也支持await
关键字。
let module = await import('/modules/my-module.js');