Skip to content

Commit f05d6df

Browse files
committed
wip: render function compat
1 parent 457a56e commit f05d6df

20 files changed

+419
-87
lines changed

packages/runtime-core/src/compat/__tests__/compatGlobal.spec.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import Vue from '@vue/compat'
2+
3+
describe('compat: global API', () => {
4+
test('should work', async () => {
5+
const el = document.createElement('div')
6+
el.innerHTML = `{{ msg }}`
7+
new Vue({
8+
el,
9+
data() {
10+
return {
11+
msg: 'hello'
12+
}
13+
}
14+
})
15+
expect('global app bootstrapping API has changed').toHaveBeenWarned()
16+
expect(el.innerHTML).toBe('hello')
17+
})
18+
})
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { ShapeFlags } from '@vue/shared/src'
2+
import { createComponentInstance } from '../../component'
3+
import { setCurrentRenderingInstance } from '../../componentRenderContext'
4+
import { DirectiveBinding } from '../../directives'
5+
import { createVNode } from '../../vnode'
6+
import { compatH as h } from '../renderFn'
7+
8+
describe('compat: render function', () => {
9+
const mockDir = {}
10+
const mockChildComp = {}
11+
const mockComponent = {
12+
directives: {
13+
mockDir
14+
},
15+
components: {
16+
foo: mockChildComp
17+
}
18+
}
19+
const mockInstance = createComponentInstance(
20+
createVNode(mockComponent),
21+
null,
22+
null
23+
)
24+
beforeEach(() => {
25+
setCurrentRenderingInstance(mockInstance)
26+
})
27+
afterEach(() => {
28+
setCurrentRenderingInstance(null)
29+
})
30+
31+
test('string component lookup', () => {
32+
expect(h('foo')).toMatchObject({
33+
type: mockChildComp
34+
})
35+
})
36+
37+
test('class / style / attrs / domProps / props', () => {
38+
expect(
39+
h('div', {
40+
class: 'foo',
41+
style: { color: 'red' },
42+
attrs: {
43+
id: 'foo'
44+
},
45+
domProps: {
46+
innerHTML: 'hi'
47+
},
48+
props: {
49+
myProp: 'foo'
50+
}
51+
})
52+
).toMatchObject({
53+
props: {
54+
class: 'foo',
55+
style: { color: 'red' },
56+
id: 'foo',
57+
innerHTML: 'hi',
58+
myProp: 'foo'
59+
}
60+
})
61+
})
62+
63+
test('on / nativeOn', () => {
64+
const fn = () => {}
65+
expect(
66+
h('div', {
67+
on: {
68+
click: fn,
69+
fooBar: fn
70+
},
71+
nativeOn: {
72+
click: fn,
73+
'bar-baz': fn
74+
}
75+
})
76+
).toMatchObject({
77+
props: {
78+
onClick: fn, // should dedupe
79+
onFooBar: fn,
80+
'onBar-baz': fn
81+
}
82+
})
83+
})
84+
85+
test('directives', () => {
86+
expect(
87+
h('div', {
88+
directives: [
89+
{
90+
name: 'mock-dir',
91+
value: '2',
92+
// expression: '1 + 1',
93+
arg: 'foo',
94+
modifiers: {
95+
bar: true
96+
}
97+
}
98+
]
99+
})
100+
).toMatchObject({
101+
dirs: [
102+
{
103+
dir: mockDir,
104+
instance: mockInstance.proxy,
105+
value: '2',
106+
oldValue: void 0,
107+
arg: 'foo',
108+
modifiers: {
109+
bar: true
110+
}
111+
}
112+
] as DirectiveBinding[]
113+
})
114+
})
115+
116+
test('scopedSlots', () => {
117+
const scopedSlots = {
118+
default() {}
119+
}
120+
const vnode = h(mockComponent, {
121+
scopedSlots
122+
})
123+
expect(vnode).toMatchObject({
124+
children: scopedSlots
125+
})
126+
expect('scopedSlots' in vnode.props!).toBe(false)
127+
expect(vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN).toBeTruthy()
128+
})
129+
130+
test('legacy named slot', () => {
131+
const vnode = h(mockComponent, [
132+
'text',
133+
h('div', { slot: 'foo' }, 'one'),
134+
h('div', { slot: 'bar' }, 'two'),
135+
h('div', { slot: 'foo' }, 'three'),
136+
h('div', 'four')
137+
])
138+
expect(vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN).toBeTruthy()
139+
const slots = vnode.children as any
140+
141+
// default
142+
expect(slots.default()).toMatchObject(['text', { children: 'four' }])
143+
expect(slots.foo()).toMatchObject([
144+
{ children: 'one' },
145+
{ children: 'three' }
146+
])
147+
expect(slots.bar()).toMatchObject([{ children: 'two' }])
148+
})
149+
})

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

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { extend } from '@vue/shared'
2-
import { ComponentOptions, getCurrentInstance } from '../component'
2+
import { ComponentInternalInstance, ComponentOptions } from '../component'
33
import { DeprecationTypes, warnDeprecation } from './deprecations'
44

