Skip to content

Commit 052a621

Browse files
authored
feat(compile-core): handle falsy dynamic args for v-on and v-bind (#2393)
fix #2388
1 parent 7390487 commit 052a621

File tree

15 files changed

+95
-70
lines changed

15 files changed

+95
-70
lines changed

packages/compiler-core/__tests__/transforms/vBind.spec.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ describe('compiler: transform v-bind', () => {
7171
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
7272
expect(props.properties[0]).toMatchObject({
7373
key: {
74-
content: `id`,
74+
content: `id || ""`,
7575
isStatic: false
7676
},
7777
value: {
@@ -130,7 +130,7 @@ describe('compiler: transform v-bind', () => {
130130
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
131131
expect(props.properties[0]).toMatchObject({
132132
key: {
133-
content: `_${helperNameMap[CAMELIZE]}(foo)`,
133+
content: `_${helperNameMap[CAMELIZE]}(foo || "")`,
134134
isStatic: false
135135
},
136136
value: {
@@ -149,10 +149,12 @@ describe('compiler: transform v-bind', () => {
149149
key: {
150150
children: [
151151
`_${helperNameMap[CAMELIZE]}(`,
152+
`(`,
152153
{ content: `_ctx.foo` },
153154
`(`,
154155
{ content: `_ctx.bar` },
155156
`)`,
157+
`) || ""`,
156158
`)`
157159
]
158160
},

packages/compiler-core/__tests__/transforms/vOn.spec.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import {
22
baseParse as parse,
3-
transform,
4-
ElementNode,
5-
ObjectExpression,
63
CompilerOptions,
4+
ElementNode,
75
ErrorCodes,
8-
NodeTypes,
9-
VNodeCall,
6+
TO_HANDLER_KEY,
107
helperNameMap,
11-
CAPITALIZE
8+
NodeTypes,
9+
ObjectExpression,
10+
transform,
11+
VNodeCall
1212
} from '../../src'
1313
import { transformOn } from '../../src/transforms/vOn'
1414
import { transformElement } from '../../src/transforms/transformElement'
@@ -76,7 +76,7 @@ describe('compiler: transform v-on', () => {
7676
key: {
7777
type: NodeTypes.COMPOUND_EXPRESSION,
7878
children: [
79-
`"on" + _${helperNameMap[CAPITALIZE]}(`,
79+
`_${helperNameMap[TO_HANDLER_KEY]}(`,
8080
{ content: `event` },
8181
`)`
8282
]
@@ -101,7 +101,7 @@ describe('compiler: transform v-on', () => {
101101
key: {
102102
type: NodeTypes.COMPOUND_EXPRESSION,
103103
children: [
104-
`"on" + _${helperNameMap[CAPITALIZE]}(`,
104+
`_${helperNameMap[TO_HANDLER_KEY]}(`,
105105
{ content: `_ctx.event` },
106106
`)`
107107
]
@@ -126,7 +126,7 @@ describe('compiler: transform v-on', () => {
126126
key: {
127127
type: NodeTypes.COMPOUND_EXPRESSION,
128128
children: [
129-
`"on" + _${helperNameMap[CAPITALIZE]}(`,
129+
`_${helperNameMap[TO_HANDLER_KEY]}(`,
130130
{ content: `_ctx.event` },
131131
`(`,
132132
{ content: `_ctx.foo` },

packages/compiler-core/src/runtimeHelpers.ts

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const MERGE_PROPS = Symbol(__DEV__ ? `mergeProps` : ``)
2323
export const TO_HANDLERS = Symbol(__DEV__ ? `toHandlers` : ``)
2424
export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``)
2525
export const CAPITALIZE = Symbol(__DEV__ ? `capitalize` : ``)
26+
export const TO_HANDLER_KEY = Symbol(__DEV__ ? `toHandlerKey` : ``)
2627
export const SET_BLOCK_TRACKING = Symbol(__DEV__ ? `setBlockTracking` : ``)
2728
export const PUSH_SCOPE_ID = Symbol(__DEV__ ? `pushScopeId` : ``)
2829
export const POP_SCOPE_ID = Symbol(__DEV__ ? `popScopeId` : ``)
@@ -56,6 +57,7 @@ export const helperNameMap: any = {
5657
[TO_HANDLERS]: `toHandlers`,
5758
[CAMELIZE]: `camelize`,
5859
[CAPITALIZE]: `capitalize`,
60+
[TO_HANDLER_KEY]: `toHandlerKey`,
5961
[SET_BLOCK_TRACKING]: `setBlockTracking`,
6062
[PUSH_SCOPE_ID]: `pushScopeId`,
6163
[POP_SCOPE_ID]: `popScopeId`,

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

+8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ import { CAMELIZE } from '../runtimeHelpers'
1010
export const transformBind: DirectiveTransform = (dir, node, context) => {
1111
const { exp, modifiers, loc } = dir
1212
const arg = dir.arg!
13+
14+
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {
15+
arg.children.unshift(`(`)
16+
arg.children.push(`) || ""`)
17+
} else if (!arg.isStatic) {
18+
arg.content = `${arg.content} || ""`
19+
}
20+
1321
// .prop is no longer necessary due to new patch behavior
1422
// .sync is replaced by v-model:arg
1523
if (modifiers.includes('camel')) {

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

+15-11
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import { DirectiveTransform, DirectiveTransformResult } from '../transform'
22
import {
3-
DirectiveNode,
3+
createCompoundExpression,
44
createObjectProperty,
55
createSimpleExpression,
6+
DirectiveNode,
7+
ElementTypes,
68
ExpressionNode,
79
NodeTypes,
8-
createCompoundExpression,
9-
SimpleExpressionNode,
10-
ElementTypes
10+
SimpleExpressionNode
1111
} from '../ast'
12-
import { capitalize, camelize } from '@vue/shared'
12+
import { camelize, toHandlerKey } from '@vue/shared'
1313
import { createCompilerError, ErrorCodes } from '../errors'
1414
import { processExpression } from './transformExpression'
1515
import { validateBrowserExpression } from '../validateExpression'
16-
import { isMemberExpression, hasScopeRef } from '../utils'
17-
import { CAPITALIZE } from '../runtimeHelpers'
16+
import { hasScopeRef, isMemberExpression } from '../utils'
17+
import { TO_HANDLER_KEY } from '../runtimeHelpers'
1818

1919
const fnExpRE = /^\s*([\w$_]+|\([^)]*?\))\s*=>|^\s*function(?:\s+[\w$]+)?\s*\(/
2020

@@ -43,19 +43,23 @@ export const transformOn: DirectiveTransform = (
4343
if (arg.isStatic) {
4444
const rawName = arg.content
4545
// for all event listeners, auto convert it to camelCase. See issue #2249
46-
const normalizedName = capitalize(camelize(rawName))
47-
eventName = createSimpleExpression(`on${normalizedName}`, true, arg.loc)
46+
eventName = createSimpleExpression(
47+
toHandlerKey(camelize(rawName)),
48+
true,
49+
arg.loc
50+
)
4851
} else {
52+
// #2388
4953
eventName = createCompoundExpression([
50-
`"on" + ${context.helperString(CAPITALIZE)}(`,
54+
`${context.helperString(TO_HANDLER_KEY)}(`,
5155
arg,
5256
`)`
5357
])
5458
}
5559
} else {
5660
// already a compound expression.
5761
eventName = arg
58-
eventName.children.unshift(`"on" + ${context.helperString(CAPITALIZE)}(`)
62+
eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`)
5963
eventName.children.push(`)`)
6064
}
6165

packages/compiler-dom/__tests__/transforms/vOn.spec.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import {
22
baseParse as parse,
3-
transform,
43
CompilerOptions,
54
ElementNode,
6-
ObjectExpression,
7-
NodeTypes,
8-
VNodeCall,
5+
TO_HANDLER_KEY,
96
helperNameMap,
10-
CAPITALIZE
7+
NodeTypes,
8+
ObjectExpression,
9+
transform,
10+
VNodeCall
1111
} from '@vue/compiler-core'
1212
import { transformOn } from '../../src/transforms/vOn'
13-
import { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from '../../src/runtimeHelpers'
13+
import { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from '../../src/runtimeHelpers'
1414
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
1515
import { transformExpression } from '../../../compiler-core/src/transforms/transformExpression'
1616
import { genFlagText } from '../../../compiler-core/__tests__/testUtils'
@@ -195,22 +195,22 @@ describe('compiler-dom: transform v-on', () => {
195195
const {
196196
props: [prop2]
197197
} = parseWithVOn(`<div @[event].right="test"/>`)
198-
// ("on" + (event)).toLowerCase() === "onclick" ? "onContextmenu" : ("on" + (event))
198+
// (_toHandlerKey(event)).toLowerCase() === "onclick" ? "onContextmenu" : (_toHandlerKey(event))
199199
expect(prop2.key).toMatchObject({
200200
type: NodeTypes.COMPOUND_EXPRESSION,
201201
children: [
202202
`(`,
203203
{
204204
children: [
205-
`"on" + _${helperNameMap[CAPITALIZE]}(`,
205+
`_${helperNameMap[TO_HANDLER_KEY]}(`,
206206
{ content: 'event' },
207207
`)`
208208
]
209209
},
210210
`) === "onClick" ? "onContextmenu" : (`,
211211
{
212212
children: [
213-
`"on" + _${helperNameMap[CAPITALIZE]}(`,
213+
`_${helperNameMap[TO_HANDLER_KEY]}(`,
214214
{ content: 'event' },
215215
`)`
216216
]
@@ -233,22 +233,22 @@ describe('compiler-dom: transform v-on', () => {
233233
const {
234234
props: [prop2]
235235
} = parseWithVOn(`<div @[event].middle="test"/>`)
236-
// ("on" + (event)).toLowerCase() === "onclick" ? "onMouseup" : ("on" + (event))
236+
// (_eventNaming(event)).toLowerCase() === "onclick" ? "onMouseup" : (_eventNaming(event))
237237
expect(prop2.key).toMatchObject({
238238
type: NodeTypes.COMPOUND_EXPRESSION,
239239
children: [
240240
`(`,
241241
{
242242
children: [
243-
`"on" + _${helperNameMap[CAPITALIZE]}(`,
243+
`_${helperNameMap[TO_HANDLER_KEY]}(`,
244244
{ content: 'event' },
245245
`)`
246246
]
247247
},
248248
`) === "onClick" ? "onMouseup" : (`,
249249
{
250250
children: [
251-
`"on" + _${helperNameMap[CAPITALIZE]}(`,
251+
`_${helperNameMap[TO_HANDLER_KEY]}(`,
252252
{ content: 'event' },
253253
`)`
254254
]

packages/compiler-ssr/__tests__/ssrElement.spec.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ describe('ssr: element', () => {
161161
expect(getCompiledString(`<div v-bind:[key]="value"></div>`))
162162
.toMatchInlineSnapshot(`
163163
"\`<div\${
164-
_ssrRenderAttrs({ [_ctx.key]: _ctx.value })
164+
_ssrRenderAttrs({ [_ctx.key || \\"\\"]: _ctx.value })
165165
}></div>\`"
166166
`)
167167

@@ -170,7 +170,7 @@ describe('ssr: element', () => {
170170
"\`<div\${
171171
_ssrRenderAttrs({
172172
class: \\"foo\\",
173-
[_ctx.key]: _ctx.value
173+
[_ctx.key || \\"\\"]: _ctx.value
174174
})
175175
}></div>\`"
176176
`)
@@ -180,7 +180,7 @@ describe('ssr: element', () => {
180180
"\`<div\${
181181
_ssrRenderAttrs({
182182
id: _ctx.id,
183-
[_ctx.key]: _ctx.value
183+
[_ctx.key || \\"\\"]: _ctx.value
184184
})
185185
}></div>\`"
186186
`)
@@ -212,7 +212,7 @@ describe('ssr: element', () => {
212212
expect(getCompiledString(`<div :[key]="id" v-bind="obj"></div>`))
213213
.toMatchInlineSnapshot(`
214214
"\`<div\${
215-
_ssrRenderAttrs(_mergeProps({ [_ctx.key]: _ctx.id }, _ctx.obj))
215+
_ssrRenderAttrs(_mergeProps({ [_ctx.key || \\"\\"]: _ctx.id }, _ctx.obj))
216216
}></div>\`"
217217
`)
218218

packages/runtime-core/src/apiLifecycle.ts

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import {
22
ComponentInternalInstance,
3-
LifecycleHooks,
43
currentInstance,
5-
setCurrentInstance,
6-
isInSSRComponentSetup
4+
isInSSRComponentSetup,
5+
LifecycleHooks,
6+
setCurrentInstance
77
} from './component'
88
import { ComponentPublicInstance } from './componentPublicInstance'
99
import { callWithAsyncErrorHandling, ErrorTypeStrings } from './errorHandling'
1010
import { warn } from './warning'
11-
import { capitalize } from '@vue/shared'
12-
import { pauseTracking, resetTracking, DebuggerEvent } from '@vue/reactivity'
11+
import { toHandlerKey } from '@vue/shared'
12+
import { DebuggerEvent, pauseTracking, resetTracking } from '@vue/reactivity'
1313

1414
export { onActivated, onDeactivated } from './components/KeepAlive'
1515

@@ -49,9 +49,7 @@ export function injectHook(
4949
}
5050
return wrappedHook
5151
} else if (__DEV__) {
52-
const apiName = `on${capitalize(
53-
ErrorTypeStrings[type].replace(/ hook$/, '')
54-
)}`
52+
const apiName = toHandlerKey(ErrorTypeStrings[type].replace(/ hook$/, ''))
5553
warn(
5654
`${apiName} is called when there is no active component instance to be ` +
5755
`associated with. ` +

packages/runtime-core/src/componentEmits.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import {
2-
isArray,
3-
isOn,
4-
hasOwn,
2+
camelize,
53
EMPTY_OBJ,
6-
capitalize,
4+
toHandlerKey,
5+
extend,
6+
hasOwn,
77
hyphenate,
8+
isArray,
89
isFunction,
9-
extend,
10-
camelize
10+
isOn
1111
} from '@vue/shared'
1212
import {
1313
ComponentInternalInstance,
@@ -56,10 +56,10 @@ export function emit(
5656
} = instance
5757
if (emitsOptions) {
5858
if (!(event in emitsOptions)) {
59-
if (!propsOptions || !(`on` + capitalize(event) in propsOptions)) {
59+
if (!propsOptions || !(toHandlerKey(event) in propsOptions)) {
6060
warn(
6161
`Component emitted event "${event}" but it is neither declared in ` +
62-
`the emits option nor as an "on${capitalize(event)}" prop.`
62+
`the emits option nor as an "${toHandlerKey(event)}" prop.`
6363
)
6464
}
6565
} else {
@@ -82,7 +82,7 @@ export function emit(
8282

8383
if (__DEV__) {
8484
const lowerCaseEvent = event.toLowerCase()
85-
if (lowerCaseEvent !== event && props[`on` + capitalize(lowerCaseEvent)]) {
85+
if (lowerCaseEvent !== event && props[toHandlerKey(lowerCaseEvent)]) {
8686
warn(
8787
`Event "${lowerCaseEvent}" is emitted in component ` +
8888
`${formatComponentName(
@@ -97,12 +97,12 @@ export function emit(
9797
}
9898

9999
// convert handler name to camelCase. See issue #2249
100-
let handlerName = `on${capitalize(camelize(event))}`
100+
let handlerName = toHandlerKey(camelize(event))
101101
let handler = props[handlerName]
102102
// for v-model update:xxx events, also trigger kebab-case equivalent
103103
// for props passed via kebab-case
104104
if (!handler && event.startsWith('update:')) {
105-
handlerName = `on${capitalize(hyphenate(event))}`
105+
handlerName = toHandlerKey(hyphenate(event))
106106
handler = props[handlerName]
107107
}
108108
if (!handler) {

packages/runtime-core/src/helpers/toHandlers.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isObject, capitalize } from '@vue/shared'
1+
import { toHandlerKey, isObject } from '@vue/shared'
22
import { warn } from '../warning'
33

44
/**
@@ -12,7 +12,7 @@ export function toHandlers(obj: Record<string, any>): Record<string, any> {
1212
return ret
1313
}
1414
for (const key in obj) {
15-
ret[`on${capitalize(key)}`] = obj[key]
15+
ret[toHandlerKey(key)] = obj[key]
1616
}
1717
return ret
1818
}

packages/runtime-core/src/index.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,12 @@ export {
240240
createCommentVNode,
241241
createStaticVNode
242242
} from './vnode'
243-
export { toDisplayString, camelize, capitalize } from '@vue/shared'
243+
export {
244+
toDisplayString,
245+
camelize,
246+
capitalize,
247+
toHandlerKey
248+
} from '@vue/shared'
244249

245250
// For test-utils
246251
export { transformVNodeArgs } from './vnode'

0 commit comments

Comments
 (0)