wip: warn v-bind object ordering

This commit is contained in:
Evan You 2021-04-17 16:26:13 -04:00
parent bf41354abd
commit 2c31227e7c
2 changed files with 70 additions and 16 deletions

View File

@ -54,10 +54,10 @@ const deprecationData: Record<CompilerDeprecationTypes, DeprecationData> = {
[CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER]: { [CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER]: {
message: message:
`v-bind="obj" usage is now order sensitive and behaves like JavaScript ` + `v-bind="obj" usage is now order sensitive and behaves like JavaScript ` +
`object spread: it will now overwrite an existing attribute that appears ` + `object spread: it will now overwrite an existing non-mergeable attribute ` +
`before v-bind in the case of conflicting keys. To retain 2.x behavior, ` + `that appears before v-bind in the case of conflict. ` +
`move v-bind to and make it the first attribute. If all occurences ` + `To retain 2.x behavior, move v-bind to make it the first attribute. ` +
`of this warning are working as intended, you can suppress it.`, `You can also suppress this warning if the usage is intended.`,
link: `https://v3.vuejs.org/guide/migration/v-bind.html` link: `https://v3.vuejs.org/guide/migration/v-bind.html`
}, },
@ -98,17 +98,24 @@ function getCompatValue(
} }
} }
export function isCompatEnabled(
key: CompilerDeprecationTypes,
context: ParserContext | TransformContext
) {
const mode = getCompatValue('MODE', context)
const value = getCompatValue(key, context)
// in v3 mode, only enable if explicitly set to true
// otherwise enable for any non-false value
return mode === 3 ? value === true : value !== false
}
export function checkCompatEnabled( export function checkCompatEnabled(
key: CompilerDeprecationTypes, key: CompilerDeprecationTypes,
context: ParserContext | TransformContext, context: ParserContext | TransformContext,
loc: SourceLocation | null, loc: SourceLocation | null,
...args: any[] ...args: any[]
): boolean { ): boolean {
const mode = getCompatValue('MODE', context) const enabled = isCompatEnabled(key, context)
const value = getCompatValue(key, context)
// in v3 mode, only enable if explicitly set to true
// otherwise enable for any non-false value
const enabled = mode === 3 ? value === true : value !== false
if (__DEV__ && enabled) { if (__DEV__ && enabled) {
warnDeprecation(key, context, loc, ...args) warnDeprecation(key, context, loc, ...args)
} }

View File

@ -55,6 +55,11 @@ import {
import { buildSlots } from './vSlot' import { buildSlots } from './vSlot'
import { getConstantType } from './hoistStatic' import { getConstantType } from './hoistStatic'
import { BindingTypes } from '../options' import { BindingTypes } from '../options'
import {
checkCompatEnabled,
CompilerDeprecationTypes,
isCompatEnabled
} from '../compat/compatConfig'
// some directive transforms (e.g. v-model) may return a symbol for runtime // some directive transforms (e.g. v-model) may return a symbol for runtime
// import, which should be used instead of a resolveDirective call. // import, which should be used instead of a resolveDirective call.
@ -450,8 +455,8 @@ export function buildProps(
} else { } else {
// directives // directives
const { name, arg, exp, loc } = prop const { name, arg, exp, loc } = prop
const isBind = name === 'bind' const isVBind = name === 'bind'
const isOn = name === 'on' const isVOn = name === 'on'
// skip v-slot - it is handled by its dedicated transform. // skip v-slot - it is handled by its dedicated transform.
if (name === 'slot') { if (name === 'slot') {
@ -469,17 +474,17 @@ export function buildProps(
// skip v-is and :is on <component> // skip v-is and :is on <component>
if ( if (
name === 'is' || name === 'is' ||
(isBind && isComponentTag(tag) && isBindKey(arg, 'is')) (isVBind && isComponentTag(tag) && isBindKey(arg, 'is'))
) { ) {
continue continue
} }
// skip v-on in SSR compilation // skip v-on in SSR compilation
if (isOn && ssr) { if (isVOn && ssr) {
continue continue
} }
// special case for v-bind and v-on with no argument // special case for v-bind and v-on with no argument
if (!arg && (isBind || isOn)) { if (!arg && (isVBind || isVOn)) {
hasDynamicKeys = true hasDynamicKeys = true
if (exp) { if (exp) {
if (properties.length) { if (properties.length) {
@ -488,7 +493,49 @@ export function buildProps(
) )
properties = [] properties = []
} }
if (isBind) { if (isVBind) {
if (__COMPAT__) {
if (__DEV__) {
const hasOverridableKeys = mergeArgs.some(arg => {
if (arg.type === NodeTypes.JS_OBJECT_EXPRESSION) {
return arg.properties.some(({ key }) => {
if (
key.type !== NodeTypes.SIMPLE_EXPRESSION ||
!key.isStatic
) {
return true
}
return (
key.content !== 'class' &&
key.content !== 'style' &&
!isOn(key.content)
)
})
} else {
// dynamic expression
return true
}
})
if (hasOverridableKeys) {
checkCompatEnabled(
CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER,
context,
loc
)
}
}
if (
isCompatEnabled(
CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER,
context
)
) {
mergeArgs.unshift(exp)
continue
}
}
mergeArgs.push(exp) mergeArgs.push(exp)
} else { } else {
// v-on="obj" -> toHandlers(obj) // v-on="obj" -> toHandlers(obj)
@ -502,7 +549,7 @@ export function buildProps(
} else { } else {
context.onError( context.onError(
createCompilerError( createCompilerError(
isBind isVBind
? ErrorCodes.X_V_BIND_NO_EXPRESSION ? ErrorCodes.X_V_BIND_NO_EXPRESSION
: ErrorCodes.X_V_ON_NO_EXPRESSION, : ErrorCodes.X_V_ON_NO_EXPRESSION,
loc loc