55
export type CompatConfig = Partial<
@@ -14,8 +14,10 @@ export function configureCompat(config: CompatConfig) {
1414
extend(globalCompatConfig, config)
1515
}
1616

17-
export function getCompatConfigForKey(key: DeprecationTypes | 'MODE') {
18-
const instance = getCurrentInstance()
17+
export function getCompatConfigForKey(
18+
key: DeprecationTypes | 'MODE',
19+
instance: ComponentInternalInstance | null
20+
) {
1921
const instanceConfig =
2022
instance && (instance.type as ComponentOptions).compatConfig
2123
if (instanceConfig && key in instanceConfig) {
@@ -24,29 +26,40 @@ export function getCompatConfigForKey(key: DeprecationTypes | 'MODE') {
2426
return globalCompatConfig[key]
2527
}
2628

27-
export function isCompatEnabled(key: DeprecationTypes): boolean {
28-
const mode = getCompatConfigForKey('MODE') || 2
29-
const val = getCompatConfigForKey(key)
29+
export function isCompatEnabled(
30+
key: DeprecationTypes,
31+
instance: ComponentInternalInstance | null
32+
): boolean {
33+
const mode = getCompatConfigForKey('MODE', instance) || 2
34+
const val = getCompatConfigForKey(key, instance)
3035
if (mode === 2) {
3136
return val !== false
3237
} else {
3338
return val === true || val === 'suppress-warning'
3439
}
3540
}
3641

37-
export function assertCompatEnabled(key: DeprecationTypes, ...args: any[]) {
38-
if (!isCompatEnabled(key)) {
42+
export function assertCompatEnabled(
43+
key: DeprecationTypes,
44+
instance: ComponentInternalInstance | null,
45+
...args: any[]
46+
) {
47+
if (!isCompatEnabled(key, instance)) {
3948
throw new Error(`${key} compat has been disabled.`)
4049
} else if (__DEV__) {
41-
warnDeprecation(key, ...args)
50+
warnDeprecation(key, instance, ...args)
4251
}
4352
}
4453

45-
export function softAssertCompatEnabled(key: DeprecationTypes, ...args: any[]) {
54+
export function softAssertCompatEnabled(
55+
key: DeprecationTypes,
56+
instance: ComponentInternalInstance | null,
57+
...args: any[]
58+
) {
4659
if (__DEV__) {
47-
warnDeprecation(key, ...args)
60+
warnDeprecation(key, instance, ...args)
4861
}
49-
return isCompatEnabled(key)
62+
return isCompatEnabled(key, instance)
5063
}
5164

5265
// disable features that conflict with v3 behavior

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { isArray, isFunction, isObject, isPromise } from '@vue/shared'
22
import { defineAsyncComponent } from '../apiAsyncComponent'
33
import {
44
Component,
5+
ComponentInternalInstance,
56
ComponentOptions,
67
FunctionalComponent,
78
getCurrentInstance
@@ -14,20 +15,30 @@ import { DeprecationTypes, warnDeprecation } from './deprecations'
1415
import { getCompatListeners } from './instanceListeners'
1516
import { compatH } from './renderFn'
1617

17-
export function convertLegacyComponent(comp: any): Component {
18+
export function convertLegacyComponent(
19+
comp: any,
20+
instance: ComponentInternalInstance | null
21+
): Component {
1822
// 2.x async component
1923
// since after disabling this, plain functions are still valid usage, do not
2024
// use softAssert here.
21-
if (isFunction(comp) && isCompatEnabled(DeprecationTypes.COMPONENT_ASYNC)) {
22-
__DEV__ && warnDeprecation(DeprecationTypes.COMPONENT_ASYNC, comp)
25+
if (
26+
isFunction(comp) &&
27+
isCompatEnabled(DeprecationTypes.COMPONENT_ASYNC, instance)
28+
) {
29+
__DEV__ && warnDeprecation(DeprecationTypes.COMPONENT_ASYNC, instance, comp)
2330
return convertLegacyAsyncComponent(comp)
2431
}
2532

2633
// 2.x functional component
2734
if (
2835
isObject(comp) &&
2936
comp.functional &&
30-
softAssertCompatEnabled(DeprecationTypes.COMPONENT_FUNCTIONAL, comp)
37+
softAssertCompatEnabled(
38+
DeprecationTypes.COMPONENT_FUNCTIONAL,
39+
instance,
40+
comp
41+
)
3142
) {
3243
return convertLegacyFunctionalComponent(comp)
3344
}
@@ -92,7 +103,7 @@ const normalizedFunctionalComponentMap = new Map<
92103
FunctionalComponent
93104
>()
94105

95-
const legacySlotProxyHandlers: ProxyHandler<InternalSlots> = {
106+
export const legacySlotProxyHandlers: ProxyHandler<InternalSlots> = {
96107
get(target, key: string) {
97108
const slot = target[key]
98109
return slot && slot()

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { isArray } from '@vue/shared'
2+
import { ComponentInternalInstance } from '../component'
23
import { ObjectDirective, DirectiveHook } from '../directives'
34
import { softAssertCompatEnabled } from './compatConfig'
45
import { DeprecationTypes } from './deprecations'
@@ -25,7 +26,8 @@ const legacyDirectiveHookMap: Partial<
2526

2627
export function mapCompatDirectiveHook(
2728
name: keyof ObjectDirective,
28-
dir: ObjectDirective & LegacyDirective
29+
dir: ObjectDirective & LegacyDirective,
30+
instance: ComponentInternalInstance | null
2931
): DirectiveHook | DirectiveHook[] | undefined {
3032
const mappedName = legacyDirectiveHookMap[name]
3133
if (mappedName) {
@@ -34,14 +36,24 @@ export function mapCompatDirectiveHook(
3436
mappedName.forEach(name => {
3537
const mappedHook = dir[name]
3638
if (mappedHook) {
37-
softAssertCompatEnabled(DeprecationTypes.CUSTOM_DIR, mappedName, name)
39+
softAssertCompatEnabled(
40+
DeprecationTypes.CUSTOM_DIR,
41+
instance,
42+
mappedName,
43+
name
44+
)
3845
hook.push(mappedHook)
3946
}
4047
})
4148
return hook.length ? hook : undefined
4249
} else {
4350
if (dir[mappedName]) {
44-
softAssertCompatEnabled(DeprecationTypes.CUSTOM_DIR, mappedName, name)
51+
softAssertCompatEnabled(
52+
DeprecationTypes.CUSTOM_DIR,
53+
instance,
54+
mappedName,
55+
name
56+
)
4557
}
4658
return dir[mappedName]
4759
}

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import { isPlainObject } from '@vue/shared'
2+
import { ComponentInternalInstance } from '../component'
23
import { DeprecationTypes, warnDeprecation } from './deprecations'
34

4-
export function deepMergeData(to: any, from: any) {
5+
export function deepMergeData(
6+
to: any,
7+
from: any,
8+
instance: ComponentInternalInstance
9+
) {
510
for (const key in from) {
611
const toVal = to[key]
712
const fromVal = from[key]
813
if (key in to && isPlainObject(toVal) && isPlainObject(fromVal)) {
9-
__DEV__ && warnDeprecation(DeprecationTypes.OPTIONS_DATA_MERGE, key)
10-
deepMergeData(toVal, fromVal)
14+
__DEV__ &&
15+
warnDeprecation(DeprecationTypes.OPTIONS_DATA_MERGE, instance, key)
16+
deepMergeData(toVal, fromVal, instance)
1117
} else {
1218
to[key] = fromVal
1319
}

0 commit comments

Comments
 (0)