Skip to content

Commit 0c2adfe

Browse files
committed
feat(compiler): support v-on
1 parent b953ec8 commit 0c2adfe

File tree

15 files changed

+900
-70
lines changed

15 files changed

+900
-70
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
"@vue-macros/common": "^1.10.4",
128128
"@vue-vapor/compiler-vapor": "3.2024.0-63dbc26",
129129
"@vue-vapor/vue": "3.2024.0-63dbc26",
130+
"@vue/shared": "^3.4.31",
130131
"html-tags": "^4.0.0",
131132
"svg-tags": "^1.0.0",
132133
"unplugin": "^1.10.1"

playground/App.vue

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,29 @@
11
<script lang="tsx">
22
import { defineComponent, ref } from 'vue'
3+
import Count2 from './Count.vue'
34
45
export default defineComponent({
56
setup() {
6-
const count = ref(0)
7-
setInterval(() => {
8-
count.value++
9-
}, 1000)
7+
const count = ref(1)
108
11-
const Count = () => <div>{count.value * 2}</div>
9+
const Count = (props) => {
10+
return <div>{props.value}</div>
11+
}
12+
13+
const Count1 = ({ value }) => {
14+
return <div>{value}</div>
15+
}
1216
1317
return (
1418
<>
15-
<Count />
19+
<input
20+
value_prop={count.value}
21+
onInput={(e) => (count.value = e.target.value)}
22+
/>
23+
24+
<Count value={count.value} />
25+
<Count1 value={count.value} />
26+
<Count2 value={count.value} />
1627
</>
1728
)
1829
},

playground/Count.vue

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,12 @@
1-
<template>Count</template>
1+
<script lang="tsx">
2+
import { defineComponent, ref } from 'vue'
3+
4+
export default defineComponent({
5+
props: {
6+
value: String,
7+
},
8+
setup(props) {
9+
return <div>{props.value}</div>
10+
},
11+
})
12+
</script>

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/compiler/compile.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
} from './ir'
2727
import { transformText } from './transforms/transformText'
2828
import { transformVBind } from './transforms/vBind'
29+
import { transformVOn } from './transforms/vOn'
2930
import type { JSXElement, JSXFragment, Program } from '@babel/types'
3031

3132
export interface VaporCodegenResult
@@ -50,8 +51,7 @@ export function compile(
5051
}
5152
}
5253

53-
const prefixIdentifiers =
54-
!__BROWSER__ && (options.prefixIdentifiers === true || isModuleMode)
54+
const prefixIdentifiers = !__BROWSER__ && options.prefixIdentifiers === true
5555

5656
if (options.scopeId && !isModuleMode) {
5757
onError(createCompilerError(ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED))
@@ -126,6 +126,7 @@ export function getBaseTransformPreset(
126126
[transformText, transformElement, transformChildren],
127127
{
128128
bind: transformVBind,
129+
on: transformVOn,
129130
},
130131
]
131132
}

src/core/compiler/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ export { transformText } from './transforms/transformText'
1414
export { transformElement } from './transforms/transformElement'
1515
export { transformChildren } from './transforms/transformChildren'
1616
export { transformVBind } from './transforms/vBind'
17+
export { transformVOn } from './transforms/vOn'

