]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: more compat progress
authorEvan You <yyx990803@gmail.com>
Wed, 21 Apr 2021 19:09:18 +0000 (15:09 -0400)
committerEvan You <yyx990803@gmail.com>
Wed, 21 Apr 2021 19:09:18 +0000 (15:09 -0400)
14 files changed:
packages/runtime-core/__tests__/componentSlots.spec.ts
packages/runtime-core/src/compat/compatConfig.ts
packages/runtime-core/src/compat/component.ts
packages/runtime-core/src/compat/instance.ts
packages/runtime-core/src/compat/props.ts
packages/runtime-core/src/compat/renderFn.ts
packages/runtime-core/src/compat/renderHelpers.ts
packages/runtime-core/src/compat/vModel.ts
packages/runtime-core/src/componentProps.ts
packages/runtime-core/src/componentRenderContext.ts
packages/runtime-core/src/componentSlots.ts
packages/runtime-core/src/vnode.ts
packages/vue-compat/src/index.ts
packages/vue-compat/src/runtime.ts

index ecb548f210cfd7a669946edcd093543a17c756a8..9248a8e6afeb167ad9965f8a6c7a8175cf5de0af 100644 (file)
@@ -82,7 +82,7 @@ describe('component: slots', () => {
     expect(slots.default()).toMatchObject([normalizeVNode(h('span'))])
   })
 
