Skip to content

Commit

Permalink
feat(jsx-directive): v-for directive supports Map and Set (#464)
Browse files Browse the repository at this point in the history
* feat(jsx-directive):
v-for directive supports Map and Set

* chore: add changeset
  • Loading branch information
zhiyuanzmj committed Aug 18, 2023
1 parent 8ad8454 commit e79e3e4
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 40 deletions.
6 changes: 6 additions & 0 deletions .changeset/fresh-zoos-marry.md
@@ -0,0 +1,6 @@
---
'@vue-macros/jsx-directive': patch
'@vue-macros/volar': patch
---

v-for directive supports Map and Set.
5 changes: 4 additions & 1 deletion packages/jsx-directive/src/core/index.ts
Expand Up @@ -21,8 +21,10 @@ import { transformVSlot } from './v-slot'

export type JsxDirectiveNode = {
node: JSXElement
attribute: JSXAttribute & { matched?: RegExpMatchArray | null }
attribute: JSXAttribute
parent?: Node | null
vForAttribute?: JSXAttribute
vMemoAttribute?: JSXAttribute
}

export function transformJsxDirective(
Expand Down Expand Up @@ -115,6 +117,7 @@ export function transformJsxDirective(
node,
attribute: vForAttribute,
parent: vIfAttribute ? undefined : parent,
vMemoAttribute,
})
}
if (vMemoAttribute) {
Expand Down
50 changes: 30 additions & 20 deletions packages/jsx-directive/src/core/v-for.ts
Expand Up @@ -6,28 +6,38 @@ export function transformVFor(
s: MagicString,
offset = 0
) {
nodes.forEach(({ node, attribute, parent }) => {
if (attribute.value) {
const matched = (attribute.matched = s
.slice(
attribute.value.start! + offset + 1,
attribute.value.end! + offset - 1
)
.match(
/^\s?\(?([$A-Z_a-z][\w$]*)\s?,?\s?([$A-Z_a-z][\w$]*)?\)?\s+in\s+(\S+)$/
))
if (!matched) return
matched[2] ??= `${HELPER_PREFIX}index`
const [, item, index, list] = matched
nodes.forEach(({ node, attribute, parent, vMemoAttribute }) => {
if (!attribute.value) return

const hasScope = ['JSXElement', 'JSXFragment'].includes(`${parent?.type}`)
s.appendLeft(
node.start! + offset,
`${hasScope ? '{' : ''}${list}.map((${item}, ${index}) => `
)
let item, index, list
if (
attribute.value.type === 'JSXExpressionContainer' &&
attribute.value.expression.type === 'BinaryExpression'
) {
if (attribute.value.expression.left.type === 'SequenceExpression') {
const expressions = attribute.value.expression.left.expressions
item = expressions[0].type === 'Identifier' ? expressions[0].name : ''
index = expressions[1].type === 'Identifier' ? expressions[1].name : ''
} else if (attribute.value.expression.left.type === 'Identifier') {
item = attribute.value.expression.left.name
}

s.prependRight(node.end! + offset, `)${hasScope ? '}' : ''}`)
s.remove(attribute.start! + offset - 1, attribute.end! + offset)
if (vMemoAttribute) index ??= `${HELPER_PREFIX}index`

if (attribute.value.expression.right)
list = s.sliceNode(attribute.value.expression.right, { offset })
}
if (!item || !list) return

const hasScope = ['JSXElement', 'JSXFragment'].includes(`${parent?.type}`)
s.appendLeft(
node.start! + offset,
`${hasScope ? '{' : ''}Array.from(${list}).map((${item}${
index ? `, ${index}` : ''
}) => `
)

s.prependRight(node.end! + offset, `)${hasScope ? '}' : ''}`)
s.remove(attribute.start! + offset - 1, attribute.end! + offset)
})
}
28 changes: 17 additions & 11 deletions packages/jsx-directive/src/core/v-memo.ts
Expand Up @@ -6,18 +6,15 @@ import {
import { type JsxDirectiveNode } from '.'

export function transformVMemo(
nodes: (JsxDirectiveNode & {
vForAttribute?: JsxDirectiveNode['attribute']
})[],
nodes: JsxDirectiveNode[],
s: MagicString,
offset = 0
) {
if (nodes.length > 0) {
importHelperFn(s, offset, 'withMemo', 'vue')
s.prependRight(offset, `const ${HELPER_PREFIX}cache = [];`)
}
if (nodes.length === 0) return
importHelperFn(s, offset, 'withMemo', 'vue')
s.prependRight(offset, `const ${HELPER_PREFIX}cache = [];`)

nodes.forEach(({ node, attribute, parent, vForAttribute }, _index) => {
nodes.forEach(({ node, attribute, parent, vForAttribute }, nodeIndex) => {
const hasScope = ['JSXElement', 'JSXFragment'].includes(`${parent?.type}`)

s.appendLeft(
Expand All @@ -32,13 +29,22 @@ export function transformVMemo(
}, () => `
)

let index = `${_index}`
let index = `${nodeIndex}`
let cache = `${HELPER_PREFIX}cache`
if (vForAttribute?.matched) {
let vForIndex = `${HELPER_PREFIX}index`
if (vForAttribute?.value?.type === 'JSXExpressionContainer') {
if (
vForAttribute.value.expression.type === 'BinaryExpression' &&
vForAttribute.value.expression.left.type === 'SequenceExpression' &&
vForAttribute.value.expression.left.expressions[1].type === 'Identifier'
)
vForIndex = vForAttribute.value.expression.left.expressions[1].name

cache += `[${index}]`
s.appendRight(offset, `${cache} = [];`)
index += ` + ${vForAttribute.matched[2]} + 1`
index += ` + ${vForIndex} + 1`
}

s.prependRight(
node.end! + offset,
`, ${cache}, ${index})${hasScope ? '}' : ''}`
Expand Down
20 changes: 17 additions & 3 deletions packages/jsx-directive/tests/__snapshots__/v-for.test.ts.snap
Expand Up @@ -6,7 +6,7 @@ const list = [1, 2, 3]
export default () => (
<>
{list.map((i, index) => <div key={index}>
{Array.from(list).map((i, index) => <div key={index}>
<div>{i}</div>
</div>)}
</>
Expand All @@ -16,13 +16,27 @@ export default () => (

exports[`jsx-vue-directive > v-for > ./fixtures/v-for/v-for.vue 1`] = `
"<script setup lang=\\"tsx\\">
const list = [1, 2, 3]
const map = new Map([
[1, '2'],
[3, '4'],
])
const set = new Set(['1', '2', '3'])
defineRender(() => (
<>
{list.map((i, __MACROS_index) => <div key={i}>
{Array.from([1, 2, 3]).map((i) => <div key={i}>
<div>{i}</div>
</div>)}
{Array.from(set).map((i) => <div key={i}>
<div>{i}</div>
</div>)}
<div v-for={([key, value], index) in map} key={index}>
<div>
{key}: {value}
</div>
</div>
</>
))
</script>
Expand Down
Expand Up @@ -8,7 +8,7 @@ let selected = $ref(0)
export default () => (
<>
{list.map((i, index) => __MACROS_withMemo([selected === i], () => <div key={index}>
{Array.from(list).map((i, index) => __MACROS_withMemo([selected === i], () => <div key={index}>
<div>
{i}: {selected}
</div>
Expand All @@ -26,7 +26,7 @@ let selected = $ref(0)
defineRender(() => (
<>
{list.map((i, __MACROS_index) => __MACROS_withMemo([selected === i], () => <div key={i}>
{Array.from(list).map((i, __MACROS_index) => __MACROS_withMemo([selected === i], () => <div key={i}>
<div>
{i}: {selected}
</div>
Expand Down
18 changes: 16 additions & 2 deletions packages/jsx-directive/tests/fixtures/v-for/v-for.vue
@@ -1,11 +1,25 @@
<script setup lang="tsx">
const list = [1, 2, 3]
const map = new Map([
[1, '2'],
[3, '4'],
])
const set = new Set(['1', '2', '3'])
defineRender(() => (
<>
<div v-for={i in list} key={i}>
<div v-for={i in [1, 2, 3]} key={i}>
<div>{i}</div>
</div>
<div v-for={i in set} key={i}>
<div>{i}</div>
</div>
<div v-for={([key, value], index) in map} key={index}>
<div>
{key}: {value}
</div>
</div>
</>
))
</script>
3 changes: 2 additions & 1 deletion packages/volar/src/jsx-directive.ts
Expand Up @@ -106,13 +106,14 @@ function transformVFor({
node.pos,
node.pos,
`${parent ? '{' : ' '}`,
'Array.from(',
[
listText,
source,
attribute.end - listText.length - 1,
FileRangeCapabilities.full,
],
'.map(',
').map(',
[itemText, source, attribute.pos + 8, FileRangeCapabilities.full],
' => '
)
Expand Down

0 comments on commit e79e3e4

Please sign in to comment.