[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`
},
-import { extend, NOOP } from '@vue/shared'
+import { extend, NOOP, toDisplayString, toNumber } from '@vue/shared'
import { PublicPropertiesMap } from '../componentPublicInstance'
import { getCompatChildren } from './instanceChildren'
import {
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) => {
$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)
}
} 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
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
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(
} 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 (
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
--- /dev/null
+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
+}
isCompatEnabled
} from './compatConfig'
-const defaultModelMapping = {
- prop: 'value',
- event: 'input'
-}
-
export const compatModelEventPrefix = `onModelCompat:`
const warnedTypes = new WeakSet()
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
isReservedProp,
EMPTY_ARR,
def,
- extend
+ extend,
+ isOn
} from '@vue/shared'
import { warn } from './warning'
import {
)
}
} else {
+ if (
+ __COMPAT__ &&
+ isOn(key) &&
+ isCompatEnabled(DeprecationTypes.INSTANCE_LISTENERS, instance)
+ ) {
+ continue
+ }
attrs[key] = value
}
}
// 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
}
}
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.`
--- /dev/null
+import Vue from './index'
+export default Vue
+export * from '@vue/runtime-dom'
--- /dev/null
+import Vue from './runtime'
+export default Vue
+export * from '@vue/runtime-dom'
// 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 = []