-  test('updateSlots: instance.slots should be update correctly (when slotType is number)', async () => {
+  test('updateSlots: instance.slots should be updated correctly (when slotType is number)', async () => {
     const flag1 = ref(true)
 
     let instance: any
@@ -124,7 +124,7 @@ describe('component: slots', () => {
     expect(instance.slots).toHaveProperty('two')
   })
 
-  test('updateSlots: instance.slots should be update correctly (when slotType is null)', async () => {
+  test('updateSlots: instance.slots should be updated correctly (when slotType is null)', async () => {
     const flag1 = ref(true)
 
     let instance: any
index 25a387f1deebf4795b0aa8795886b7a3183d7aca..50f7efd698d6861ddc3f69c14dde1c889f9d8be8 100644 (file)
@@ -262,7 +262,8 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
 
   [DeprecationTypes.PROPS_DEFAULT_THIS]: {
     message: (key: string) =>
-      `props default value function no longer has access to "this". ` +
+      `props default value function no longer has access to "this". The compat ` +
+      `build only offers access to this.$options.` +
       `(found in prop "${key}")`,
     link: `https://v3.vuejs.org/guide/migration/props-default-this.html`
   },
index 0cae7ad38421f53e6b5cd2fe54347e06815fa08e..36188d0cec0fb2d8560401b11bacca53df8aea54 100644 (file)
@@ -132,7 +132,7 @@ function convertLegacyFunctionalComponent(comp: ComponentOptions) {
       data: instance.vnode.props || {},
       scopedSlots: ctx.slots,
       parent: instance.parent && instance.parent.proxy,
-      get slots() {
+      slots() {
         return new Proxy(ctx.slots, legacySlotProxyHandlers)
       },
       get listeners() {
index b324d7f7044ae5d7652b068341f85082d644edfb..8466d0cd6e8937a8723cca6bded0498472aa15a4 100644 (file)
@@ -12,13 +12,16 @@ import { getCompatListeners } from './instanceListeners'
 import { shallowReadonly } from '@vue/reactivity'
 import { legacySlotProxyHandlers } from './component'
 import { compatH } from './renderFn'
+import { createCommentVNode, createTextVNode } from '../vnode'
+import { renderList } from '../helpers/renderList'
 import {
+  legacyBindObjectListeners,
   legacyBindObjectProps,
+  legacyCheckKeyCodes,
   legacyRenderSlot,
-  legacyRenderStatic
+  legacyRenderStatic,
+  legacyresolveScopedSlots
 } from './renderHelpers'
-import { createCommentVNode, createTextVNode } from '../vnode'
-import { renderList } from '../helpers/renderList'
 
 export function installCompatInstanceProperties(map: PublicPropertiesMap) {
   const set = (target: any, key: any, val: any) => {
@@ -98,6 +101,9 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
     _b: () => legacyBindObjectProps,
     _e: () => createCommentVNode,
     _v: () => createTextVNode,
-    _m: i => legacyRenderStatic.bind(null, i)
+    _m: i => legacyRenderStatic.bind(null, i),
+    _g: () => legacyBindObjectListeners,
+    _u: () => legacyresolveScopedSlots,
+    _k: i => legacyCheckKeyCodes.bind(null, i)
   } as PublicPropertiesMap)
 }
index 51871562d1af62e7b7b2b28e12880f0b255070c8..e2fccb6675fccad563f9693545f5fdec5997f409 100644 (file)
@@ -1,11 +1,39 @@
+import { isArray } from '@vue/shared'
+import { inject } from '../apiInject'
+import { ComponentInternalInstance, Data } from '../component'
+import { ComponentOptions, resolveMergedOptions } from '../componentOptions'
 import { DeprecationTypes, warnDeprecation } from './compatConfig'
 
-export function createPropsDefaultThis(propKey: string) {
+export function createPropsDefaultThis(
+  instance: ComponentInternalInstance,
+  rawProps: Data,
+  propKey: string
+) {
   return new Proxy(
     {},
     {
-      get() {
-        warnDeprecation(DeprecationTypes.PROPS_DEFAULT_THIS, null, propKey)
+      get(_, key: string) {
+        __DEV__ &&
+          warnDeprecation(DeprecationTypes.PROPS_DEFAULT_THIS, null, propKey)
+        // $options
+        if (key === '$options') {
+          return resolveMergedOptions(instance)
+        }
+        // props
+        if (key in rawProps) {
+          return rawProps[key]
+        }
+        // injections
+        const injections = (instance.type as ComponentOptions).inject
+        if (injections) {
+          if (isArray(injections)) {
+            if (injections.includes(key)) {
+              return inject(key)
+            }
+          } else if (key in injections) {
+            return inject(key)
+          }
+        }
       }
     }
   )
index 29180f633a46022019f8ae914c2c5172e5ef64c2..b04ca0cda174129d9e649d8db504fb3d470e92a7 100644 (file)
@@ -1,7 +1,9 @@
 import {
   extend,
+  hyphenate,
   isArray,
   isObject,
+  makeMap,
   normalizeClass,
   normalizeStyle,
   ShapeFlags,
@@ -14,6 +16,7 @@ import {
   Data,
   InternalRenderFunction
 } from '../component'
+import { currentRenderingInstance } from '../componentRenderContext'
 import { DirectiveArguments, withDirectives } from '../directives'
 import {
   resolveDirective,
@@ -27,7 +30,12 @@ import {
   VNodeArrayChildren,
   VNodeProps
 } from '../vnode'
-import { checkCompatEnabled, DeprecationTypes } from './compatConfig'
+import {
+  checkCompatEnabled,
+  DeprecationTypes,
+  isCompatEnabled
+} from './compatConfig'
+import { compatModelEventPrefix } from './vModel'
 
 const v3CompiledRenderFnRE = /^(?:function \w+)?\(_ctx, _cache/
 
@@ -76,6 +84,11 @@ interface LegacyVNodeProps {
   props?: Record<string, unknown>
   slot?: string
   scopedSlots?: Record<string, Function>
+  model?: {
+    value: any
+    callback: (v: any) => void
+    expression: string
+  }
 }
 
 interface LegacyVNodeDirective {
@@ -107,12 +120,22 @@ export function compatH(
   propsOrChildren?: any,
   children?: any
 ): VNode {
-  // to support v2 string component name lookup
-  type = resolveDynamicComponent(type)
+  // to support v2 string component name look!up
+  if (typeof type === 'string') {
+    const t = hyphenate(type)
+    if (t === 'transition' || t === 'transition-group' || t === 'keep-alive') {
+      // since transition and transition-group are runtime-dom-specific,
+      // we cannot import them directly here. Instead they are registered using
+      // special keys in @vue/compat entry.
+      type = `__compat__${t}`
+    }
+    type = resolveDynamicComponent(type)
+  }
 
   const l = arguments.length
-  if (l === 2) {
-    if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
+  const is2ndArgArrayChildren = isArray(propsOrChildren)
+  if (l === 2 || is2ndArgArrayChildren) {
+    if (isObject(propsOrChildren) && !is2ndArgArrayChildren) {
       // single vnode without props
       if (isVNode(propsOrChildren)) {
         return convertLegacySlots(createVNode(type, null, [propsOrChildren]))
@@ -120,7 +143,7 @@ export function compatH(
       // props without children
       return convertLegacySlots(
         convertLegacyDirectives(
-          createVNode(type, convertLegacyProps(propsOrChildren)),
+          createVNode(type, convertLegacyProps(propsOrChildren, type)),
           propsOrChildren
         )
       )
@@ -134,15 +157,20 @@ export function compatH(
     }
     return convertLegacySlots(
       convertLegacyDirectives(
-        createVNode(type, convertLegacyProps(propsOrChildren), children),
+        createVNode(type, convertLegacyProps(propsOrChildren, type), children),
         propsOrChildren
       )
     )
   }
 }
 
+const skipLegacyRootLevelProps = /*#__PURE__*/ makeMap(
+  'refInFor,staticStyle,staticClass,directives,model'
+)
+
 function convertLegacyProps(
-  legacyProps?: LegacyVNodeProps
+  legacyProps: LegacyVNodeProps | undefined,
+  type: any
 ): Data & VNodeProps | null {
   if (!legacyProps) {
     return null
@@ -172,11 +200,7 @@ function convertLegacyProps(
           }
         }
       }
-    } else if (
-      key !== 'refInFor' &&
-      key !== 'staticStyle' &&
-      key !== 'staticClass'
-    ) {
+    } else if (!skipLegacyRootLevelProps(key)) {
       converted[key] = legacyProps[key as keyof LegacyVNodeProps]
     }
   }
@@ -188,6 +212,13 @@ function convertLegacyProps(
     converted.style = normalizeStyle([legacyProps.staticStyle, converted.style])
   }
 
+  if (legacyProps.model && isObject(type)) {
+    // v2 compiled component v-model
+    const { prop = 'value', event = 'input' } = (type as any).model || {}
+    converted[prop] = legacyProps.model.value
+    converted[compatModelEventPrefix + event] = legacyProps.model.callback
+  }
+
   return converted
 }
 
@@ -237,7 +268,12 @@ function convertLegacySlots(vnode: VNode): VNode {
       const child = children[i]
       const slotName =
         (isVNode(child) && child.props && child.props.slot) || 'default'
-      ;(slots[slotName] || (slots[slotName] = [] as any[])).push(child)
+      const slot = slots[slotName] || (slots[slotName] = [] as any[])
+      if (isVNode(child) && child.type === 'template') {
+        slot.push(child.children)
+      } else {
+        slot.push(child)
+      }
     }
     if (slots) {
       for (const key in slots) {
@@ -263,3 +299,31 @@ function convertLegacySlots(vnode: VNode): VNode {
 
   return vnode
 }
+
+export function defineLegacyVNodeProperties(vnode: VNode) {
+  if (
+    isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, currentRenderingInstance)
+  ) {
+    const getInstance = () => vnode.component && vnode.component.proxy
+    let componentOptions: any
+    Object.defineProperties(vnode, {
+      elm: { get: () => vnode.el },
+      componentInstance: { get: getInstance },
+      child: { get: getInstance },
+      componentOptions: {
+        get: () => {
+          if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
+            if (componentOptions) {
+              return componentOptions
+            }
+            return (componentOptions = {
+              Ctor: vnode.type,
+              propsData: vnode.props,
+              children: vnode.children
+            })
+          }
+        }
+      }
+    })
+  }
+}
index 0f38a245a132111792f8960c277d7e58e77bffed..0b90632dc9e961133d84f1d8fa222c677e55ae0d 100644 (file)
@@ -8,9 +8,22 @@ import {
   normalizeClass
 } from '@vue/shared'
 import { ComponentInternalInstance } from '../component'
+import { Slot } from '../componentSlots'
+import { createSlots } from '../helpers/createSlots'
 import { renderSlot } from '../helpers/renderSlot'
+import { toHandlers } from '../helpers/toHandlers'
 import { mergeProps, VNode } from '../vnode'
 
+function toObject(arr: Array<any>): Object {
+  const res = {}
+  for (let i = 0; i < arr.length; i++) {
+    if (arr[i]) {
+      extend(res, arr[i])
+    }
+  }
+  return res
+}
+
 export function legacyBindObjectProps(
   data: any,
   _tag: string,
@@ -49,6 +62,10 @@ export function legacyBindObjectProps(
   return data
 }
 
+export function legacyBindObjectListeners(props: any, listeners: any) {
+  return mergeProps(props, toHandlers(listeners))
+}
+
 export function legacyRenderSlot(
   instance: ComponentInternalInstance,
   name: string,
@@ -62,6 +79,41 @@ export function legacyRenderSlot(
   return renderSlot(instance.slots, name, props, fallback && (() => fallback))
 }
 
+type LegacyScopedSlotsData = Array<
+  | {
+      key: string
+      fn: Function
+    }
+  | LegacyScopedSlotsData
+>
+
+export function legacyresolveScopedSlots(
+  fns: LegacyScopedSlotsData,
+  raw?: Record<string, Slot>,
+  // the following are added in 2.6
+  hasDynamicKeys?: boolean
+) {
+  // v2 default slot doesn't have name
+  return createSlots(
+    raw || ({ $stable: !hasDynamicKeys } as any),
+    mapKeyToName(fns)
+  )
+}
+
+function mapKeyToName(slots: LegacyScopedSlotsData) {
+  for (let i = 0; i < slots.length; i++) {
+    const fn = slots[i]
+    if (fn) {
+      if (isArray(fn)) {
+        mapKeyToName(fn)
+      } else {
+        ;(fn as any).name = fn.key || 'default'
+      }
+    }
+  }
+  return slots as any
+}
+
 const staticCacheMap = /*#__PURE__*/ new WeakMap<
   ComponentInternalInstance,
   any[]
@@ -83,12 +135,30 @@ export function legacyRenderStatic(
   return (cache[index] = fn.call(ctx, null, ctx))
 }
 
-function toObject(arr: Array<any>): Object {
-  const res = {}
-  for (let i = 0; i < arr.length; i++) {
-    if (arr[i]) {
-      extend(res, arr[i])
-    }
+export function legacyCheckKeyCodes(
+  instance: ComponentInternalInstance,
+  eventKeyCode: number,
+  key: string,
+  builtInKeyCode?: number | number[],
+  eventKeyName?: string,
+  builtInKeyName?: string | string[]
+) {
+  const config = instance.appContext.config as any
+  const configKeyCodes = config.keyCodes || {}
+  const mappedKeyCode = configKeyCodes[key] || builtInKeyCode
+  if (builtInKeyName && eventKeyName && !configKeyCodes[key]) {
+    return isKeyNotMatch(builtInKeyName, eventKeyName)
+  } else if (mappedKeyCode) {
+    return isKeyNotMatch(mappedKeyCode, eventKeyCode)
+  } else if (eventKeyName) {
+    return hyphenate(eventKeyName) !== key
+  }
+}
+
+function isKeyNotMatch<T>(expect: T | T[], actual: T): boolean {
+  if (isArray(expect)) {
+    return expect.indexOf(actual) === -1
+  } else {
+    return expect !== actual
   }
-  return res
 }
index d24f06b6202416731954f853f681cd3c967d5bec..03ae787afb9446a2ffee0b4a6491b029c166b660 100644 (file)
@@ -35,6 +35,9 @@ export function convertLegacyVModelProps(vnode: VNode) {
       warnedTypes.add(type as ComponentOptions)
     }
 
+    // v3 compiled model code -> v2 compat props
+    // modelValue -> value
+    // onUpdate:modelValue -> onModelCompat:input
     const { prop = 'value', event = 'input' } = (type as any).model || {}
     props[prop] = props.modelValue
     delete props.modelValue
@@ -42,7 +45,6 @@ export function convertLegacyVModelProps(vnode: VNode) {
     if (dynamicProps) {
       dynamicProps[dynamicProps.indexOf('modelValue')] = prop
     }
-
     props[compatModelEventPrefix + event] = props['onUpdate:modelValue']
     delete props['onUpdate:modelValue']
   }
index ef08c3ad41298b3e8276c75dd62fb924fb8a57b1..ed933056c58a975131e99abfbc25e91a8cf0cc7d 100644 (file)
@@ -376,9 +376,8 @@ function resolvePropValue(
           setCurrentInstance(instance)
           value = propsDefaults[key] = defaultValue.call(
             __COMPAT__ &&
-            __DEV__ &&
             isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance)
-              ? createPropsDefaultThis(key)
+              ? createPropsDefaultThis(instance, props, key)
               : null,
             props
           )
index 1771d9aa0297e1531f431b913a556cadc17e124d..6cc232358b536d4171325db2698d95d9122160ce 100644 (file)
@@ -25,6 +25,10 @@ export function setCurrentRenderingInstance(
   const prev = currentRenderingInstance
   currentRenderingInstance = instance
   currentScopeId = (instance && instance.type.__scopeId) || null
+  // v2 pre-compiled components uses _scopeId instead of __scopeId
+  if (__COMPAT__ && !currentScopeId) {
+    currentScopeId = (instance && (instance.type as any)._scopeId) || null
+  }
   return prev
 }
 
index ab9b1fbc60ec09b6af2eaad136186ded5f22f187..1acbd1aa1d6fec69a734b5a00db5169713db5dc7 100644 (file)
@@ -19,6 +19,7 @@ import { warn } from './warning'
 import { isKeepAlive } from './components/KeepAlive'
 import { withCtx } from './componentRenderContext'
 import { isHmrUpdating } from './hmr'
+import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
 
 export type Slot = (...args: any[]) => VNode[]
 
@@ -72,7 +73,11 @@ const normalizeSlot = (
     return normalizeSlotValue(rawSlot(props))
   }, ctx) as Slot
 
-const normalizeObjectSlots = (rawSlots: RawSlots, slots: InternalSlots) => {
+const normalizeObjectSlots = (
+  rawSlots: RawSlots,
+  slots: InternalSlots,
+  instance: ComponentInternalInstance
+) => {
   const ctx = rawSlots._ctx
   for (const key in rawSlots) {
     if (isInternalKey(key)) continue
@@ -80,7 +85,13 @@ const normalizeObjectSlots = (rawSlots: RawSlots, slots: InternalSlots) => {
     if (isFunction(value)) {
       slots[key] = normalizeSlot(key, value, ctx)
     } else if (value != null) {
-      if (__DEV__ && !__COMPAT__) {
+      if (
+        __DEV__ &&
+        !(
+          __COMPAT__ &&
+          isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance)
+        )
+      ) {
         warn(
           `Non-function value encountered for slot "${key}". ` +
             `Prefer function slots for better performance.`
@@ -117,7 +128,11 @@ export const initSlots = (
       // make compiler marker non-enumerable
       def(children as InternalSlots, '_', type)
     } else {
-      normalizeObjectSlots(children as RawSlots, (instance.slots = {}))
+      normalizeObjectSlots(
+        children as RawSlots,
+        (instance.slots = {}),
+        instance
+      )
     }
   } else {
     instance.slots = {}
@@ -162,7 +177,7 @@ export const updateSlots = (
       }
     } else {
       needDeletionCheck = !(children as RawSlots).$stable
-      normalizeObjectSlots(children as RawSlots, slots)
+      normalizeObjectSlots(children as RawSlots, slots, instance)
     }
     deletionComparisonTarget = children as RawSlots
   } else if (children) {
index 9d1fd791ab2d5b660487d4917724480cbd45c7fc..05fc6ad079bd65b26297e447818fc76a4bb2c361 100644 (file)
@@ -43,6 +43,7 @@ import { hmrDirtyComponents } from './hmr'
 import { setCompiledSlotRendering } from './helpers/renderSlot'
 import { convertLegacyComponent } from './compat/component'
 import { convertLegacyVModelProps } from './compat/vModel'
+import { defineLegacyVNodeProperties } from './compat/renderFn'
 
 export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
   __isFragment: true
@@ -472,6 +473,7 @@ function _createVNode(
 
   if (__COMPAT__) {
     convertLegacyVModelProps(vnode)
+    defineLegacyVNodeProperties(vnode)
   }
 
   return vnode
@@ -486,7 +488,7 @@ export function cloneVNode<T, U>(
   // key enumeration cost.
   const { props, ref, patchFlag, children } = vnode
   const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
-  return {
+  const cloned: VNode = {
     __v_isVNode: true,
     [ReactiveFlags.SKIP]: true,
     type: vnode.type,
@@ -540,6 +542,10 @@ export function cloneVNode<T, U>(
     el: vnode.el,
     anchor: vnode.anchor
   }
+  if (__COMPAT__) {
+    defineLegacyVNodeProperties(cloned)
+  }
+  return cloned as any
 }
 
 /**
index 845736352ff3048b2d6a4e6907b2e5bdc6b5039f..6421b939ef356c96ce118cc4be889f67f9e74a76 100644 (file)
@@ -2,16 +2,11 @@
 // and the compiler, and supports on-the-fly compilation of the template option.
 import { initDev } from './dev'
 import { compile, CompilerError, CompilerOptions } from '@vue/compiler-dom'
-import {
-  registerRuntimeCompiler,
-  RenderFunction,
-  warn,
-  createApp,
-  compatUtils
-} from '@vue/runtime-dom'
+import { registerRuntimeCompiler, RenderFunction, warn } from '@vue/runtime-dom'
 import { isString, NOOP, generateCodeFrame, extend } from '@vue/shared'
 import { InternalRenderFunction } from 'packages/runtime-core/src/component'
 import * as runtimeDom from '@vue/runtime-dom'
+import Vue from './runtime'
 
 if (__DEV__) {
   initDev()
@@ -92,10 +87,6 @@ function compileToFunction(
 
 registerRuntimeCompiler(compileToFunction)
 
-const Vue = compatUtils.createCompatVue(createApp)
-
 Vue.compile = compileToFunction
 
-extend(Vue, runtimeDom)
-
 export default Vue
index a9f19cf7067004e67b8ca625e364d6a5b02ac0ae..38ee5cff27ee55a0063f05c94094e5a9781a3f3a 100644 (file)
@@ -1,7 +1,17 @@
 // This entry exports the runtime only, and is built as
 // `dist/vue.esm-bundler.js` which is used by default for bundlers.
 import { initDev } from './dev'
-import { compatUtils, createApp, warn } from '@vue/runtime-dom'
+import {
+  compatUtils,
+  createApp,
+  warn,
+  Transition,
+  TransitionGroup,
+  KeepAlive,
+  DeprecationTypes,
+  vShow,
+  vModelDynamic
+} from '@vue/runtime-dom'
 import { extend } from '@vue/shared'
 
 if (__DEV__) {
@@ -10,7 +20,25 @@ if (__DEV__) {
 
 import * as runtimeDom from '@vue/runtime-dom'
 
-const Vue = compatUtils.createCompatVue(createApp)
+function wrappedCreateApp(...args: any[]) {
+  // @ts-ignore
+  const app = createApp(...args)
+  if (compatUtils.isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, null)) {
+    // register built-in components so that they can be resolved via strings
+    // in the legacy h() call. The __compat__ prefix is to ensure that v3 h()
+    // doesn't get affected.
+    app.component('__compat__transition', Transition)
+    app.component('__compat__transition-group', TransitionGroup)
+    app.component('__compat__keep-alive', KeepAlive)
+    // built-in directives. No need for prefix since there's no render fn API
+    // for resolving directives via string in v3.
+    app._context.directives.show = vShow
+    app._context.directives.model = vModelDynamic
+  }
+  return app
+}
+
+const Vue = compatUtils.createCompatVue(wrappedCreateApp)
 
 Vue.compile = (() => {
   if (__DEV__) {
@@ -29,4 +57,7 @@ Vue.compile = (() => {
 
 extend(Vue, runtimeDom)
 
+// @ts-ignore
+Vue.createApp = wrappedCreateApp
+
 export default Vue