diff --git a/packages/compiler-core/src/transforms/vOn.ts b/packages/compiler-core/src/transforms/vOn.ts
index 113a1181880..487db39232b 100644
--- a/packages/compiler-core/src/transforms/vOn.ts
+++ b/packages/compiler-core/src/transforms/vOn.ts
@@ -56,6 +56,9 @@ export const transformOn: DirectiveTransform = (
 
   // handler processing
   let exp: ExpressionNode | undefined = dir.exp
+  if (exp && !exp.content.trim()) {
+    exp = undefined
+  }
   let isCacheable: boolean = !exp
   if (exp) {
     const isMemberExp = isMemberExpression(exp.content)
diff --git a/packages/compiler-dom/__tests__/transforms/vOn.spec.ts b/packages/compiler-dom/__tests__/transforms/vOn.spec.ts
index 7d56b3a8343..adc063da2f5 100644
--- a/packages/compiler-dom/__tests__/transforms/vOn.spec.ts
+++ b/packages/compiler-dom/__tests__/transforms/vOn.spec.ts
@@ -152,6 +152,58 @@ describe('compiler-dom: transform v-on', () => {
     })
   })
 
+  test('should transform click.right', () => {
+    const {
+      props: [prop]
+    } = parseWithVOn(`<div @click.right="test"/>`)
+    expect(prop.key).toMatchObject({
+      type: NodeTypes.SIMPLE_EXPRESSION,
+      content: `onContextmenu`
+    })
+
+    // dynamic
+    const {
+      props: [prop2]
+    } = parseWithVOn(`<div @[event].right="test"/>`)
+    // ("on" + (event)).toLowerCase() === "onclick" ? "onContextmenu" : ("on" + (event))
+    expect(prop2.key).toMatchObject({
+      type: NodeTypes.COMPOUND_EXPRESSION,
+      children: [
+        `(`,
+        { children: [`"on" + (`, { content: 'event' }, `)`] },
+        `).toLowerCase() === "onclick" ? "onContextmenu" : (`,
+        { children: [`"on" + (`, { content: 'event' }, `)`] },
+        `)`
+      ]
+    })
+  })
+
+  test('should transform click.middle', () => {
+    const {
+      props: [prop]
+    } = parseWithVOn(`<div @click.middle="test"/>`)
+    expect(prop.key).toMatchObject({
+      type: NodeTypes.SIMPLE_EXPRESSION,
+      content: `onMouseup`
+    })
+
+    // dynamic
+    const {
+      props: [prop2]
+    } = parseWithVOn(`<div @[event].middle="test"/>`)
+    // ("on" + (event)).toLowerCase() === "onclick" ? "onMouseup" : ("on" + (event))
+    expect(prop2.key).toMatchObject({
+      type: NodeTypes.COMPOUND_EXPRESSION,
+      children: [
+        `(`,
+        { children: [`"on" + (`, { content: 'event' }, `)`] },
+        `).toLowerCase() === "onclick" ? "onMouseup" : (`,
+        { children: [`"on" + (`, { content: 'event' }, `)`] },
+        `)`
+      ]
+    })
+  })
+
   test('cache handler w/ modifiers', () => {
     const {
       root,
diff --git a/packages/compiler-dom/src/transforms/vOn.ts b/packages/compiler-dom/src/transforms/vOn.ts
index dd6696b824a..b4c013d43ff 100644
--- a/packages/compiler-dom/src/transforms/vOn.ts
+++ b/packages/compiler-dom/src/transforms/vOn.ts
@@ -5,7 +5,9 @@ import {
   createCallExpression,
   createObjectExpression,
   createSimpleExpression,
-  NodeTypes
+  NodeTypes,
+  createCompoundExpression,
+  ExpressionNode
 } from '@vue/compiler-core'
 import { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from '../runtimeHelpers'
 import { makeMap } from '@vue/shared'
@@ -52,6 +54,24 @@ const generateModifiers = (modifiers: string[]) => {
   }
 }
 
+const transformClick = (key: ExpressionNode, event: string) => {
+  const isStaticClick =
+    key.type === NodeTypes.SIMPLE_EXPRESSION &&
+    key.isStatic &&
+    key.content.toLowerCase() === 'onclick'
+  return isStaticClick
+    ? createSimpleExpression(event, true)
+    : key.type !== NodeTypes.SIMPLE_EXPRESSION
+      ? createCompoundExpression([
+          `(`,
+          key,
+          `).toLowerCase() === "onclick" ? "${event}" : (`,
+          key,
+          `)`
+        ])
+      : key
+}
+
 export const transformOn: DirectiveTransform = (dir, node, context) => {
   return baseTransform(dir, node, context, baseResult => {
     const { modifiers } = dir
@@ -64,6 +84,14 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
       eventOptionModifiers
     } = generateModifiers(modifiers)
 
+    // normalize click.right and click.middle since they don't actually fire
+    if (nonKeyModifiers.includes('right')) {
+      key = transformClick(key, `onContextmenu`)
+    }
+    if (nonKeyModifiers.includes('middle')) {
+      key = transformClick(key, `onMouseup`)
+    }
+
     if (nonKeyModifiers.length) {
       handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [
         handlerExp,