diff --git a/packages/compiler-core/src/transforms/vOn.ts b/packages/compiler-core/src/transforms/vOn.ts index 9bd5b2a46e0..31dd16a0bde 100644 --- a/packages/compiler-core/src/transforms/vOn.ts +++ b/packages/compiler-core/src/transforms/vOn.ts @@ -42,10 +42,8 @@ export const transformOn: DirectiveTransform = ( if (arg.type === NodeTypes.SIMPLE_EXPRESSION) { if (arg.isStatic) { const rawName = arg.content - // for @vnode-xxx event listeners, auto convert it to camelCase - const normalizedName = rawName.startsWith(`vnode`) - ? capitalize(camelize(rawName)) - : capitalize(rawName) + // for all event listeners, auto convert it to camelCase. See issue #2249 + const normalizedName = capitalize(camelize(rawName)) eventName = createSimpleExpression(`on${normalizedName}`, true, arg.loc) } else { eventName = createCompoundExpression([ diff --git a/packages/runtime-core/__tests__/componentEmits.spec.ts b/packages/runtime-core/__tests__/componentEmits.spec.ts index 973099545e0..317b36f8ccb 100644 --- a/packages/runtime-core/__tests__/componentEmits.spec.ts +++ b/packages/runtime-core/__tests__/componentEmits.spec.ts @@ -28,6 +28,24 @@ describe('component: emit', () => { expect(onBaz).toHaveBeenCalled() }) + test('trigger camelize event', () => { + const Foo = defineComponent({ + render() {}, + created() { + this.$emit('test-event') + } + }) + + const fooSpy = jest.fn() + const Comp = () => + h(Foo, { + onTestEvent: fooSpy + }) + render(h(Comp), nodeOps.createElement('div')) + + expect(fooSpy).toHaveBeenCalled() + }) + // for v-model:foo-bar usage in DOM templates test('trigger hyphenated events for update:xxx events', () => { const Foo = defineComponent({ diff --git a/packages/runtime-core/src/componentEmits.ts b/packages/runtime-core/src/componentEmits.ts index ac37bc568d8..d8578589c4e 100644 --- a/packages/runtime-core/src/componentEmits.ts +++ b/packages/runtime-core/src/componentEmits.ts @@ -6,12 +6,14 @@ import { capitalize, hyphenate, isFunction, - extend + extend, + camelize } from '@vue/shared' import { ComponentInternalInstance, ComponentOptions, - ConcreteComponent + ConcreteComponent, + formatComponentName } from './component' import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling' import { warn } from './warning' @@ -78,7 +80,24 @@ export function emit( devtoolsComponentEmit(instance, event, args) } - let handlerName = `on${capitalize(event)}` + if (__DEV__) { + const lowerCaseEvent = event.toLowerCase() + if (lowerCaseEvent !== event && props[`on` + capitalize(lowerCaseEvent)]) { + warn( + `Event "${lowerCaseEvent}" is emitted in component ` + + `${formatComponentName( + instance, + instance.type + )} but the handler is registered for "${event}". ` + + `Note that HTML attributes are case-insensitive and you cannot use ` + + `v-on to listen to camelCase events when using in-DOM templates. ` + + `You should probably use "${hyphenate(event)}" instead of "${event}".` + ) + } + } + + // convert handler name to camelCase. See issue #2249 + let handlerName = `on${capitalize(camelize(event))}` let handler = props[handlerName] // for v-model update:xxx events, also trigger kebab-case equivalent // for props passed via kebab-case