+import { hasOwn, isArray } from '@vue/shared/src'
import {
ComponentInternalInstance,
+ ComponentOptions,
formatComponentName,
getComponentName,
getCurrentInstance,
COMPONENT_ASYNC = 'COMPONENT_ASYNC',
COMPONENT_FUNCTIONAL = 'COMPONENT_FUNCTIONAL',
+ COMPONENT_V_MODEL = 'COMPONENT_V_MODEL',
RENDER_FUNCTION = 'RENDER_FUNCTION'
}
link: `https://v3.vuejs.org/guide/migration/functional-components.html`
},
+ [DeprecationTypes.COMPONENT_V_MODEL]: {
+ message: (comp: ComponentOptions) => {
+ const configMsg =
+ `opt-in to ` +
+ `Vue 3 behavior on a per-component basis with \`compatConfig: { ${
+ DeprecationTypes.COMPONENT_V_MODEL
+ }: false }\`.`
+ if (
+ comp.props && isArray(comp.props)
+ ? comp.props.includes('modelValue')
+ : hasOwn(comp.props, 'modelValue')
+ ) {
+ return (
+ `Component delcares "modelValue" prop, which is Vue 3 usage, but ` +
+ `is running under Vue 2 compat v-model behavior. You can ${configMsg}`
+ )
+ }
+ return (
+ `v-model usage on component has changed in Vue 3. Component that expects ` +
+ `to work with v-model should now use the "modelValue" prop and emit the ` +
+ `"update:modelValue" event. You can update the usage and then ${configMsg}`
+ )
+ },
+ link: `https://v3.vuejs.org/guide/migration/v-model.html`
+ },
+
[DeprecationTypes.RENDER_FUNCTION]: {
message:
`Vue 3's render function API has changed. ` +
--- /dev/null
+import { ShapeFlags } from '@vue/shared'
+import { ComponentInternalInstance, ComponentOptions } from '../component'
+import { callWithErrorHandling, ErrorCodes } from '../errorHandling'
+import { VNode } from '../vnode'
+import { popWarningContext, pushWarningContext } from '../warning'
+import { isCompatEnabled } from './compatConfig'
+import { DeprecationTypes, warnDeprecation } from './deprecations'
+
+const defaultModelMapping = {
+ prop: 'value',
+ event: 'input'
+}
+
+export const compatModelEventPrefix = `onModelCompat:`
+
+const warnedTypes = new WeakSet()
+
+export function convertLegacyVModelProps(vnode: VNode) {
+ const { type, shapeFlag, props, dynamicProps } = vnode
+ if (shapeFlag & ShapeFlags.COMPONENT && props && 'modelValue' in props) {
+ if (
+ !isCompatEnabled(
+ DeprecationTypes.COMPONENT_V_MODEL,
+ // this is a special case where we want to use the vnode component's
+ // compat config instead of the current rendering instance (which is the
+ // parent of the component that exposes v-model)
+ { type } as any
+ )
+ ) {
+ return
+ }
+
+ if (__DEV__ && !warnedTypes.has(type as ComponentOptions)) {
+ pushWarningContext(vnode)
+ warnDeprecation(DeprecationTypes.COMPONENT_V_MODEL, { type } as any, type)
+ popWarningContext()
+ warnedTypes.add(type as ComponentOptions)
+ }
+
+ const { prop, event } = (type as any).model || defaultModelMapping
+ props[prop] = props.modelValue
+ delete props.modelValue
+ // important: update dynamic props
+ if (dynamicProps) {
+ dynamicProps[dynamicProps.indexOf('modelValue')] = prop
+ }
+
+ props[compatModelEventPrefix + event] = props['onUpdate:modelValue']
+ delete props['onUpdate:modelValue']
+ }
+}
+
+export function compatModelEmit(
+ instance: ComponentInternalInstance,
+ event: string,
+ args: any[]
+) {
+ if (!isCompatEnabled(DeprecationTypes.COMPONENT_V_MODEL, instance)) {
+ return
+ }
+ const props = instance.vnode.props
+ const modelHandler = props && props[compatModelEventPrefix + event]
+ if (modelHandler) {
+ callWithErrorHandling(
+ modelHandler,
+ instance,
+ ErrorCodes.COMPONENT_EVENT_HANDLER,
+ args
+ )
+ }
+}
if (
__COMPAT__ &&
Component.render &&
- isCompatEnabled(DeprecationTypes.RENDER_FUNCTION)
+ isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance)
) {
- warnDeprecation(DeprecationTypes.RENDER_FUNCTION)
+ warnDeprecation(DeprecationTypes.RENDER_FUNCTION, instance)
const originalRender = Component.render
Component.render = function compatRender() {
return originalRender.call(this, compatH)
import { UnionToIntersection } from './helpers/typeUtils'
import { devtoolsComponentEmit } from './devtools'
import { AppContext } from './apiCreateApp'
-import { emit as compatEmit } from './compat/instanceEventEmitter'
+import { emit as compatInstanceEmit } from './compat/instanceEventEmitter'
+import { compatModelEventPrefix, compatModelEmit } from './compat/vModel'
export type ObjectEmitsOptions = Record<
string,
propsOptions: [propsOptions]
} = instance
if (emitsOptions) {
- if (!(event in emitsOptions) && !event.startsWith('hook:')) {
+ if (
+ !(event in emitsOptions) &&
+ !(
+ __COMPAT__ &&
+ (event.startsWith('hook:') ||
+ event.startsWith(compatModelEventPrefix))
+ )
+ ) {
if (!propsOptions || !(toHandlerKey(event) in propsOptions)) {
warn(
`Component emitted event "${event}" but it is neither declared in ` +
}
if (__COMPAT__) {
- return compatEmit(instance, event, args)
+ compatModelEmit(instance, event, args)
+ return compatInstanceEmit(instance, event, args)
}
}
value = propsDefaults[key] = defaultValue.call(
__COMPAT__ &&
__DEV__ &&
- isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS)
+ isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance)
? createPropsDefaultThis(key)
: null,
props
import { hmrDirtyComponents } from './hmr'
import { setCompiledSlotRendering } from './helpers/renderSlot'
import { convertLegacyComponent } from './compat/component'
+import { convertLegacyVModelProps } from './compat/vModel'
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
__isFragment: true
currentBlock.push(vnode)
}
+ if (__COMPAT__) {
+ convertLegacyVModelProps(vnode)
+ }
+
return vnode
}