Skip to content

Commit 2c31227

Browse files
committed
wip: warn v-bind object ordering
1 parent bf41354 commit 2c31227

File tree

2 files changed

+70
-16
lines changed

2 files changed

+70
-16
lines changed

packages/compiler-core/src/compat/compatConfig.ts

+16-9
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@ const deprecationData: Record<CompilerDeprecationTypes, DeprecationData> = {
5454
[CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER]: {
5555
message:
5656
`v-bind="obj" usage is now order sensitive and behaves like JavaScript ` +
57-
`object spread: it will now overwrite an existing attribute that appears ` +
58-
`before v-bind in the case of conflicting keys. To retain 2.x behavior, ` +
59-
`move v-bind to and make it the first attribute. If all occurences ` +
60-
`of this warning are working as intended, you can suppress it.`,
57+
`object spread: it will now overwrite an existing non-mergeable attribute ` +
58+
`that appears before v-bind in the case of conflict. ` +
59+
`To retain 2.x behavior, move v-bind to make it the first attribute. ` +
60+
`You can also suppress this warning if the usage is intended.`,
6161
link: `https://v3.vuejs.org/guide/migration/v-bind.html`
6262
},
6363

@@ -98,17 +98,24 @@ function getCompatValue(
9898
}
9999
}
100100

101+
export function isCompatEnabled(
102+
key: CompilerDeprecationTypes,
103+
context: ParserContext | TransformContext
104+
) {
105+
const mode = getCompatValue('MODE', context)
106+
const value = getCompatValue(key, context)
107+
// in v3 mode, only enable if explicitly set to true
108+
// otherwise enable for any non-false value
109+
return mode === 3 ? value === true : value !== false
110+
}
111+
101112
export function checkCompatEnabled(
102113
key: CompilerDeprecationTypes,
103114
context: ParserContext | TransformContext,
104115
loc: SourceLocation | null,
105116
...args: any[]
106117
): boolean {
107-
const mode = getCompatValue('MODE', context)
108-
const value = getCompatValue(key, context)
109-
// in v3 mode, only enable if explicitly set to true
110-
// otherwise enable for any non-false value
111-
const enabled = mode === 3 ? value === true : value !== false
118+
const enabled = isCompatEnabled(key, context)
112119
if (__DEV__ && enabled) {
113120
warnDeprecation(key, context, loc, ...args)
114121
}

packages/compiler-core/src/transforms/transformElement.ts

+54-7
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ import {
5555
import { buildSlots } from './vSlot'
5656
import { getConstantType } from './hoistStatic'
5757
import { BindingTypes } from '../options'
58+
import {
59+
checkCompatEnabled,
60+
CompilerDeprecationTypes,
61+
isCompatEnabled
62+
} from '../compat/compatConfig'
5863

5964
// some directive transforms (e.g. v-model) may return a symbol for runtime
6065
// import, which should be used instead of a resolveDirective call.
@@ -450,8 +455,8 @@ export function buildProps(
450455
} else {
451456
// directives
452457
const { name, arg, exp, loc } = prop
453-
const isBind = name === 'bind'
454-
const isOn = name === 'on'
458+
const isVBind = name === 'bind'
459+
const isVOn = name === 'on'
455460

456461
// skip v-slot - it is handled by its dedicated transform.
457462
if (name === 'slot') {
@@ -469,17 +474,17 @@ export function buildProps(
469474
// skip v-is and :is on <component>
470475
if (
471476
name === 'is' ||
472-
(isBind && isComponentTag(tag) && isBindKey(arg, 'is'))
477+
(isVBind && isComponentTag(tag) && isBindKey(arg, 'is'))
473478
) {
474479
continue
475480
}
476481
// skip v-on in SSR compilation
477-
if (isOn && ssr) {
482+
if (isVOn && ssr) {
478483
continue
479484
}
480485

481486
// special case for v-bind and v-on with no argument
482-
if (!arg && (isBind || isOn)) {
487+
if (!arg && (isVBind || isVOn)) {
483488
hasDynamicKeys = true
484489
if (exp) {
485490
if (properties.length) {
@@ -488,7 +493,49 @@ export function buildProps(
488493
)
489494
properties = []
490495
}
491-
if (isBind) {
496+
if (isVBind) {
497+
if (__COMPAT__) {
498+
if (__DEV__) {
499+
const hasOverridableKeys = mergeArgs.some(arg => {
500+
if (arg.type === NodeTypes.JS_OBJECT_EXPRESSION) {
501+
return arg.properties.some(({ key }) => {
502+
if (
503+
key.type !== NodeTypes.SIMPLE_EXPRESSION ||
504+
!key.isStatic
505+
) {
506+
return true
507+
}
508+
return (
509+
key.content !== 'class' &&
510+
key.content !== 'style' &&
511+
!isOn(key.content)
512+
)
513+
})
514+
} else {
515+
// dynamic expression
516+
return true
517+
}
518+
})
519+
if (hasOverridableKeys) {
520+
checkCompatEnabled(
521+
CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER,
522+
context,
523+
loc
524+
)
525+
}
526+
}
527+
528+
if (
529+
isCompatEnabled(
530+
CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER,
531+
context
532+
)
533+
) {
534+
mergeArgs.unshift(exp)
535+
continue
536+
}
537+
}
538+
492539
mergeArgs.push(exp)
493540
} else {
494541
// v-on="obj" -> toHandlers(obj)
@@ -502,7 +549,7 @@ export function buildProps(
502549
} else {
503550
context.onError(
504551
createCompilerError(
505-
isBind
552+
isVBind
506553
? ErrorCodes.X_V_BIND_NO_EXPRESSION
507554
: ErrorCodes.X_V_ON_NO_EXPRESSION,
508555
loc

0 commit comments

Comments
 (0)