wip: render function compat

This commit is contained in:
Evan You 2021-04-09 18:52:14 -04:00
parent 457a56e331
commit f05d6dfd98
20 changed files with 419 additions and 87 deletions

View File

@ -1,16 +0,0 @@
import Vue from '@vue/compat'
test('should work', async () => {
const el = document.createElement('div')
el.innerHTML = `{{ msg }}`
new Vue({
el,
data() {
return {
msg: 'hello'
}
}
})
expect('global app bootstrapping API has changed').toHaveBeenWarned()
expect(el.innerHTML).toBe('hello')
})

View File

@ -0,0 +1,18 @@
import Vue from '@vue/compat'
describe('compat: global API', () => {
test('should work', async () => {
const el = document.createElement('div')
el.innerHTML = `{{ msg }}`
new Vue({
el,
data() {
return {
msg: 'hello'
}
}
})
expect('global app bootstrapping API has changed').toHaveBeenWarned()
expect(el.innerHTML).toBe('hello')
})
})

View File

@ -0,0 +1,149 @@
import { ShapeFlags } from '@vue/shared/src'
import { createComponentInstance } from '../../component'
import { setCurrentRenderingInstance } from '../../componentRenderContext'
import { DirectiveBinding } from '../../directives'
import { createVNode } from '../../vnode'
import { compatH as h } from '../renderFn'
describe('compat: render function', () => {
const mockDir = {}
const mockChildComp = {}
const mockComponent = {
directives: {
mockDir
},
components: {
foo: mockChildComp
}
}
const mockInstance = createComponentInstance(
createVNode(mockComponent),
null,
null
)
beforeEach(() => {
setCurrentRenderingInstance(mockInstance)
})
afterEach(() => {
setCurrentRenderingInstance(null)
})
test('string component lookup', () => {
expect(h('foo')).toMatchObject({
type: mockChildComp
})
})
test('class / style / attrs / domProps / props', () => {
expect(
h('div', {
class: 'foo',
style: { color: 'red' },
attrs: {
id: 'foo'
},
domProps: {
innerHTML: 'hi'
},
props: {
myProp: 'foo'
}
})
).toMatchObject({
props: {
class: 'foo',
style: { color: 'red' },
id: 'foo',
innerHTML: 'hi',
myProp: 'foo'
}
})
})
test('on / nativeOn', () => {
const fn = () => {}
expect(
h('div', {
on: {
click: fn,
fooBar: fn
},
nativeOn: {
click: fn,
'bar-baz': fn
}
})
).toMatchObject({
props: {
onClick: fn, // should dedupe
onFooBar: fn,
'onBar-baz': fn
}
})
})
test('directives', () => {
expect(
h('div', {
directives: [
{
name: 'mock-dir',
value: '2',
// expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
]
})
).toMatchObject({
dirs: [
{
dir: mockDir,
instance: mockInstance.proxy,
value: '2',
oldValue: void 0,
arg: 'foo',
modifiers: {
bar: true
}
}
] as DirectiveBinding[]
})
})
test('scopedSlots', () => {
const scopedSlots = {
default() {}
}
const vnode = h(mockComponent, {
scopedSlots
})
expect(vnode).toMatchObject({
children: scopedSlots
})
expect('scopedSlots' in vnode.props!).toBe(false)
expect(vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN).toBeTruthy()
})
test('legacy named slot', () => {
const vnode = h(mockComponent, [
'text',
h('div', { slot: 'foo' }, 'one'),
h('div', { slot: 'bar' }, 'two'),
h('div', { slot: 'foo' }, 'three'),
h('div', 'four')
])
expect(vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN).toBeTruthy()
const slots = vnode.children as any
// default
expect(slots.default()).toMatchObject(['text', { children: 'four' }])
expect(slots.foo()).toMatchObject([
{ children: 'one' },
{ children: 'three' }
])
expect(slots.bar()).toMatchObject([{ children: 'two' }])
})
})

View File

@ -1,5 +1,5 @@
import { extend } from '@vue/shared' import { extend } from '@vue/shared'
import { ComponentOptions, getCurrentInstance } from '../component' import { ComponentInternalInstance, ComponentOptions } from '../component'
import { DeprecationTypes, warnDeprecation } from './deprecations' import { DeprecationTypes, warnDeprecation } from './deprecations'
export type CompatConfig = Partial< export type CompatConfig = Partial<
@ -14,8 +14,10 @@ export function configureCompat(config: CompatConfig) {
extend(globalCompatConfig, config) extend(globalCompatConfig, config)
} }
export function getCompatConfigForKey(key: DeprecationTypes | 'MODE') { export function getCompatConfigForKey(
const instance = getCurrentInstance() key: DeprecationTypes | 'MODE',
instance: ComponentInternalInstance | null
) {
const instanceConfig = const instanceConfig =
instance && (instance.type as ComponentOptions).compatConfig instance && (instance.type as ComponentOptions).compatConfig
if (instanceConfig && key in instanceConfig) { if (instanceConfig && key in instanceConfig) {
@ -24,9 +26,12 @@ export function getCompatConfigForKey(key: DeprecationTypes | 'MODE') {
return globalCompatConfig[key] return globalCompatConfig[key]
} }
export function isCompatEnabled(key: DeprecationTypes): boolean { export function isCompatEnabled(
const mode = getCompatConfigForKey('MODE') || 2 key: DeprecationTypes,
const val = getCompatConfigForKey(key) instance: ComponentInternalInstance | null
): boolean {
const mode = getCompatConfigForKey('MODE', instance) || 2
const val = getCompatConfigForKey(key, instance)
if (mode === 2) { if (mode === 2) {
return val !== false return val !== false
} else { } else {
@ -34,19 +39,27 @@ export function isCompatEnabled(key: DeprecationTypes): boolean {
} }
} }
export function assertCompatEnabled(key: DeprecationTypes, ...args: any[]) { export function assertCompatEnabled(
if (!isCompatEnabled(key)) { key: DeprecationTypes,
instance: ComponentInternalInstance | null,
...args: any[]
) {
if (!isCompatEnabled(key, instance)) {
throw new Error(`${key} compat has been disabled.`) throw new Error(`${key} compat has been disabled.`)
} else if (__DEV__) { } else if (__DEV__) {
warnDeprecation(key, ...args) warnDeprecation(key, instance, ...args)
} }
} }
export function softAssertCompatEnabled(key: DeprecationTypes, ...args: any[]) { export function softAssertCompatEnabled(
key: DeprecationTypes,
instance: ComponentInternalInstance | null,
...args: any[]
) {
if (__DEV__) { if (__DEV__) {
warnDeprecation(key, ...args) warnDeprecation(key, instance, ...args)
} }
return isCompatEnabled(key) return isCompatEnabled(key, instance)
} }
// disable features that conflict with v3 behavior // disable features that conflict with v3 behavior

View File

@ -2,6 +2,7 @@ import { isArray, isFunction, isObject, isPromise } from '@vue/shared'
import { defineAsyncComponent } from '../apiAsyncComponent' import { defineAsyncComponent } from '../apiAsyncComponent'
import { import {
Component, Component,
ComponentInternalInstance,
ComponentOptions, ComponentOptions,
FunctionalComponent, FunctionalComponent,
getCurrentInstance getCurrentInstance
@ -14,12 +15,18 @@ import { DeprecationTypes, warnDeprecation } from './deprecations'
import { getCompatListeners } from './instanceListeners' import { getCompatListeners } from './instanceListeners'
import { compatH } from './renderFn' import { compatH } from './renderFn'
export function convertLegacyComponent(comp: any): Component { export function convertLegacyComponent(
comp: any,
instance: ComponentInternalInstance | null
): Component {
// 2.x async component // 2.x async component
// since after disabling this, plain functions are still valid usage, do not // since after disabling this, plain functions are still valid usage, do not
// use softAssert here. // use softAssert here.
if (isFunction(comp) && isCompatEnabled(DeprecationTypes.COMPONENT_ASYNC)) { if (
__DEV__ && warnDeprecation(DeprecationTypes.COMPONENT_ASYNC, comp) isFunction(comp) &&
isCompatEnabled(DeprecationTypes.COMPONENT_ASYNC, instance)
) {
__DEV__ && warnDeprecation(DeprecationTypes.COMPONENT_ASYNC, instance, comp)
return convertLegacyAsyncComponent(comp) return convertLegacyAsyncComponent(comp)
} }
@ -27,7 +34,11 @@ export function convertLegacyComponent(comp: any): Component {
if ( if (
isObject(comp) && isObject(comp) &&
comp.functional && comp.functional &&
softAssertCompatEnabled(DeprecationTypes.COMPONENT_FUNCTIONAL, comp) softAssertCompatEnabled(
DeprecationTypes.COMPONENT_FUNCTIONAL,
instance,
comp
)
) { ) {
return convertLegacyFunctionalComponent(comp) return convertLegacyFunctionalComponent(comp)
} }
@ -92,7 +103,7 @@ const normalizedFunctionalComponentMap = new Map<
FunctionalComponent FunctionalComponent
>() >()
const legacySlotProxyHandlers: ProxyHandler<InternalSlots> = { export const legacySlotProxyHandlers: ProxyHandler<InternalSlots> = {
get(target, key: string) { get(target, key: string) {
const slot = target[key] const slot = target[key]
return slot && slot() return slot && slot()

View File

@ -1,4 +1,5 @@
import { isArray } from '@vue/shared' import { isArray } from '@vue/shared'
import { ComponentInternalInstance } from '../component'
import { ObjectDirective, DirectiveHook } from '../directives' import { ObjectDirective, DirectiveHook } from '../directives'
import { softAssertCompatEnabled } from './compatConfig' import { softAssertCompatEnabled } from './compatConfig'
import { DeprecationTypes } from './deprecations' import { DeprecationTypes } from './deprecations'
@ -25,7 +26,8 @@ const legacyDirectiveHookMap: Partial<
export function mapCompatDirectiveHook( export function mapCompatDirectiveHook(
name: keyof ObjectDirective, name: keyof ObjectDirective,
dir: ObjectDirective & LegacyDirective dir: ObjectDirective & LegacyDirective,
instance: ComponentInternalInstance | null
): DirectiveHook | DirectiveHook[] | undefined { ): DirectiveHook | DirectiveHook[] | undefined {
const mappedName = legacyDirectiveHookMap[name] const mappedName = legacyDirectiveHookMap[name]
if (mappedName) { if (mappedName) {
@ -34,14 +36,24 @@ export function mapCompatDirectiveHook(
mappedName.forEach(name => { mappedName.forEach(name => {
const mappedHook = dir[name] const mappedHook = dir[name]
if (mappedHook) { if (mappedHook) {
softAssertCompatEnabled(DeprecationTypes.CUSTOM_DIR, mappedName, name) softAssertCompatEnabled(
DeprecationTypes.CUSTOM_DIR,
instance,
mappedName,
name
)
hook.push(mappedHook) hook.push(mappedHook)
} }
}) })
return hook.length ? hook : undefined return hook.length ? hook : undefined
} else { } else {
if (dir[mappedName]) { if (dir[mappedName]) {
softAssertCompatEnabled(DeprecationTypes.CUSTOM_DIR, mappedName, name) softAssertCompatEnabled(
DeprecationTypes.CUSTOM_DIR,
instance,
mappedName,
name
)
} }
return dir[mappedName] return dir[mappedName]
} }

View File

@ -1,13 +1,19 @@
import { isPlainObject } from '@vue/shared' import { isPlainObject } from '@vue/shared'
import { ComponentInternalInstance } from '../component'
import { DeprecationTypes, warnDeprecation } from './deprecations' import { DeprecationTypes, warnDeprecation } from './deprecations'
export function deepMergeData(to: any, from: any) { export function deepMergeData(
to: any,
from: any,
instance: ComponentInternalInstance
) {
for (const key in from) { for (const key in from) {
const toVal = to[key] const toVal = to[key]
const fromVal = from[key] const fromVal = from[key]
if (key in to && isPlainObject(toVal) && isPlainObject(fromVal)) { if (key in to && isPlainObject(toVal) && isPlainObject(fromVal)) {
__DEV__ && warnDeprecation(DeprecationTypes.OPTIONS_DATA_MERGE, key) __DEV__ &&
deepMergeData(toVal, fromVal) warnDeprecation(DeprecationTypes.OPTIONS_DATA_MERGE, instance, key)
deepMergeData(toVal, fromVal, instance)
} else { } else {
to[key] = fromVal to[key] = fromVal
} }

View File

@ -1,4 +1,5 @@
import { import {
ComponentInternalInstance,
formatComponentName, formatComponentName,
getComponentName, getComponentName,
getCurrentInstance, getCurrentInstance,
@ -50,7 +51,9 @@ export const enum DeprecationTypes {
TRANSITION_GROUP_ROOT = 'TRANSITION_GROUP_ROOT', TRANSITION_GROUP_ROOT = 'TRANSITION_GROUP_ROOT',
COMPONENT_ASYNC = 'COMPONENT_ASYNC', COMPONENT_ASYNC = 'COMPONENT_ASYNC',
COMPONENT_FUNCTIONAL = 'COMPONENT_FUNCTIONAL' COMPONENT_FUNCTIONAL = 'COMPONENT_FUNCTIONAL',
RENDER_FUNCTION = 'RENDER_FUNCTION'
} }
type DeprecationData = { type DeprecationData = {
@ -340,25 +343,41 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
) )
}, },
link: `https://v3.vuejs.org/guide/migration/functional-components.html` link: `https://v3.vuejs.org/guide/migration/functional-components.html`
},
[DeprecationTypes.RENDER_FUNCTION]: {
message:
`Vue 3's render function API has changed. ` +
`You can opt-in to the new API with:` +
`\n\n configureCompat({ ${
DeprecationTypes.RENDER_FUNCTION
}: false })\n` +
`\n (This can also be done per-component via the "compatConfig" option.)`,
link: `https://v3.vuejs.org/guide/migration/render-function-api.html`
} }
} }
const instanceWarned: Record<string, true> = Object.create(null) const instanceWarned: Record<string, true> = Object.create(null)
const warnCount: Record<string, number> = Object.create(null) const warnCount: Record<string, number> = Object.create(null)
export function warnDeprecation(key: DeprecationTypes, ...args: any[]) { export function warnDeprecation(
key: DeprecationTypes,
instance: ComponentInternalInstance | null,
...args: any[]
) {
if (!__DEV__) { if (!__DEV__) {
return return
} }
instance = instance || getCurrentInstance()
// check user config // check user config
const config = getCompatConfigForKey(key) const config = getCompatConfigForKey(key, instance)
if (config === 'suppress-warning') { if (config === 'suppress-warning') {
return return
} }
const dupKey = key + args.join('') const dupKey = key + args.join('')
const instance = getCurrentInstance()
const compName = instance && formatComponentName(instance, instance.type) const compName = instance && formatComponentName(instance, instance.type)
// skip if the same warning is emitted for the same component type // skip if the same warning is emitted for the same component type
@ -385,7 +404,7 @@ export function warnDeprecation(key: DeprecationTypes, ...args: any[]) {
typeof message === 'function' ? message(...args) : message typeof message === 'function' ? message(...args) : message
}${link ? `\n Details: ${link}` : ``}` }${link ? `\n Details: ${link}` : ``}`
) )
if (!isCompatEnabled(key)) { if (!isCompatEnabled(key, instance)) {
console.error( console.error(
`^ The above deprecation's compat behavior is disabled and will likely ` + `^ The above deprecation's compat behavior is disabled and will likely ` +
`lead to runtime errors.` `lead to runtime errors.`

View File

@ -96,13 +96,13 @@ export function createCompatVue(
const singletonApp = createApp({}) const singletonApp = createApp({})
function createCompatApp(options: ComponentOptions = {}, Ctor: any) { function createCompatApp(options: ComponentOptions = {}, Ctor: any) {
assertCompatEnabled(DeprecationTypes.GLOBAL_MOUNT) assertCompatEnabled(DeprecationTypes.GLOBAL_MOUNT, null)
const { data } = options const { data } = options
if ( if (
data && data &&
!isFunction(data) && !isFunction(data) &&
softAssertCompatEnabled(DeprecationTypes.OPTIONS_DATA_FN) softAssertCompatEnabled(DeprecationTypes.OPTIONS_DATA_FN, null)
) { ) {
options.data = () => data options.data = () => data
} }
@ -130,7 +130,8 @@ export function createCompatVue(
// copy prototype augmentations as config.globalProperties // copy prototype augmentations as config.globalProperties
const isPrototypeEnabled = isCompatEnabled( const isPrototypeEnabled = isCompatEnabled(
DeprecationTypes.GLOBAL_PROTOTYPE DeprecationTypes.GLOBAL_PROTOTYPE,
null
) )
let hasPrototypeAugmentations = false let hasPrototypeAugmentations = false
for (const key in Ctor.prototype) { for (const key in Ctor.prototype) {
@ -142,7 +143,7 @@ export function createCompatVue(
} }
} }
if (__DEV__ && hasPrototypeAugmentations) { if (__DEV__ && hasPrototypeAugmentations) {
warnDeprecation(DeprecationTypes.GLOBAL_PROTOTYPE) warnDeprecation(DeprecationTypes.GLOBAL_PROTOTYPE, null)
} }
const vm = app._createRoot!(options) const vm = app._createRoot!(options)
@ -158,7 +159,7 @@ export function createCompatVue(
Vue.nextTick = nextTick Vue.nextTick = nextTick
Vue.extend = ((options: ComponentOptions = {}) => { Vue.extend = ((options: ComponentOptions = {}) => {
assertCompatEnabled(DeprecationTypes.GLOBAL_EXTEND) assertCompatEnabled(DeprecationTypes.GLOBAL_EXTEND, null)
function SubVue(inlineOptions?: ComponentOptions) { function SubVue(inlineOptions?: ComponentOptions) {
if (!inlineOptions) { if (!inlineOptions) {
@ -180,17 +181,17 @@ export function createCompatVue(
}) as any }) as any
Vue.set = (target, key, value) => { Vue.set = (target, key, value) => {
assertCompatEnabled(DeprecationTypes.GLOBAL_SET) assertCompatEnabled(DeprecationTypes.GLOBAL_SET, null)
target[key] = value target[key] = value
} }
Vue.delete = (target, key) => { Vue.delete = (target, key) => {
assertCompatEnabled(DeprecationTypes.GLOBAL_DELETE) assertCompatEnabled(DeprecationTypes.GLOBAL_DELETE, null)
delete target[key] delete target[key]
} }
Vue.observable = (target: any) => { Vue.observable = (target: any) => {
assertCompatEnabled(DeprecationTypes.GLOBAL_OBSERVABLE) assertCompatEnabled(DeprecationTypes.GLOBAL_OBSERVABLE, null)
return reactive(target) return reactive(target)
} }
@ -320,7 +321,7 @@ export function installCompatMount(
for (let i = 0; i < container.attributes.length; i++) { for (let i = 0; i < container.attributes.length; i++) {
const attr = container.attributes[i] const attr = container.attributes[i]
if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) { if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
warnDeprecation(DeprecationTypes.GLOBAL_MOUNT_CONTAINER) warnDeprecation(DeprecationTypes.GLOBAL_MOUNT_CONTAINER, null)
break break
} }
} }

View File

@ -52,14 +52,14 @@ export function installLegacyConfigTraps(config: AppConfig) {
}, },
set(newVal) { set(newVal) {
if (!isCopyingConfig) { if (!isCopyingConfig) {
warnDeprecation(legacyConfigOptions[key]) warnDeprecation(legacyConfigOptions[key], null)
} }
val = newVal val = newVal
// compat for runtime ignoredElements -> isCustomElement // compat for runtime ignoredElements -> isCustomElement
if ( if (
key === 'ignoredElements' && key === 'ignoredElements' &&
isCompatEnabled(DeprecationTypes.CONFIG_IGNORED_ELEMENTS) && isCompatEnabled(DeprecationTypes.CONFIG_IGNORED_ELEMENTS, null) &&
!isRuntimeOnly() && !isRuntimeOnly() &&
isArray(newVal) isArray(newVal)
) { ) {

View File

@ -1,11 +1,12 @@
import { extend, NOOP } from '@vue/shared' import { extend, NOOP } from '@vue/shared'
import { PublicPropertiesMap } from '../componentPublicInstance' import { PublicPropertiesMap } from '../componentPublicInstance'
import { getCompatChildren } from './instanceChildren' import { getCompatChildren } from './instanceChildren'
import { assertCompatEnabled } from './compatConfig' import { assertCompatEnabled, isCompatEnabled } from './compatConfig'
import { DeprecationTypes, warnDeprecation } from './deprecations' import { DeprecationTypes, warnDeprecation } from './deprecations'
import { off, on, once } from './instanceEventEmitter' import { off, on, once } from './instanceEventEmitter'
import { getCompatListeners } from './instanceListeners' import { getCompatListeners } from './instanceListeners'
import { shallowReadonly } from '@vue/reactivity' import { shallowReadonly } from '@vue/reactivity'
import { legacySlotProxyHandlers } from './component'
export function installCompatInstanceProperties(map: PublicPropertiesMap) { export function installCompatInstanceProperties(map: PublicPropertiesMap) {
const set = (target: any, key: any, val: any) => { const set = (target: any, key: any, val: any) => {
@ -17,37 +18,48 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
} }
extend(map, { extend(map, {
$set: () => { $set: i => {
assertCompatEnabled(DeprecationTypes.INSTANCE_SET) assertCompatEnabled(DeprecationTypes.INSTANCE_SET, i)
return set return set
}, },
$delete: () => { $delete: i => {
assertCompatEnabled(DeprecationTypes.INSTANCE_DELETE) assertCompatEnabled(DeprecationTypes.INSTANCE_DELETE, i)
return del return del
}, },
$mount: i => { $mount: i => {
assertCompatEnabled(DeprecationTypes.GLOBAL_MOUNT) assertCompatEnabled(
DeprecationTypes.GLOBAL_MOUNT,
null /* this warning is global */
)
// root mount override from ./global.ts in installCompatMount // root mount override from ./global.ts in installCompatMount
return i.ctx._compat_mount || NOOP return i.ctx._compat_mount || NOOP
}, },
$destroy: i => { $destroy: i => {
assertCompatEnabled(DeprecationTypes.INSTANCE_DESTROY) assertCompatEnabled(DeprecationTypes.INSTANCE_DESTROY, i)
// root destroy override from ./global.ts in installCompatMount // root destroy override from ./global.ts in installCompatMount
return i.ctx._compat_destroy || NOOP return i.ctx._compat_destroy || NOOP
}, },
// overrides existing accessor
$slots: i => {
if (isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, i)) {
return new Proxy(i.slots, legacySlotProxyHandlers)
}
return i.slots
},
$scopedSlots: i => { $scopedSlots: i => {
assertCompatEnabled(DeprecationTypes.INSTANCE_SCOPED_SLOTS) assertCompatEnabled(DeprecationTypes.INSTANCE_SCOPED_SLOTS, i)
return __DEV__ ? shallowReadonly(i.slots) : i.slots return __DEV__ ? shallowReadonly(i.slots) : i.slots
}, },
// overrides existing accessor // overrides existing accessor
$attrs: i => { $attrs: i => {
if (__DEV__ && i.type.inheritAttrs === false) { if (__DEV__ && i.type.inheritAttrs === false) {
warnDeprecation(DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE) warnDeprecation(DeprecationTypes.INSTANCE_ATTRS_CLASS_STYLE, i)
} }
return __DEV__ ? shallowReadonly(i.attrs) : i.attrs return __DEV__ ? shallowReadonly(i.attrs) : i.attrs
}, },

View File

@ -8,7 +8,7 @@ import { DeprecationTypes } from './deprecations'
export function getCompatChildren( export function getCompatChildren(
instance: ComponentInternalInstance instance: ComponentInternalInstance
): ComponentPublicInstance[] { ): ComponentPublicInstance[] {
assertCompatEnabled(DeprecationTypes.INSTANCE_CHILDREN) assertCompatEnabled(DeprecationTypes.INSTANCE_CHILDREN, instance)
const root = instance.subTree const root = instance.subTree
const children: ComponentPublicInstance[] = [] const children: ComponentPublicInstance[] = []
if (root) { if (root) {

View File

@ -32,9 +32,9 @@ export function on(
event.forEach(e => on(instance, e, fn)) event.forEach(e => on(instance, e, fn))
} else { } else {
if (event.startsWith('hook:')) { if (event.startsWith('hook:')) {
assertCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS) assertCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
} else { } else {
assertCompatEnabled(DeprecationTypes.INSTANCE_EVENT_EMITTER) assertCompatEnabled(DeprecationTypes.INSTANCE_EVENT_EMITTER, instance)
} }
const events = getRegistry(instance) const events = getRegistry(instance)
;(events[event] || (events[event] = [])).push(fn) ;(events[event] || (events[event] = [])).push(fn)
@ -61,7 +61,7 @@ export function off(
event?: string, event?: string,
fn?: Function fn?: Function
) { ) {
assertCompatEnabled(DeprecationTypes.INSTANCE_EVENT_EMITTER) assertCompatEnabled(DeprecationTypes.INSTANCE_EVENT_EMITTER, instance)
const vm = instance.proxy const vm = instance.proxy
// all // all
if (!arguments.length) { if (!arguments.length) {

View File

@ -4,7 +4,7 @@ import { assertCompatEnabled } from './compatConfig'
import { DeprecationTypes } from './deprecations' import { DeprecationTypes } from './deprecations'
export function getCompatListeners(instance: ComponentInternalInstance) { export function getCompatListeners(instance: ComponentInternalInstance) {
assertCompatEnabled(DeprecationTypes.INSTANCE_LISTENERS) assertCompatEnabled(DeprecationTypes.INSTANCE_LISTENERS, instance)
const listeners: Record<string, Function | Function[]> = {} const listeners: Record<string, Function | Function[]> = {}
const rawProps = instance.vnode.props const rawProps = instance.vnode.props

View File

@ -5,7 +5,7 @@ export function createPropsDefaultThis(propKey: string) {
{}, {},
{ {
get() { get() {
warnDeprecation(DeprecationTypes.PROPS_DEFAULT_THIS, propKey) warnDeprecation(DeprecationTypes.PROPS_DEFAULT_THIS, null, propKey)
} }
} }
) )

View File

@ -1,8 +1,20 @@
import { isArray, isObject } from '@vue/shared' import {
extend,
isArray,
isObject,
ShapeFlags,
toHandlerKey
} from '@vue/shared'
import { Component, Data } from '../component' import { Component, Data } from '../component'
import { DirectiveArguments, withDirectives } from '../directives'
import {
resolveDirective,
resolveDynamicComponent
} from '../helpers/resolveAssets'
import { import {
createVNode, createVNode,
isVNode, isVNode,
normalizeChildren,
VNode, VNode,
VNodeArrayChildren, VNodeArrayChildren,
VNodeProps VNodeProps
@ -23,6 +35,8 @@ interface LegacyVNodeProps {
nativeOn?: Record<string, Function | Function[]> nativeOn?: Record<string, Function | Function[]>
directives?: LegacyVNodeDirective[] directives?: LegacyVNodeDirective[]
// component only
props?: Record<string, unknown>
slot?: string slot?: string
scopedSlots?: Record<string, Function> scopedSlots?: Record<string, Function>
} }
@ -56,6 +70,9 @@ export function compatH(
propsOrChildren?: any, propsOrChildren?: any,
children?: any children?: any
): VNode { ): VNode {
// to support v2 string component name lookup
type = resolveDynamicComponent(type)
const l = arguments.length const l = arguments.length
if (l === 2) { if (l === 2) {
if (isObject(propsOrChildren) && !isArray(propsOrChildren)) { if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
@ -64,10 +81,12 @@ export function compatH(
return convertLegacySlots(createVNode(type, null, [propsOrChildren])) return convertLegacySlots(createVNode(type, null, [propsOrChildren]))
} }
// props without children // props without children
return convertLegacyDirectives( return convertLegacySlots(
convertLegacyDirectives(
createVNode(type, convertLegacyProps(propsOrChildren)), createVNode(type, convertLegacyProps(propsOrChildren)),
propsOrChildren propsOrChildren
) )
)
} else { } else {
// omit props // omit props
return convertLegacySlots(createVNode(type, null, propsOrChildren)) return convertLegacySlots(createVNode(type, null, propsOrChildren))
@ -87,17 +106,87 @@ export function compatH(
} }
} }
function convertLegacyProps(props: LegacyVNodeProps): Data & VNodeProps { function convertLegacyProps(legacyProps?: LegacyVNodeProps): Data & VNodeProps {
// TODO const converted: Data & VNodeProps = {}
return props as any
for (const key in legacyProps) {
if (key === 'attrs' || key === 'domProps' || key === 'props') {
extend(converted, legacyProps[key])
} else if (key === 'on' || key === 'nativeOn') {
const listeners = legacyProps[key]
for (const event in listeners) {
const handlerKey = toHandlerKey(event)
const existing = converted[handlerKey]
const incoming = listeners[event]
if (existing !== incoming) {
converted[handlerKey] = existing
? [].concat(existing as any, incoming as any)
: incoming
}
}
} else {
converted[key] = legacyProps[key as keyof LegacyVNodeProps]
}
}
return converted
} }
function convertLegacyDirectives(vnode: VNode, props: LegacyVNodeProps): VNode { function convertLegacyDirectives(
// TODO vnode: VNode,
props?: LegacyVNodeProps
): VNode {
if (props && props.directives) {
return withDirectives(
vnode,
props.directives.map(({ name, value, arg, modifiers }) => {
return [
resolveDirective(name)!,
value,
arg,
modifiers
] as DirectiveArguments[number]
})
)
}
return vnode return vnode
} }
function convertLegacySlots(vnode: VNode): VNode { function convertLegacySlots(vnode: VNode): VNode {
// TODO const { props, children } = vnode
let slots: Record<string, any> | undefined
if (vnode.shapeFlag & ShapeFlags.COMPONENT && isArray(children)) {
slots = {}
// check "slot" property on vnodes and turn them into v3 function slots
for (let i = 0; i < children.length; i++) {
const child = children[i]
const slotName =
(isVNode(child) && child.props && child.props.slot) || 'default'
;(slots[slotName] || (slots[slotName] = [] as any[])).push(child)
}
if (slots) {
for (const key in slots) {
const slotChildren = slots[key]
slots[key] = () => slotChildren
}
}
}
const scopedSlots = props && props.scopedSlots
if (scopedSlots) {
delete props!.scopedSlots
if (slots) {
extend(slots, scopedSlots)
} else {
slots = scopedSlots
}
}
if (slots) {
normalizeChildren(vnode, slots)
}
return vnode return vnode
} }

View File

@ -54,6 +54,9 @@ import { CompilerOptions } from '@vue/compiler-core'
import { markAttrsAccessed } from './componentRenderUtils' import { markAttrsAccessed } from './componentRenderUtils'
import { currentRenderingInstance } from './componentRenderContext' import { currentRenderingInstance } from './componentRenderContext'
import { startMeasure, endMeasure } from './profiling' import { startMeasure, endMeasure } from './profiling'
import { isCompatEnabled } from './compat/compatConfig'
import { DeprecationTypes, warnDeprecation } from './compat/deprecations'
import { compatH } from './compat/renderFn'
export type Data = Record<string, unknown> export type Data = Record<string, unknown>
@ -681,6 +684,18 @@ export function finishComponentSetup(
) { ) {
const Component = instance.type as ComponentOptions const Component = instance.type as ComponentOptions
if (
__COMPAT__ &&
Component.render &&
isCompatEnabled(DeprecationTypes.RENDER_FUNCTION)
) {
warnDeprecation(DeprecationTypes.RENDER_FUNCTION)
const originalRender = Component.render
Component.render = function compatRender() {
return originalRender.call(this, compatH)
}
}
// template / render function normalization // template / render function normalization
if (__NODE_JS__ && isSSR) { if (__NODE_JS__ && isSSR) {
// 1. the render function may already exist, returned by `setup` // 1. the render function may already exist, returned by `setup`

View File

@ -790,13 +790,13 @@ export function applyOptions(
if (__COMPAT__) { if (__COMPAT__) {
if ( if (
beforeDestroy && beforeDestroy &&
softAssertCompatEnabled(DeprecationTypes.OPTIONS_BEFORE_DESTROY) softAssertCompatEnabled(DeprecationTypes.OPTIONS_BEFORE_DESTROY, instance)
) { ) {
onBeforeUnmount(beforeDestroy.bind(publicThis)) onBeforeUnmount(beforeDestroy.bind(publicThis))
} }
if ( if (
destroyed && destroyed &&
softAssertCompatEnabled(DeprecationTypes.OPTIONS_DESTROYED) softAssertCompatEnabled(DeprecationTypes.OPTIONS_DESTROYED, instance)
) { ) {
onUnmounted(destroyed.bind(publicThis)) onUnmounted(destroyed.bind(publicThis))
} }
@ -930,8 +930,11 @@ function resolveData(
instance.data = reactive(data) instance.data = reactive(data)
} else { } else {
// existing data: this is a mixin or extends. // existing data: this is a mixin or extends.
if (__COMPAT__ && isCompatEnabled(DeprecationTypes.OPTIONS_DATA_MERGE)) { if (
deepMergeData(instance.data, data) __COMPAT__ &&
isCompatEnabled(DeprecationTypes.OPTIONS_DATA_MERGE, instance)
) {
deepMergeData(instance.data, data, instance)
} else { } else {
extend(instance.data, data) extend(instance.data, data)
} }

View File

@ -127,7 +127,7 @@ export function invokeDirectiveHook(
} }
let hook = binding.dir[name] as DirectiveHook | DirectiveHook[] | undefined let hook = binding.dir[name] as DirectiveHook | DirectiveHook[] | undefined
if (__COMPAT__ && !hook) { if (__COMPAT__ && !hook) {
hook = mapCompatDirectiveHook(name, binding.dir) hook = mapCompatDirectiveHook(name, binding.dir, instance)
} }
if (hook) { if (hook) {
callWithAsyncErrorHandling(hook, instance, ErrorCodes.DIRECTIVE_HOOK, [ callWithAsyncErrorHandling(hook, instance, ErrorCodes.DIRECTIVE_HOOK, [

View File

@ -361,7 +361,7 @@ function _createVNode(
// 2.x async/functional component compat // 2.x async/functional component compat
if (__COMPAT__) { if (__COMPAT__) {
type = convertLegacyComponent(type) type = convertLegacyComponent(type, currentRenderingInstance)
} }
// class & style normalization. // class & style normalization.
@ -677,7 +677,7 @@ export function mergeProps(...args: (Data & VNodeProps)[]) {
const incoming = toMerge[key] const incoming = toMerge[key]
if (existing !== incoming) { if (existing !== incoming) {
ret[key] = existing ret[key] = existing
? [].concat(existing as any, toMerge[key] as any) ? [].concat(existing as any, incoming as any)
: incoming : incoming
} }
} else if (key !== '') { } else if (key !== '') {