]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: render function compat
authorEvan You <yyx990803@gmail.com>
Fri, 9 Apr 2021 22:52:14 +0000 (18:52 -0400)
committerEvan You <yyx990803@gmail.com>
Fri, 9 Apr 2021 22:56:31 +0000 (18:56 -0400)
20 files changed:
packages/runtime-core/src/compat/__tests__/compatGlobal.spec.ts [deleted file]
packages/runtime-core/src/compat/__tests__/global.spec.ts [new file with mode: 0644]
packages/runtime-core/src/compat/__tests__/renderFn.spec.ts [new file with mode: 0644]
packages/runtime-core/src/compat/compatConfig.ts
packages/runtime-core/src/compat/component.ts
packages/runtime-core/src/compat/customDirective.ts
packages/runtime-core/src/compat/data.ts
packages/runtime-core/src/compat/deprecations.ts
packages/runtime-core/src/compat/global.ts
packages/runtime-core/src/compat/globalConfig.ts
packages/runtime-core/src/compat/instance.ts
packages/runtime-core/src/compat/instanceChildren.ts
packages/runtime-core/src/compat/instanceEventEmitter.ts
packages/runtime-core/src/compat/instanceListeners.ts
packages/runtime-core/src/compat/props.ts
packages/runtime-core/src/compat/renderFn.ts
packages/runtime-core/src/component.ts
packages/runtime-core/src/componentOptions.ts
packages/runtime-core/src/directives.ts
packages/runtime-core/src/vnode.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 (file)
index d4e9443..0000000
+++ /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 (file)
index 0000000..7c153e9
--- /dev/null
@@ -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 (file)
index 0000000..f67e25c
--- /dev/null
@@ -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' }])
+  })
+})
index 3eae262ecb894f5948af94a4467c1d37f216a558..06c5c6b43b3740a95a8ee4d600701b51403f7213 100644 (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
index c67c681f042992815c6cba900136ce8148224996..4812440f3ce975543e97793b7e9b6b4b7b3723cd 100644 (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()
index 823b9ce4c547bde3bcfa82c33181bee7a705be3c..f6303c4e68d86f2e57564d787a834945a6b0206d 100644 (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]
     }
index dbe66a8de82e8b8b4dd3c660f2884fd92ed4eb76..c296b460a65679f60279f575d331483a9eb3fafd 100644 (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
     }
index 5894987684fa1cc9be91147df47a91084898fa95..b2d10f52d48d48788f32b744da9874c8ec5d0fde 100644 (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.`
index f79b5c4ff15bd19806618ccd818b679a4483d3e5..93490b2c4c20846b2b1497f42a562d28c9ca06f0 100644 (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
             }
           }
index 6337ded796248d9e4552fb31b145ce8c871436f5..cf081c3eeabbcef609dadd3deab6bddfae27d4cd 100644 (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)
         ) {
index 74792b05ddba92c2f1ede2dc2c4df8cecfe08636..38c265a282e380f203f134a20689f149407b4944 100644 (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
     },
index 4bdaadb5d31392a6839a33d5cda7a9842fcaa249..09bb4a9fef3bab7db79aaeca2a1c016382538971 100644 (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) {
index ba266fa9cff34e823f586ea75c2a3dd14db212b0..0e1307badb6efea2ea7999f7e5edcda6c373a606 100644 (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) {
index 640bbfcf5c7139cd61d95e050cad1931260a6142..263d19611fbc9d5c1b908d180769e19d9b34d6ac 100644 (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
index 1582cb08f8b227fbe4be85f6295cde741a573b77..48d75fa52b10459d1431a683e2ee08222cfecdd3 100644 (file)
@@ -5,7 +5,7 @@ export function createPropsDefaultThis(propKey: string) {
     {},
     {
       get() {
-        warnDeprecation(DeprecationTypes.PROPS_DEFAULT_THIS, propKey)
+        warnDeprecation(DeprecationTypes.PROPS_DEFAULT_THIS, null, propKey)
       }
     }
   )
index ef4cd37b1e72b68a0ac9c913738169dd3a474942..5bc720cf7420fec62c87c7dfe4a843b35237d42c 100644 (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,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<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
 }
index 635bccc4a0ef225aca02a95e98c62a90fec0d030..1045db8c899ace62d5746d7b0d49992cce52fd7e 100644 (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`
index 1f182c6d5c2200aa77aa7a1cdaf113429d7dc70b..8b029380ffa7fe5bbca50d23438776f0bf7cafd7 100644 (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)
     }
index 69b3d3ad4ee017c2b189d330f5f854827f94226a..a19879081e12eceb779af0fe278edb086b3d8461 100644 (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, [
index 364214de935381bc1541fac4d57c3abc17e07f45..35aa6678b2e1d7e935e16508c85a5d3b51ce6f07 100644 (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 !== '') {