src/core/compiler/transforms/transformElement.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
type IRPropsDynamicAttribute,
1717
type IRPropsStatic,
1818
} from '../ir'
19-
import { isComponent as _isComponent, resolveSimpleExpression } from '../utils'
19+
import { isComponentNode, resolveSimpleExpression } from '../utils'
2020
import { EMPTY_EXPRESSION } from './utils'
2121
import type { SimpleExpressionNode } from '@vue/compiler-dom'
2222
import type { JSXAttribute, JSXElement, JSXSpreadAttribute } from '@babel/types'
@@ -44,7 +44,7 @@ export const transformElement: NodeTransform = (node, context) => {
4444
openingElement: { name },
4545
} = node
4646
const tag = name.type === 'JSXIdentifier' ? name.name : ''
47-
const isComponent = _isComponent(node.openingElement.name)
47+
const isComponent = isComponentNode(node)
4848
const propsResult = buildProps(
4949
node,
5050
context as TransformContext<JSXElement>,

src/core/compiler/transforms/transformText.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
} from '../ir'
77
import {
88
getLiteralExpressionValue,
9-
isComponent,
9+
isComponentNode,
1010
isConstantExpression,
1111
resolveExpression,
1212
} from '../utils'
@@ -33,7 +33,7 @@ export const transformText: NodeTransform = (node, context) => {
3333

3434
if (
3535
node.type === 'JSXElement' &&
36-
!isComponent(node.openingElement) &&
36+
!isComponentNode(node) &&
3737
isAllTextLike(node.children)
3838
) {
3939
processTextLikeContainer(

src/core/compiler/transforms/vBind.ts

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,16 @@
1-
import {
2-
ErrorCodes,
3-
type SimpleExpressionNode,
4-
createCompilerError,
5-
} from '@vue/compiler-dom'
61
import { camelize, extend } from '@vue/shared'
7-
import {
8-
resolveExpression,
9-
resolveLocation,
10-
resolveSimpleExpression,
11-
} from '../utils'
2+
import { resolveExpression, resolveSimpleExpression } from '../utils'
123
import { isReservedProp } from './transformElement'
134
import type { DirectiveTransform } from '../transform'
145

15-
const __BROWSER__ = false
166
export const transformVBind: DirectiveTransform = (dir, node, context) => {
17-
const { loc } = dir
18-
if (!loc || dir.name.type === 'JSXNamespacedName') return
7+
const { name, value, loc } = dir
8+
if (!loc || name.type === 'JSXNamespacedName') return
199

20-
const [name, ...modifiers] = dir.name.name.split('_')
10+
const [nameString, ...modifiers] = name.name.split('_')
2111

22-
let exp: SimpleExpressionNode
23-
if (!name.trim() && loc) {
24-
if (!__BROWSER__) {
25-
// #10280 only error against empty expression in non-browser build
26-
// because :foo in in-DOM templates will be parsed into :foo="" by the
27-
// browser
28-
context.options.onError(
29-
createCompilerError(
30-
ErrorCodes.X_V_BIND_NO_EXPRESSION,
31-
resolveLocation(loc, name),
32-
),
33-
)
34-
}
35-
exp = resolveSimpleExpression('', true, loc)
36-
}
37-
38-
exp = resolveExpression(dir.value, context)
39-
let arg = resolveExpression(dir.name, context)
12+
const exp = resolveExpression(value, context)
13+
let arg = resolveSimpleExpression(nameString, true, dir.name.loc)
4014

4115
if (arg.isStatic && isReservedProp(arg.content)) return
4216

src/core/compiler/transforms/vOn.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import {
2+
ErrorCodes,
3+
createCompilerError,
4+
resolveModifiers,
5+
} from '@vue/compiler-dom'
6+
import { extend, makeMap } from '@vue/shared'
7+
import { IRNodeTypes, type KeyOverride, type SetEventIRNode } from '../ir'
8+
import {
9+
isComponentNode,
10+
resolveExpression,
11+
resolveLocation,
12+
resolveSimpleExpression,
13+
} from '../utils'
14+
import { EMPTY_EXPRESSION } from './utils'
15+
import type { DirectiveTransform } from '../transform'
16+
17+
const delegatedEvents = /*#__PURE__*/ makeMap(
18+
'beforeinput,click,dblclick,contextmenu,focusin,focusout,input,keydown,' +
19+
'keyup,mousedown,mousemove,mouseout,mouseover,mouseup,pointerdown,' +
20+
'pointermove,pointerout,pointerover,pointerup,touchend,touchmove,' +
21+
'touchstart',
22+
)
23+
24+
export const transformVOn: DirectiveTransform = (dir, node, context) => {
25+
const { name, loc, value } = dir
26+
if (name.type === 'JSXNamespacedName') return
27+
const isComponent = isComponentNode(node)
28+
29+
const [nameString, ...modifiers] = name.name
30+
.replace(/^on([A-Z])/, (_, $1) => $1.toLowerCase())
31+
.split('_')
32+
33+
if (!value && !modifiers.length) {
34+
context.options.onError(
35+
createCompilerError(
36+
ErrorCodes.X_V_ON_NO_EXPRESSION,
37+
resolveLocation(loc, context),
38+
),
39+
)
40+
}
41+
42+
let arg = resolveSimpleExpression(nameString, true, dir.name.loc)
43+
const exp = resolveExpression(dir.value, context)
44+
45+
const { keyModifiers, nonKeyModifiers, eventOptionModifiers } =
46+
resolveModifiers(
47+
arg.isStatic ? `on${nameString}` : arg,
48+
modifiers,
49+
null,
50+
resolveLocation(loc, context),
51+
)
52+
53+
let keyOverride: KeyOverride | undefined
54+
const isStaticClick = arg.isStatic && arg.content.toLowerCase() === 'click'
55+
const delegate =
56+
arg.isStatic && !eventOptionModifiers.length && delegatedEvents(arg.content)
57+
58+
// normalize click.right and click.middle since they don't actually fire
59+
if (nonKeyModifiers.includes('middle')) {
60+
if (keyOverride) {
61+
// TODO error here
62+
}
63+
if (isStaticClick) {
64+
arg = extend({}, arg, { content: 'mouseup' })
65+
} else if (!arg.isStatic) {
66+
keyOverride = ['click', 'mouseup']
67+
}
68+
}
69+
if (nonKeyModifiers.includes('right')) {
70+
if (isStaticClick) {
71+
arg = extend({}, arg, { content: 'contextmenu' })
72+
} else if (!arg.isStatic) {
73+
keyOverride = ['click', 'contextmenu']
74+
}
75+
}
76+
77+
if (isComponent) {
78+
const handler = exp || EMPTY_EXPRESSION
79+
return {
80+
key: arg,
81+
value: handler,
82+
handler: true,
83+
}
84+
}
85+
86+
const operation: SetEventIRNode = {
87+
type: IRNodeTypes.SET_EVENT,
88+
element: context.reference(),
89+
key: arg,
90+
value: exp,
91+
modifiers: {
92+
keys: keyModifiers,
93+
nonKeys: nonKeyModifiers,
94+
options: eventOptionModifiers,
95+
},
96+
keyOverride,
97+
delegate,
98+
effect: !arg.isStatic,
99+
}
100+
101+
context.registerEffect([arg], operation)
102+
}

0 commit comments

Comments
 (0)