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
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
[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`
},
data: instance.vnode.props || {},
scopedSlots: ctx.slots,
parent: instance.parent && instance.parent.proxy,
- get slots() {
+ slots() {
return new Proxy(ctx.slots, legacySlotProxyHandlers)
},
get listeners() {
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) => {
_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)
}
+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)
+ }
+ }
}
}
)
import {
extend,
+ hyphenate,
isArray,
isObject,
+ makeMap,
normalizeClass,
normalizeStyle,
ShapeFlags,
Data,
InternalRenderFunction
} from '../component'
+import { currentRenderingInstance } from '../componentRenderContext'
import { DirectiveArguments, withDirectives } from '../directives'
import {
resolveDirective,
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/
props?: Record<string, unknown>
slot?: string
scopedSlots?: Record<string, Function>
+ model?: {
+ value: any
+ callback: (v: any) => void
+ expression: string
+ }
}
interface LegacyVNodeDirective {
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]))
// props without children
return convertLegacySlots(
convertLegacyDirectives(
- createVNode(type, convertLegacyProps(propsOrChildren)),
+ createVNode(type, convertLegacyProps(propsOrChildren, type)),
propsOrChildren
)
)
}
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
}
}
}
- } else if (
- key !== 'refInFor' &&
- key !== 'staticStyle' &&
- key !== 'staticClass'
- ) {
+ } else if (!skipLegacyRootLevelProps(key)) {
converted[key] = legacyProps[key as keyof LegacyVNodeProps]
}
}
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
}
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) {
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
+ })
+ }
+ }
+ }
+ })
+ }
+}
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,
return data
}
+export function legacyBindObjectListeners(props: any, listeners: any) {
+ return mergeProps(props, toHandlers(listeners))
+}
+
export function legacyRenderSlot(
instance: ComponentInternalInstance,
name: string,
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[]
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
}
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
if (dynamicProps) {
dynamicProps[dynamicProps.indexOf('modelValue')] = prop
}
-
props[compatModelEventPrefix + event] = props['onUpdate:modelValue']
delete props['onUpdate:modelValue']
}
setCurrentInstance(instance)
value = propsDefaults[key] = defaultValue.call(
__COMPAT__ &&
- __DEV__ &&
isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance)
- ? createPropsDefaultThis(key)
+ ? createPropsDefaultThis(instance, props, key)
: null,
props
)
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
}
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[]
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
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.`
// 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 = {}
}
} else {
needDeletionCheck = !(children as RawSlots).$stable
- normalizeObjectSlots(children as RawSlots, slots)
+ normalizeObjectSlots(children as RawSlots, slots, instance)
}
deletionComparisonTarget = children as RawSlots
} else if (children) {
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
if (__COMPAT__) {
convertLegacyVModelProps(vnode)
+ defineLegacyVNodeProperties(vnode)
}
return vnode
// 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,
el: vnode.el,
anchor: vnode.anchor
}
+ if (__COMPAT__) {
+ defineLegacyVNodeProperties(cloned)
+ }
+ return cloned as any
}
/**
// 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()
registerRuntimeCompiler(compileToFunction)
-const Vue = compatUtils.createCompatVue(createApp)
-
Vue.compile = compileToFunction
-extend(Vue, runtimeDom)
-
export default Vue
// 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__) {
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__) {
extend(Vue, runtimeDom)
+// @ts-ignore
+Vue.createApp = wrappedCreateApp
+
export default Vue