Skip to content

Commit 18cf63f

Browse files
committed
wip: async component compat
1 parent d7957a7 commit 18cf63f

File tree

3 files changed

+127
-1
lines changed

3 files changed

+127
-1
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { isArray, isFunction, isObject, isPromise } from '@vue/shared/src'
2+
import { defineAsyncComponent } from '../apiAsyncComponent'
3+
import { Component, ComponentOptions, FunctionalComponent } from '../component'
4+
import { isVNode } from '../vnode'
5+
import { softAssertCompatEnabled } from './compatConfig'
6+
import { DeprecationTypes } from './deprecations'
7+
8+
export function convertLegacyComponent(comp: any): Component {
9+
// 2.x async component
10+
if (
11+
isFunction(comp) &&
12+
softAssertCompatEnabled(DeprecationTypes.COMPONENT_ASYNC, comp)
13+
) {
14+
return convertLegacyAsyncComponent(comp)
15+
}
16+
17+
// 2.x functional component
18+
if (
19+
isObject(comp) &&
20+
comp.functional &&
21+
softAssertCompatEnabled(DeprecationTypes.COMPONENT_FUNCTIONAL, comp)
22+
) {
23+
return convertLegacyFunctionalComponent(comp)
24+
}
25+
26+
return comp
27+
}
28+
29+
interface LegacyAsyncOptions {
30+
component: Promise<Component>
31+
loading?: Component
32+
error?: Component
33+
delay?: number
34+
timeout?: number
35+
}
36+
37+
type LegacyAsyncReturnValue = Promise<Component> | LegacyAsyncOptions
38+
39+
type LegacyAsyncComponent = (
40+
resolve?: (res: LegacyAsyncReturnValue) => void,
41+
reject?: (reason?: any) => void
42+
) => LegacyAsyncReturnValue | undefined
43+
44+
const normalizedAsyncComponentMap = new Map<LegacyAsyncComponent, Component>()
45+
46+
function convertLegacyAsyncComponent(comp: LegacyAsyncComponent) {
47+
if (normalizedAsyncComponentMap.has(comp)) {
48+
return normalizedAsyncComponentMap.get(comp)!
49+
}
50+
51+
// we have to call the function here due to how v2's API won't expose the
52+
// options until we call it
53+
let resolve: (res: LegacyAsyncReturnValue) => void
54+
let reject: (reason?: any) => void
55+
const fallbackPromise = new Promise<Component>((r, rj) => {
56+
;(resolve = r), (reject = rj)
57+
})
58+
59+
const res = comp(resolve!, reject!)
60+
61+
let converted: Component
62+
if (isPromise(res)) {
63+
converted = defineAsyncComponent(() => res)
64+
} else if (isObject(res) && !isVNode(res) && !isArray(res)) {
65+
converted = defineAsyncComponent({
66+
loader: () => res.component,
67+
loadingComponent: res.loading,
68+
errorComponent: res.error,
69+
delay: res.delay,
70+
timeout: res.timeout
71+
})
72+
} else if (res == null) {
73+
converted = defineAsyncComponent(() => fallbackPromise)
74+
} else {
75+
converted = comp as any // probably a v3 functional comp
76+
}
77+
normalizedAsyncComponentMap.set(comp, converted)
78+
return converted
79+
}
80+
81+
function convertLegacyFunctionalComponent(comp: ComponentOptions) {
82+
return comp.render as FunctionalComponent
83+
}

packages/runtime-core/src/compat/deprecations.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
formatComponentName,
3+
getComponentName,
34
getCurrentInstance,
45
isRuntimeOnly
56
} from '../component'
@@ -46,7 +47,10 @@ export const enum DeprecationTypes {
4647
ATTR_ENUMERATED_COERSION = 'ATTR_ENUMERATED_COERSION',
4748

4849
TRANSITION_CLASSES = 'TRANSITION_CLASSES',
49-
TRANSITION_GROUP_ROOT = 'TRANSITION_GROUP_ROOT'
50+
TRANSITION_GROUP_ROOT = 'TRANSITION_GROUP_ROOT',
51+
52+
COMPONENT_ASYNC = 'COMPONENT_ASYNC',
53+
COMPONENT_FUNCTIONAL = 'COMPONENT_FUNCTIONAL'
5054
}
5155

5256
type DeprecationData = {
@@ -302,6 +306,39 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
302306
DeprecationTypes.TRANSITION_GROUP_ROOT
303307
}: { enabled: false }})\n`,
304308
link: `https://v3.vuejs.org/guide/migration/transition-group.html`
309+
},
310+
311+
[DeprecationTypes.COMPONENT_ASYNC]: {
312+
message: (comp: any) => {
313+
const name = getComponentName(comp)
314+
return (
315+
`Async component${
316+
name ? ` <${name}>` : `s`
317+
} should be explicitly created via \`defineAsyncComponent()\` ` +
318+
`in Vue 3. Plain functions will be treated as functional components in ` +
319+
`non-compat build.`
320+
)
321+
},
322+
link: `https://v3.vuejs.org/guide/migration/async-components.html`
323+
},
324+
325+
[DeprecationTypes.COMPONENT_FUNCTIONAL]: {
326+
message: (comp: any) => {
327+
const name = getComponentName(comp)
328+
return (
329+
`Functional component${
330+
name ? ` <${name}>` : `s`
331+
} should be defined as a plain function in Vue 3. The "functional" ` +
332+
`option has been removed.\n` +
333+
`NOTE: Before migrating, ensure that all async ` +
334+
`components have been upgraded to use \`defineAsyncComponent()\` and ` +
335+
`then disable compat for legacy async components with:` +
336+
`\n\n configureCompat({ ${
337+
DeprecationTypes.COMPONENT_ASYNC
338+
}: { enabled: false }})\n`
339+
)
340+
},
341+
link: `https://v3.vuejs.org/guide/migration/functional-components.html`
305342
}
306343
}
307344

packages/runtime-core/src/vnode.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { RendererNode, RendererElement } from './renderer'
4141
import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets'
4242
import { hmrDirtyComponents } from './hmr'
4343
import { setCompiledSlotRendering } from './helpers/renderSlot'
44+
import { convertLegacyComponent } from './compat/component'
4445

4546
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
4647
__isFragment: true
@@ -358,6 +359,11 @@ function _createVNode(
358359
type = type.__vccOpts
359360
}
360361

362+
// 2.x async/functional component compat
363+
if (__COMPAT__) {
364+
type = convertLegacyComponent(type)
365+
}
366+
361367
// class & style normalization.
362368
if (props) {
363369
// for reactive or proxy objects, we need to clone it to enable mutation.

0 commit comments

Comments
 (0)