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 { ComponentOptions, getCurrentInstance } from '../component'
import { ComponentInternalInstance, ComponentOptions } from '../component'
import { DeprecationTypes, warnDeprecation } from './deprecations'
export type CompatConfig = Partial<
@ -14,8 +14,10 @@ export function configureCompat(config: CompatConfig) {
extend(globalCompatConfig, config)
}
export function getCompatConfigForKey(key: DeprecationTypes | 'MODE') {
const instance = getCurrentInstance()
export function getCompatConfigForKey(
key: DeprecationTypes | 'MODE',
instance: ComponentInternalInstance | null
) {
const instanceConfig =
instance && (instance.type as ComponentOptions).compatConfig
if (instanceConfig && key in instanceConfig) {
@ -24,9 +26,12 @@ export function getCompatConfigForKey(key: DeprecationTypes | 'MODE') {
return globalCompatConfig[key]
}
export function isCompatEnabled(key: DeprecationTypes): boolean {
const mode = getCompatConfigForKey('MODE') || 2
const val = getCompatConfigForKey(key)
export function isCompatEnabled(
key: DeprecationTypes,
instance: ComponentInternalInstance | null
): boolean {
const mode = getCompatConfigForKey('MODE', instance) || 2
const val = getCompatConfigForKey(key, instance)
if (mode === 2) {
return val !== false
} else {
@ -34,19 +39,27 @@ export function isCompatEnabled(key: DeprecationTypes): boolean {
}
}
export function assertCompatEnabled(key: DeprecationTypes, ...args: any[]) {
if (!isCompatEnabled(key)) {
export function assertCompatEnabled(
key: DeprecationTypes,
instance: ComponentInternalInstance | null,
...args: any[]
) {
if (!isCompatEnabled(key, instance)) {
throw new Error(`${key} compat has been disabled.`)
} 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__) {
warnDeprecation(key, ...args)
warnDeprecation(key, instance, ...args)
}
return isCompatEnabled(key)
return isCompatEnabled(key, instance)
}
// 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 {
Component,
ComponentInternalInstance,
ComponentOptions,
FunctionalComponent,
getCurrentInstance
@ -14,12 +15,18 @@ import { DeprecationTypes, warnDeprecation } from './deprecations'
import { getCompatListeners } from './instanceListeners'
import { compatH } from './renderFn'
export function convertLegacyComponent(comp: any): Component {
export function convertLegacyComponent(
comp: any,
instance: ComponentInternalInstance | null
): Component {
// 2.x async component
// since after disabling this, plain functions are still valid usage, do not
// use softAssert here.
if (isFunction(comp) && isCompatEnabled(DeprecationTypes.COMPONENT_ASYNC)) {
__DEV__ && warnDeprecation(DeprecationTypes.COMPONENT_ASYNC, comp)
if (
isFunction(comp) &&
isCompatEnabled(DeprecationTypes.COMPONENT_ASYNC, instance)
) {
__DEV__ && warnDeprecation(DeprecationTypes.COMPONENT_ASYNC, instance, comp)
return convertLegacyAsyncComponent(comp)
}
@ -27,7 +34,11 @@ export function convertLegacyComponent(comp: any): Component {
if (
isObject(comp) &&
comp.functional &&
softAssertCompatEnabled(DeprecationTypes.COMPONENT_FUNCTIONAL, comp)
softAssertCompatEnabled(
DeprecationTypes.COMPONENT_FUNCTIONAL,
instance,
comp
)
) {
return convertLegacyFunctionalComponent(comp)
}
@ -92,7 +103,7 @@ const normalizedFunctionalComponentMap = new Map<
FunctionalComponent
>()
const legacySlotProxyHandlers: ProxyHandler<InternalSlots> = {
export const legacySlotProxyHandlers: ProxyHandler<InternalSlots> = {
get(target, key: string) {
const slot = target[key]
return slot && slot()

View File

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

View File

@ -1,13 +1,19 @@
import { isPlainObject } from '@vue/shared'
import { ComponentInternalInstance } from '../component'
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) {
const toVal = to[key]
const fromVal = from[key]
if (key in to && isPlainObject(toVal) && isPlainObject(fromVal)) {
__DEV__ && warnDeprecation(DeprecationTypes.OPTIONS_DATA_MERGE, key)
deepMergeData(toVal, fromVal)
__DEV__ &&
warnDeprecation(DeprecationTypes.OPTIONS_DATA_MERGE, instance, key)
deepMergeData(toVal, fromVal, instance)
} else {
to[key] = fromVal
}

View File

@ -1,4 +1,5 @@
import {
ComponentInternalInstance,
formatComponentName,
getComponentName,
getCurrentInstance,
@ -50,7 +51,9 @@ export const enum DeprecationTypes {
TRANSITION_GROUP_ROOT = 'TRANSITION_GROUP_ROOT',
COMPONENT_ASYNC = 'COMPONENT_ASYNC',
COMPONENT_FUNCTIONAL = 'COMPONENT_FUNCTIONAL'
COMPONENT_FUNCTIONAL = 'COMPONENT_FUNCTIONAL',
RENDER_FUNCTION = 'RENDER_FUNCTION'
}
type DeprecationData = {
@ -340,25 +343,41 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
)
},
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 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__) {
return
}
instance = instance || getCurrentInstance()
// check user config
const config = getCompatConfigForKey(key)
const config = getCompatConfigForKey(key, instance)
if (config === 'suppress-warning') {
return
}
const dupKey = key + args.join('')
const instance = getCurrentInstance()
const compName = instance && formatComponentName(instance, instance.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
}${link ? `\n Details: ${link}` : ``}`
)
if (!isCompatEnabled(key)) {
if (!isCompatEnabled(key, instance)) {
console.error(
`^ The above deprecation's compat behavior is disabled and will likely ` +
`lead to runtime errors.`

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@ export function createPropsDefaultThis(propKey: string) {
{},
{
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 { DirectiveArguments, withDirectives } from '../directives'
import {
resolveDirective,
resolveDynamicComponent
} from '../helpers/resolveAssets'
import {
createVNode,
isVNode,
normalizeChildren,
VNode,
VNodeArrayChildren,
VNodeProps
@ -23,6 +35,8 @@ interface LegacyVNodeProps {
nativeOn?: Record<string, Function | Function[]>
directives?: LegacyVNodeDirective[]
// component only
props?: Record<string, unknown>
slot?: string
scopedSlots?: Record<string, Function>
}
@ -56,6 +70,9 @@ export function compatH(
propsOrChildren?: any,
children?: any
): VNode {
// to support v2 string component name lookup
type = resolveDynamicComponent(type)
const l = arguments.length
if (l === 2) {
if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
@ -64,10 +81,12 @@ export function compatH(
return convertLegacySlots(createVNode(type, null, [propsOrChildren]))
}
// props without children
return convertLegacyDirectives(
return convertLegacySlots(
convertLegacyDirectives(
createVNode(type, convertLegacyProps(propsOrChildren)),
propsOrChildren
)
)
} else {
// omit props
return convertLegacySlots(createVNode(type, null, propsOrChildren))
@ -87,17 +106,87 @@ export function compatH(
}
}
function convertLegacyProps(props: LegacyVNodeProps): Data & VNodeProps {
// TODO
return props as any
function convertLegacyProps(legacyProps?: LegacyVNodeProps): Data & VNodeProps {
const converted: Data & VNodeProps = {}
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 {
// TODO
function convertLegacyDirectives(
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
}
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
}

View File

@ -54,6 +54,9 @@ import { CompilerOptions } from '@vue/compiler-core'
import { markAttrsAccessed } from './componentRenderUtils'
import { currentRenderingInstance } from './componentRenderContext'
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>
@ -681,6 +684,18 @@ export function finishComponentSetup(
) {
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
if (__NODE_JS__ && isSSR) {
// 1. the render function may already exist, returned by `setup`

View File

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

View File

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

View File

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