From 18cf63ff05d1a884c2084cd9e68a511414221980 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 8 Apr 2021 16:11:28 -0400 Subject: [PATCH] wip: async component compat --- packages/runtime-core/src/compat/component.ts | 83 +++++++++++++++++++ .../runtime-core/src/compat/deprecations.ts | 39 ++++++++- packages/runtime-core/src/vnode.ts | 6 ++ 3 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 packages/runtime-core/src/compat/component.ts diff --git a/packages/runtime-core/src/compat/component.ts b/packages/runtime-core/src/compat/component.ts new file mode 100644 index 00000000..ca15a657 --- /dev/null +++ b/packages/runtime-core/src/compat/component.ts @@ -0,0 +1,83 @@ +import { isArray, isFunction, isObject, isPromise } from '@vue/shared/src' +import { defineAsyncComponent } from '../apiAsyncComponent' +import { Component, ComponentOptions, FunctionalComponent } from '../component' +import { isVNode } from '../vnode' +import { softAssertCompatEnabled } from './compatConfig' +import { DeprecationTypes } from './deprecations' + +export function convertLegacyComponent(comp: any): Component { + // 2.x async component + if ( + isFunction(comp) && + softAssertCompatEnabled(DeprecationTypes.COMPONENT_ASYNC, comp) + ) { + return convertLegacyAsyncComponent(comp) + } + + // 2.x functional component + if ( + isObject(comp) && + comp.functional && + softAssertCompatEnabled(DeprecationTypes.COMPONENT_FUNCTIONAL, comp) + ) { + return convertLegacyFunctionalComponent(comp) + } + + return comp +} + +interface LegacyAsyncOptions { + component: Promise + loading?: Component + error?: Component + delay?: number + timeout?: number +} + +type LegacyAsyncReturnValue = Promise | LegacyAsyncOptions + +type LegacyAsyncComponent = ( + resolve?: (res: LegacyAsyncReturnValue) => void, + reject?: (reason?: any) => void +) => LegacyAsyncReturnValue | undefined + +const normalizedAsyncComponentMap = new Map() + +function convertLegacyAsyncComponent(comp: LegacyAsyncComponent) { + if (normalizedAsyncComponentMap.has(comp)) { + return normalizedAsyncComponentMap.get(comp)! + } + + // we have to call the function here due to how v2's API won't expose the + // options until we call it + let resolve: (res: LegacyAsyncReturnValue) => void + let reject: (reason?: any) => void + const fallbackPromise = new Promise((r, rj) => { + ;(resolve = r), (reject = rj) + }) + + const res = comp(resolve!, reject!) + + let converted: Component + if (isPromise(res)) { + converted = defineAsyncComponent(() => res) + } else if (isObject(res) && !isVNode(res) && !isArray(res)) { + converted = defineAsyncComponent({ + loader: () => res.component, + loadingComponent: res.loading, + errorComponent: res.error, + delay: res.delay, + timeout: res.timeout + }) + } else if (res == null) { + converted = defineAsyncComponent(() => fallbackPromise) + } else { + converted = comp as any // probably a v3 functional comp + } + normalizedAsyncComponentMap.set(comp, converted) + return converted +} + +function convertLegacyFunctionalComponent(comp: ComponentOptions) { + return comp.render as FunctionalComponent +} diff --git a/packages/runtime-core/src/compat/deprecations.ts b/packages/runtime-core/src/compat/deprecations.ts index b34cd716..5f6e7b28 100644 --- a/packages/runtime-core/src/compat/deprecations.ts +++ b/packages/runtime-core/src/compat/deprecations.ts @@ -1,5 +1,6 @@ import { formatComponentName, + getComponentName, getCurrentInstance, isRuntimeOnly } from '../component' @@ -46,7 +47,10 @@ export const enum DeprecationTypes { ATTR_ENUMERATED_COERSION = 'ATTR_ENUMERATED_COERSION', TRANSITION_CLASSES = 'TRANSITION_CLASSES', - TRANSITION_GROUP_ROOT = 'TRANSITION_GROUP_ROOT' + TRANSITION_GROUP_ROOT = 'TRANSITION_GROUP_ROOT', + + COMPONENT_ASYNC = 'COMPONENT_ASYNC', + COMPONENT_FUNCTIONAL = 'COMPONENT_FUNCTIONAL' } type DeprecationData = { @@ -302,6 +306,39 @@ const deprecationData: Record = { DeprecationTypes.TRANSITION_GROUP_ROOT }: { enabled: false }})\n`, link: `https://v3.vuejs.org/guide/migration/transition-group.html` + }, + + [DeprecationTypes.COMPONENT_ASYNC]: { + message: (comp: any) => { + const name = getComponentName(comp) + return ( + `Async component${ + name ? ` <${name}>` : `s` + } should be explicitly created via \`defineAsyncComponent()\` ` + + `in Vue 3. Plain functions will be treated as functional components in ` + + `non-compat build.` + ) + }, + link: `https://v3.vuejs.org/guide/migration/async-components.html` + }, + + [DeprecationTypes.COMPONENT_FUNCTIONAL]: { + message: (comp: any) => { + const name = getComponentName(comp) + return ( + `Functional component${ + name ? ` <${name}>` : `s` + } should be defined as a plain function in Vue 3. The "functional" ` + + `option has been removed.\n` + + `NOTE: Before migrating, ensure that all async ` + + `components have been upgraded to use \`defineAsyncComponent()\` and ` + + `then disable compat for legacy async components with:` + + `\n\n configureCompat({ ${ + DeprecationTypes.COMPONENT_ASYNC + }: { enabled: false }})\n` + ) + }, + link: `https://v3.vuejs.org/guide/migration/functional-components.html` } } diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index e46c9842..364214de 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -41,6 +41,7 @@ import { RendererNode, RendererElement } from './renderer' import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets' import { hmrDirtyComponents } from './hmr' import { setCompiledSlotRendering } from './helpers/renderSlot' +import { convertLegacyComponent } from './compat/component' export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as { __isFragment: true @@ -358,6 +359,11 @@ function _createVNode( type = type.__vccOpts } + // 2.x async/functional component compat + if (__COMPAT__) { + type = convertLegacyComponent(type) + } + // class & style normalization. if (props) { // for reactive or proxy objects, we need to clone it to enable mutation.