]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: compat integration progress
authorEvan You <yyx990803@gmail.com>
Tue, 20 Apr 2021 13:25:12 +0000 (09:25 -0400)
committerEvan You <yyx990803@gmail.com>
Tue, 20 Apr 2021 13:25:12 +0000 (09:25 -0400)
packages/runtime-core/src/compat/compatConfig.ts
packages/runtime-core/src/compat/instance.ts
packages/runtime-core/src/compat/renderFn.ts
packages/runtime-core/src/compat/renderHelpers.ts [new file with mode: 0644]
packages/runtime-core/src/compat/vModel.ts
packages/runtime-core/src/componentProps.ts
packages/runtime-core/src/componentSlots.ts
packages/vue-compat/src/esm-index.ts [new file with mode: 0644]
packages/vue-compat/src/esm-runtime.ts [new file with mode: 0644]
rollup.config.js

index 5ebe077059e0faffb322c9c1c2bb8d351e085cf9..25a387f1deebf4795b0aa8795886b7a3183d7aca 100644 (file)
@@ -203,9 +203,10 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
 
   [DeprecationTypes.INSTANCE_LISTENERS]: {
     message:
-      `vm.$listeners has been removed. Parent v-on listeners are now ` +
+      `vm.$listeners has been removed. In Vue 3, parent v-on listeners are ` +
       `included in vm.$attrs and it is no longer necessary to separately use ` +
-      `v-on="$listeners" if you are already using v-bind="$attrs".`,
+      `v-on="$listeners" if you are already using v-bind="$attrs". ` +
+      `(Note: the Vue 3 behavior only applies if this compat config is disabled)`,
     link: `https://v3.vuejs.org/guide/migration/listeners-removed.html`
   },
 
index 60144fa6f32da023444ba8c3764a2f620b892553..b324d7f7044ae5d7652b068341f85082d644edfb 100644 (file)
@@ -1,4 +1,4 @@
-import { extend, NOOP } from '@vue/shared'
+import { extend, NOOP, toDisplayString, toNumber } from '@vue/shared'
 import { PublicPropertiesMap } from '../componentPublicInstance'
 import { getCompatChildren } from './instanceChildren'
 import {
@@ -11,6 +11,14 @@ import { off, on, once } from './instanceEventEmitter'
 import { getCompatListeners } from './instanceListeners'
 import { shallowReadonly } from '@vue/reactivity'
 import { legacySlotProxyHandlers } from './component'
+import { compatH } from './renderFn'
+import {
+  legacyBindObjectProps,
+  legacyRenderSlot,
+  legacyRenderStatic
+} 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) => {
@@ -77,6 +85,19 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
     $off: i => off.bind(null, i),
 
     $children: getCompatChildren,
-    $listeners: getCompatListeners
+    $listeners: getCompatListeners,
+
+    // v2 render helpers
+    $createElement: () => compatH,
+    _self: i => i.proxy,
+    _c: () => compatH,
+    _n: () => toNumber,
+    _s: () => toDisplayString,
+    _l: () => renderList,
+    _t: i => legacyRenderSlot.bind(null, i),
+    _b: () => legacyBindObjectProps,
+    _e: () => createCommentVNode,
+    _v: () => createTextVNode,
+    _m: i => legacyRenderStatic.bind(null, i)
   } as PublicPropertiesMap)
 }
index 458d9e4e52b93315daa458794a25bd2658372650..29180f633a46022019f8ae914c2c5172e5ef64c2 100644 (file)
@@ -29,6 +29,8 @@ import {
 } from '../vnode'
 import { checkCompatEnabled, DeprecationTypes } from './compatConfig'
 
