From f05d6dfd98f9c8dc8700c91cf8a8db4437f5827a Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 9 Apr 2021 18:52:14 -0400 Subject: [PATCH] wip: render function compat --- .../src/compat/__tests__/compatGlobal.spec.ts | 16 -- .../src/compat/__tests__/global.spec.ts | 18 +++ .../src/compat/__tests__/renderFn.spec.ts | 149 ++++++++++++++++++ .../runtime-core/src/compat/compatConfig.ts | 37 +++-- packages/runtime-core/src/compat/component.ts | 21 ++- .../src/compat/customDirective.ts | 18 ++- packages/runtime-core/src/compat/data.ts | 12 +- .../runtime-core/src/compat/deprecations.ts | 29 +++- packages/runtime-core/src/compat/global.ts | 19 +-- .../runtime-core/src/compat/globalConfig.ts | 4 +- packages/runtime-core/src/compat/instance.ts | 30 ++-- .../src/compat/instanceChildren.ts | 2 +- .../src/compat/instanceEventEmitter.ts | 6 +- .../src/compat/instanceListeners.ts | 2 +- packages/runtime-core/src/compat/props.ts | 2 +- packages/runtime-core/src/compat/renderFn.ts | 109 +++++++++++-- packages/runtime-core/src/component.ts | 15 ++ packages/runtime-core/src/componentOptions.ts | 11 +- packages/runtime-core/src/directives.ts | 2 +- packages/runtime-core/src/vnode.ts | 4 +- 20 files changed, 419 insertions(+), 87 deletions(-) delete mode 100644 packages/runtime-core/src/compat/__tests__/compatGlobal.spec.ts create mode 100644 packages/runtime-core/src/compat/__tests__/global.spec.ts create mode 100644 packages/runtime-core/src/compat/__tests__/renderFn.spec.ts diff --git a/packages/runtime-core/src/compat/__tests__/compatGlobal.spec.ts b/packages/runtime-core/src/compat/__tests__/compatGlobal.spec.ts deleted file mode 100644 index d4e94433..00000000 --- a/packages/runtime-core/src/compat/__tests__/compatGlobal.spec.ts +++ /dev/null @@ -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') -}) diff --git a/packages/runtime-core/src/compat/__tests__/global.spec.ts b/packages/runtime-core/src/compat/__tests__/global.spec.ts new file mode 100644 index 00000000..7c153e92 --- /dev/null +++ b/packages/runtime-core/src/compat/__tests__/global.spec.ts @@ -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') + }) +}) diff --git a/packages/runtime-core/src/compat/__tests__/renderFn.spec.ts b/packages/runtime-core/src/compat/__tests__/renderFn.spec.ts new file mode 100644 index 00000000..f67e25cd --- /dev/null +++ b/packages/runtime-core/src/compat/__tests__/renderFn.spec.ts @@ -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' }]) + }) +}) diff --git a/packages/runtime-core/src/compat/compatConfig.ts b/packages/runtime-core/src/compat/compatConfig.ts index 3eae262e..06c5c6b4 100644 --- a/packages/runtime-core/src/compat/compatConfig.ts +++ b/packages/runtime-core/src/compat/compatConfig.ts @@ -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 diff --git a/packages/runtime-core/src/compat/component.ts b/packages/runtime-core/src/compat/component.ts index c67c681f..4812440f 100644 --- a/packages/runtime-core/src/compat/component.ts +++ b/packages/runtime-core/src/compat/component.ts @@ -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 = { +export const legacySlotProxyHandlers: ProxyHandler = { get(target, key: string) { const slot = target[key] return slot && slot() diff --git a/packages/runtime-core/src/compat/customDirective.ts b/packages/runtime-core/src/compat/customDirective.ts index 823b9ce4..f6303c4e 100644 --- a/packages/runtime-core/src/compat/customDirective.ts +++ b/packages/runtime-core/src/compat/customDirective.ts @@ -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] } diff --git a/packages/runtime-core/src/compat/data.ts b/packages/runtime-core/src/compat/data.ts index dbe66a8d..c296b460 100644 --- a/packages/runtime-core/src/compat/data.ts +++ b/packages/runtime-core/src/compat/data.ts @@ -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 } diff --git a/packages/runtime-core/src/compat/deprecations.ts b/packages/runtime-core/src/compat/deprecations.ts index 58949876..b2d10f52 100644 --- a/packages/runtime-core/src/compat/deprecations.ts +++ b/packages/runtime-core/src/compat/deprecations.ts @@ -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 = { ) }, 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 = Object.create(null) const warnCount: Record = 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.` diff --git a/packages/runtime-core/src/compat/global.ts b/packages/runtime-core/src/compat/global.ts index f79b5c4f..93490b2c 100644 --- a/packages/runtime-core/src/compat/global.ts +++ b/packages/runtime-core/src/compat/global.ts @@ -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 } } diff --git a/packages/runtime-core/src/compat/globalConfig.ts b/packages/runtime-core/src/compat/globalConfig.ts index 6337ded7..cf081c3e 100644 --- a/packages/runtime-core/src/compat/globalConfig.ts +++ b/packages/runtime-core/src/compat/globalConfig.ts @@ -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) ) { diff --git a/packages/runtime-core/src/compat/instance.ts b/packages/runtime-core/src/compat/instance.ts index 74792b05..38c265a2 100644 --- a/packages/runtime-core/src/compat/instance.ts +++ b/packages/runtime-core/src/compat/instance.ts @@ -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 }, diff --git a/packages/runtime-core/src/compat/instanceChildren.ts b/packages/runtime-core/src/compat/instanceChildren.ts index 4bdaadb5..09bb4a9f 100644 --- a/packages/runtime-core/src/compat/instanceChildren.ts +++ b/packages/runtime-core/src/compat/instanceChildren.ts @@ -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) { diff --git a/packages/runtime-core/src/compat/instanceEventEmitter.ts b/packages/runtime-core/src/compat/instanceEventEmitter.ts index ba266fa9..0e1307ba 100644 --- a/packages/runtime-core/src/compat/instanceEventEmitter.ts +++ b/packages/runtime-core/src/compat/instanceEventEmitter.ts @@ -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) { diff --git a/packages/runtime-core/src/compat/instanceListeners.ts b/packages/runtime-core/src/compat/instanceListeners.ts index 640bbfcf..263d1961 100644 --- a/packages/runtime-core/src/compat/instanceListeners.ts +++ b/packages/runtime-core/src/compat/instanceListeners.ts @@ -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 = {} const rawProps = instance.vnode.props diff --git a/packages/runtime-core/src/compat/props.ts b/packages/runtime-core/src/compat/props.ts index 1582cb08..48d75fa5 100644 --- a/packages/runtime-core/src/compat/props.ts +++ b/packages/runtime-core/src/compat/props.ts @@ -5,7 +5,7 @@ export function createPropsDefaultThis(propKey: string) { {}, { get() { - warnDeprecation(DeprecationTypes.PROPS_DEFAULT_THIS, propKey) + warnDeprecation(DeprecationTypes.PROPS_DEFAULT_THIS, null, propKey) } } ) diff --git a/packages/runtime-core/src/compat/renderFn.ts b/packages/runtime-core/src/compat/renderFn.ts index ef4cd37b..5bc720cf 100644 --- a/packages/runtime-core/src/compat/renderFn.ts +++ b/packages/runtime-core/src/compat/renderFn.ts @@ -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 directives?: LegacyVNodeDirective[] + // component only + props?: Record slot?: string scopedSlots?: Record } @@ -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,9 +81,11 @@ export function compatH( return convertLegacySlots(createVNode(type, null, [propsOrChildren])) } // props without children - return convertLegacyDirectives( - createVNode(type, convertLegacyProps(propsOrChildren)), - propsOrChildren + return convertLegacySlots( + convertLegacyDirectives( + createVNode(type, convertLegacyProps(propsOrChildren)), + propsOrChildren + ) ) } else { // omit props @@ -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 | 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 } diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 635bccc4..1045db8c 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -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 @@ -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` diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index 1f182c6d..8b029380 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -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) } diff --git a/packages/runtime-core/src/directives.ts b/packages/runtime-core/src/directives.ts index 69b3d3ad..a1987908 100644 --- a/packages/runtime-core/src/directives.ts +++ b/packages/runtime-core/src/directives.ts @@ -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, [ diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 364214de..35aa6678 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -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 !== '') {