Skip to content

Commit

Permalink
feat(jsx-directive): add v-on directive with object syntax (#578)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhiyuanzmj committed Dec 11, 2023
1 parent 3915dbf commit ac5494c
Show file tree
Hide file tree
Showing 19 changed files with 619 additions and 475 deletions.
6 changes: 6 additions & 0 deletions .changeset/quiet-bottles-fold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@vue-macros/jsx-directive': minor
'@vue-macros/volar': minor
---

add `v-on` directive with object syntax
11 changes: 10 additions & 1 deletion docs/features/jsx-directive.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,18 @@ Vue built-in directives for JSX.
| `v-else-if` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| `v-else` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| `v-for` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| `v-on` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| `v-slot` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| `v-html` | :white_check_mark: | :white_check_mark: | / |
| `v-once` | :white_check_mark: | :x: | / |
| `v-memo` | :white_check_mark: | :x: | / |

::: warning

`v-on` only supports binding to an object of event / listener pairs without an argument.

:::

## Usage

```vue
Expand All @@ -38,7 +45,9 @@ defineRender(() => (
{i}
</div>
<Child v-slot={props}>{props}</Child>
<Child v-on={{ submit: () => {} }} v-slot={props}>
{props}
</Child>
</>
))
</script>
Expand Down
15 changes: 12 additions & 3 deletions docs/zh-CN/features/jsx-directive.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@
| `v-else-if` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| `v-else` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| `v-for` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| `v-on` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| `v-slot` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| `v-html` | :white_check_mark: | :white_check_mark: | / |
| `v-once` | :white_check_mark: | :x: | / |
| `v-memo` | :white_check_mark: | :x: | / |

## Usage
::: warning

`v-on` 仅支持绑定不带参数的事件/监听器对的对象。

:::

## 用法

```vue
<script setup lang="tsx">
Expand All @@ -38,13 +45,15 @@ defineRender(() => (
{i}
</div>
<Child v-slot={props}>{props}</Child>
<Child v-on={{ submit: () => {} }} v-slot={props}>
{props}
</Child>
</>
))
</script>
```

## Volar Configuration
## Volar 配置

```jsonc {6}
// tsconfig.json
Expand Down
13 changes: 12 additions & 1 deletion packages/jsx-directive/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { transformVFor } from './v-for'
import { transformVMemo } from './v-memo'
import { transformVHtml } from './v-html'
import { transformVSlot } from './v-slot'
import { transformVOn } from './v-on'
import type { JSXAttribute, JSXElement, Node, Program } from '@babel/types'

export type JsxDirectiveNode = {
Expand Down Expand Up @@ -51,7 +52,9 @@ export function transformJsxDirective(

const s = new MagicString(code)
for (const { ast, offset } of asts) {
if (!/\sv-(if|for|memo|once|html|slot)/.test(s.sliceNode(ast, { offset })))
if (
!/\sv-(if|for|memo|once|html|slot|on)/.test(s.sliceNode(ast, { offset }))
)
continue

const vIfMap = new Map<Node, JsxDirectiveNode[]>()
Expand All @@ -61,6 +64,7 @@ export function transformJsxDirective(
})[] = []
const vHtmlNodes: JsxDirectiveNode[] = []
const vSlotSet = new Set<JSXElement>()
const vOnNodes: JsxDirectiveNode[] = []
walkAST<Node>(ast, {
enter(node, parent) {
if (node.type !== 'JSXElement') return
Expand Down Expand Up @@ -97,6 +101,12 @@ export function transformJsxDirective(
: node,
)
}
if (attribute.name.name === 'v-on') {
vOnNodes.push({
node,
attribute,
})
}
}

if (vIfAttribute) {
Expand Down Expand Up @@ -131,6 +141,7 @@ export function transformJsxDirective(
version >= 3.2 && transformVMemo(vMemoNodes, s, offset)
transformVHtml(vHtmlNodes, s, offset, version)
transformVSlot(Array.from(vSlotSet), s, offset, version)
transformVOn(vOnNodes, s, offset, version)
}

return generateTransform(s, id)
Expand Down
31 changes: 31 additions & 0 deletions packages/jsx-directive/src/core/v-on.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { HELPER_PREFIX, type MagicString } from '@vue-macros/common'
import type { JsxDirectiveNode } from '.'

export function transformVOn(
nodes: JsxDirectiveNode[],
s: MagicString,
offset: number,
version: number,
) {
if (nodes.length > 0 && version >= 3)
s.prependRight(
offset,
`const ${HELPER_PREFIX}transformVOn = (obj) => Object.entries(obj).reduce((res, [key, value]) => (res['on' + key[0].toUpperCase() + key.slice(1)] = value, res), {})`,
)

nodes.forEach(({ attribute }) => {
if (version < 3) {
s.remove(attribute.start! + offset, attribute.start! + offset + 2)
return
}

s.overwriteNode(
attribute,
`{...${HELPER_PREFIX}transformVOn(${s.slice(
attribute.value!.start! + offset + 1,
attribute.value!.end! + offset - 1,
)})}`,
{ offset },
)
})
}
6 changes: 5 additions & 1 deletion packages/jsx-directive/src/core/v-slot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,11 @@ export function transformVSlot(
if (attribute) {
s.overwriteNode(attribute, result, { offset })
} else {
s.appendLeft(node.openingElement.end! + offset - 1, ` ${result}`)
s.overwrite(
node.openingElement.end! + offset - 1,
node.openingElement.end! + offset,
` ${result}>`,
)
}
})
}

0 comments on commit ac5494c

Please sign in to comment.