+const v3CompiledRenderFnRE = /^(?:function \w+)?\(_ctx, _cache/
+
 export function convertLegacyRenderFn(instance: ComponentInternalInstance) {
   const Component = instance.type as ComponentOptions
   const render = Component.render as InternalRenderFunction | undefined
@@ -38,8 +40,7 @@ export function convertLegacyRenderFn(instance: ComponentInternalInstance) {
     return
   }
 
-  const string = render.toString()
-  if (string.startsWith('function render(_ctx') || string.startsWith('(_ctx')) {
+  if (v3CompiledRenderFnRE.test(render.toString())) {
     // v3 pre-compiled function
     render._compatChecked = true
     return
@@ -128,9 +129,7 @@ export function compatH(
       return convertLegacySlots(createVNode(type, null, propsOrChildren))
     }
   } else {
-    if (l > 3) {
-      children = Array.prototype.slice.call(arguments, 2)
-    } else if (l === 3 && isVNode(children)) {
+    if (isVNode(children)) {
       children = [children]
     }
     return convertLegacySlots(
@@ -157,13 +156,20 @@ function convertLegacyProps(
     } else if (key === 'on' || key === 'nativeOn') {
       const listeners = legacyProps[key]
       for (const event in listeners) {
-        const handlerKey = toHandlerKey(event)
+        const handlerKey = convertLegacyEventKey(event)
         const existing = converted[handlerKey]
         const incoming = listeners[event]
         if (existing !== incoming) {
-          converted[handlerKey] = existing
-            ? [].concat(existing as any, incoming as any)
-            : incoming
+          if (existing) {
+            // for the rare case where the same handler is attached
+            // twice with/without .native modifier...
+            if (key === 'nativeOn' && String(existing) === String(incoming)) {
+              continue
+            }
+            converted[handlerKey] = [].concat(existing as any, incoming as any)
+          } else {
+            converted[handlerKey] = incoming
+          }
         }
       }
     } else if (
@@ -185,6 +191,20 @@ function convertLegacyProps(
   return converted
 }
 
+function convertLegacyEventKey(event: string): string {
+  // normalize v2 event prefixes
+  if (event[0] === '&') {
+    event = event.slice(1) + 'Passive'
+  }
+  if (event[0] === '~') {
+    event = event.slice(1) + 'Once'
+  }
+  if (event[0] === '!') {
+    event = event.slice(1) + 'Capture'
+  }
+  return toHandlerKey(event)
+}
+
 function convertLegacyDirectives(
   vnode: VNode,
   props?: LegacyVNodeProps
diff --git a/packages/runtime-core/src/compat/renderHelpers.ts b/packages/runtime-core/src/compat/renderHelpers.ts
new file mode 100644 (file)
index 0000000..0f38a24
--- /dev/null
@@ -0,0 +1,94 @@
+import {
+  camelize,
+  extend,
+  hyphenate,
+  isArray,
+  isObject,
+  isReservedProp,
+  normalizeClass
+} from '@vue/shared'
+import { ComponentInternalInstance } from '../component'
+import { renderSlot } from '../helpers/renderSlot'
+import { mergeProps, VNode } from '../vnode'
+
+export function legacyBindObjectProps(
+  data: any,
+  _tag: string,
+  value: any,
+  _asProp: boolean,
+  isSync?: boolean
+) {
+  if (value && isObject(value)) {
+    if (isArray(value)) {
+      value = toObject(value)
+    }
+    for (const key in value) {
+      if (isReservedProp(key)) {
+        data[key] = value[key]
+      } else if (key === 'class') {
+        data.class = normalizeClass([data.class, value.class])
+      } else if (key === 'style') {
+        data.style = normalizeClass([data.style, value.style])
+      } else {
+        const attrs = data.attrs || (data.attrs = {})
+        const camelizedKey = camelize(key)
+        const hyphenatedKey = hyphenate(key)
+        if (!(camelizedKey in attrs) && !(hyphenatedKey in attrs)) {
+          attrs[key] = value[key]
+
+          if (isSync) {
+            const on = data.on || (data.on = {})
+            on[`update:${key}`] = function($event: any) {
+              value[key] = $event
+            }
+          }
+        }
+      }
+    }
+  }
+  return data
+}
+
+export function legacyRenderSlot(
+  instance: ComponentInternalInstance,
+  name: string,
+  fallback?: VNode[],
+  props?: any,
+  bindObject?: any
+) {
+  if (bindObject) {
+    props = mergeProps(props, bindObject)
+  }
+  return renderSlot(instance.slots, name, props, fallback && (() => fallback))
+}
+
+const staticCacheMap = /*#__PURE__*/ new WeakMap<
+  ComponentInternalInstance,
+  any[]
+>()
+
+export function legacyRenderStatic(
+  instance: ComponentInternalInstance,
+  index: number
+) {
+  let cache = staticCacheMap.get(instance)
+  if (!cache) {
+    staticCacheMap.set(instance, (cache = []))
+  }
+  if (cache[index]) {
+    return cache[index]
+  }
+  const fn = (instance.type as any).staticRenderFns[index]
+  const ctx = instance.proxy
+  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])
+    }
+  }
+  return res
+}
index c359d17c87a5fda8d062a7b6bf4615ae3e3d25b0..d24f06b6202416731954f853f681cd3c967d5bec 100644 (file)
@@ -9,11 +9,6 @@ import {
   isCompatEnabled
 } from './compatConfig'
 
-const defaultModelMapping = {
-  prop: 'value',
-  event: 'input'
-}
-
 export const compatModelEventPrefix = `onModelCompat:`
 
 const warnedTypes = new WeakSet()
@@ -40,7 +35,7 @@ export function convertLegacyVModelProps(vnode: VNode) {
       warnedTypes.add(type as ComponentOptions)
     }
 
-    const { prop, event } = (type as any).model || defaultModelMapping
+    const { prop = 'value', event = 'input' } = (type as any).model || {}
     props[prop] = props.modelValue
     delete props.modelValue
     // important: update dynamic props
index 40765ba9f6992ee8b5acbaa6e4ae9c6e8c42a040..ef08c3ad41298b3e8276c75dd62fb924fb8a57b1 100644 (file)
@@ -20,7 +20,8 @@ import {
   isReservedProp,
   EMPTY_ARR,
   def,
-  extend
+  extend,
+  isOn
 } from '@vue/shared'
 import { warn } from './warning'
 import {
@@ -224,6 +225,13 @@ export function updateProps(
             )
           }
         } else {
+          if (
+            __COMPAT__ &&
+            isOn(key) &&
+            isCompatEnabled(DeprecationTypes.INSTANCE_LISTENERS, instance)
+          ) {
+            continue
+          }
           attrs[key] = value
         }
       }
@@ -320,6 +328,13 @@ function setFullProps(
         // Any non-declared (either as a prop or an emitted event) props are put
         // into a separate `attrs` object for spreading. Make sure to preserve
         // original key casing
+        if (
+          __COMPAT__ &&
+          isOn(key) &&
+          isCompatEnabled(DeprecationTypes.INSTANCE_LISTENERS, instance)
+        ) {
+          continue
+        }
         attrs[key] = value
       }
     }
index f1eacd8703d175df30beb81ced514f96ff20e55d..ab9b1fbc60ec09b6af2eaad136186ded5f22f187 100644 (file)
@@ -80,7 +80,7 @@ const normalizeObjectSlots = (rawSlots: RawSlots, slots: InternalSlots) => {
     if (isFunction(value)) {
       slots[key] = normalizeSlot(key, value, ctx)
     } else if (value != null) {
-      if (__DEV__) {
+      if (__DEV__ && !__COMPAT__) {
         warn(
           `Non-function value encountered for slot "${key}". ` +
             `Prefer function slots for better performance.`
diff --git a/packages/vue-compat/src/esm-index.ts b/packages/vue-compat/src/esm-index.ts
new file mode 100644 (file)
index 0000000..c3caa4a
--- /dev/null
@@ -0,0 +1,3 @@
+import Vue from './index'
+export default Vue
+export * from '@vue/runtime-dom'
diff --git a/packages/vue-compat/src/esm-runtime.ts b/packages/vue-compat/src/esm-runtime.ts
new file mode 100644 (file)
index 0000000..aae95db
--- /dev/null
@@ -0,0 +1,3 @@
+import Vue from './runtime'
+export default Vue
+export * from '@vue/runtime-dom'
index 844d912e1e9aa26e5c38b7057668025f82437300..5c5d3b90ef504d25e28e4def50d13095e9f8982e 100644 (file)
@@ -116,7 +116,16 @@ function createConfig(format, output, plugins = []) {
   // during a single build.
   hasTSChecked = true
 
-  const entryFile = /runtime$/.test(format) ? `src/runtime.ts` : `src/index.ts`
+  let entryFile = /runtime$/.test(format) ? `src/runtime.ts` : `src/index.ts`
+
+  // the compat build needs both default AND named exports. This will cause
+  // Rollup to complain for non-ESM targets, so we use separate entries for
+  // esm vs. non-esm builds.
+  if (isCompatBuild && (isBrowserESMBuild || isBundlerESMBuild)) {
+    entryFile = /runtime$/.test(format)
+      ? `src/esm-runtime.ts`
+      : `src/esm-index.ts`
+  }
 
   let external = []