Skip to content

Commit f0dcbc5

Browse files
feat(compiler-vapor, runtime-vapor): recognize selector pattern and key only binding pattern in v-for
Co-Authored-By: edison <[email protected]>
1 parent 8178e99 commit f0dcbc5

File tree

11 files changed

+589
-25
lines changed

11 files changed

+589
-25
lines changed

Diff for: packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap

+90
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,27 @@ export function render(_ctx) {
4747
}"
4848
`;
4949

50+
exports[`compiler: v-for > key only binding pattern 1`] = `
51+
"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, createFor as _createFor, template as _template } from 'vue';
52+
const t0 = _template("<tr> </tr>", true)
53+
54+
export function render(_ctx) {
55+
const n0 = _createFor(() => (_ctx.rows), (_for_item0) => {
56+
const n2 = t0()
57+
const x2 = _child(n2)
58+
let _row, _row_id
59+
{
60+
_row = _for_item0.value
61+
_row_id = _row.id
62+
63+
}
64+
_setText(x2, _toDisplayString(_row_id + _row_id))
65+
return n2
66+
}, (row) => (row.id))
67+
return n0
68+
}"
69+
`;
70+
5071
exports[`compiler: v-for > multi effect 1`] = `
5172
"import { setProp as _setProp, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
5273
const t0 = _template("<div></div>", true)
@@ -115,6 +136,75 @@ export function render(_ctx) {
115136
}"
116137
`;
117138

139+
exports[`compiler: v-for > selector pattern 1`] = `
140+
"import { useSelectorPattern as _useSelectorPattern, child as _child, toDisplayString as _toDisplayString, setText as _setText, createFor as _createFor, template as _template } from 'vue';
141+
const t0 = _template("<tr> </tr>", true)
142+
143+
export function render(_ctx) {
144+
const _selector0_0 = _useSelectorPattern(() => _ctx.selected, () => (_ctx.rows))
145+
const n0 = _createFor(() => (_ctx.rows), (_for_item0) => {
146+
const n2 = t0()
147+
const x2 = _child(n2)
148+
_selector0_0.register(_for_item0.value.id, () => {
149+
_setText(x2, _toDisplayString(_ctx.selected === _for_item0.value.id ? 'danger' : ''))
150+
})
151+
return n2
152+
}, (row) => (row.id))
153+
return n0
154+
}"
155+
`;
156+
157+
exports[`compiler: v-for > selector pattern 2`] = `
158+
"import { useSelectorPattern as _useSelectorPattern, setClass as _setClass, createFor as _createFor, template as _template } from 'vue';
159+
const t0 = _template("<tr></tr>", true)
160+
161+
export function render(_ctx) {
162+
const _selector0_0 = _useSelectorPattern(() => _ctx.selected, () => (_ctx.rows))
163+
const n0 = _createFor(() => (_ctx.rows), (_for_item0) => {
164+
const n2 = t0()
165+
_selector0_0.register(_for_item0.value.id, () => {
166+
_setClass(n2, _ctx.selected === _for_item0.value.id ? 'danger' : '')
167+
})
168+
return n2
169+
}, (row) => (row.id))
170+
return n0
171+
}"
172+
`;
173+
174+
exports[`compiler: v-for > selector pattern 3`] = `
175+
"import { setClass as _setClass, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
176+
const t0 = _template("<tr></tr>", true)
177+
178+
export function render(_ctx) {
179+
const n0 = _createFor(() => (_ctx.rows), (_for_item0) => {
180+
const n2 = t0()
181+
_renderEffect(() => {
182+
const _row = _for_item0.value
183+
_setClass(n2, _row.label === _row.id ? 'danger' : '')
184+
})
185+
return n2
186+
}, (row) => (row.id))
187+
return n0
188+
}"
189+
`;
190+
191+
exports[`compiler: v-for > selector pattern 4`] = `
192+
"import { useSelectorPattern as _useSelectorPattern, setClass as _setClass, createFor as _createFor, template as _template } from 'vue';
193+
const t0 = _template("<tr></tr>", true)
194+
195+
export function render(_ctx) {
196+
const _selector0_0 = _useSelectorPattern(() => _ctx.selected, () => (_ctx.rows))
197+
const n0 = _createFor(() => (_ctx.rows), (_for_item0) => {
198+
const n2 = t0()
199+
_selector0_0.register(_for_item0.value.id, () => {
200+
_setClass(n2, { danger: _for_item0.value.id === _ctx.selected })
201+
})
202+
return n2
203+
}, (row) => (row.id))
204+
return n0
205+
}"
206+
`;
207+
118208
exports[`compiler: v-for > v-for aliases w/ complex expressions 1`] = `
119209
"import { getDefaultValue as _getDefaultValue, child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
120210
const t0 = _template("<div> </div>", true)

Diff for: packages/compiler-vapor/__tests__/transforms/vFor.spec.ts

+67
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,73 @@ describe('compiler: v-for', () => {
6767
).lengthOf(1)
6868
})
6969

70+
test('key only binding pattern', () => {
71+
expect(
72+
compileWithVFor(
73+
`
74+
<tr
75+
v-for="row of rows"
76+
:key="row.id"
77+
>
78+
{{ row.id + row.id }}
79+
</tr>
80+
`,
81+
).code,
82+
).matchSnapshot()
83+
})
84+
85+
test('selector pattern', () => {
86+
expect(
87+
compileWithVFor(
88+
`
89+
<tr
90+
v-for="row of rows"
91+
:key="row.id"
92+
>
93+
{{ selected === row.id ? 'danger' : '' }}
94+
</tr>
95+
`,
96+
).code,
97+
).matchSnapshot()
98+
99+
expect(
100+
compileWithVFor(
101+
`
102+
<tr
103+
v-for="row of rows"
104+
:key="row.id"
105+
:class="selected === row.id ? 'danger' : ''"
106+
></tr>
107+
`,
108+
).code,
109+
).matchSnapshot()
110+
111+
// Should not be optimized because row.label is not from parent scope
112+
expect(
113+
compileWithVFor(
114+
`
115+
<tr
116+
v-for="row of rows"
117+
:key="row.id"
118+
:class="row.label === row.id ? 'danger' : ''"
119+
></tr>
120+
`,
121+
).code,
122+
).matchSnapshot()
123+
124+
expect(
125+
compileWithVFor(
126+
`
127+
<tr
128+
v-for="row of rows"
129+
:key="row.id"
130+
:class="{ danger: row.id === selected }"
131+
></tr>
132+
`,
133+
).code,
134+
).matchSnapshot()
135+
})
136+
70137
test('multi effect', () => {
71138
const { code } = compileWithVFor(
72139
`<div v-for="(item, index) of items" :item="item" :index="index" />`,

Diff for: packages/compiler-vapor/src/generators/block.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,13 @@ export function genBlock(
1919
context: CodegenContext,
2020
args: CodeFragment[] = [],
2121
root?: boolean,
22-
customReturns?: (returns: CodeFragment[]) => CodeFragment[],
2322
): CodeFragment[] {
2423
return [
2524
'(',
2625
...args,
2726
') => {',
2827
INDENT_START,
29-
...genBlockContent(oper, context, root, customReturns),
28+
...genBlockContent(oper, context, root),
3029
INDENT_END,
3130
NEWLINE,
3231
'}',
@@ -37,7 +36,7 @@ export function genBlockContent(
3736
block: BlockIRNode,
3837
context: CodegenContext,
3938
root?: boolean,
40-
customReturns?: (returns: CodeFragment[]) => CodeFragment[],
39+
genEffectsExtraFrag?: () => CodeFragment[],
4140
): CodeFragment[] {
4241
const [frag, push] = buildCodeFragment()
4342
const { dynamic, effect, operation, returns } = block
@@ -56,7 +55,7 @@ export function genBlockContent(
5655
}
5756

5857
push(...genOperations(operation, context))
59-
push(...genEffects(effect, context))
58+
push(...genEffects(effect, context, genEffectsExtraFrag))
6059

6160
push(NEWLINE, `return `)
6261

@@ -65,7 +64,7 @@ export function genBlockContent(
6564
returnNodes.length > 1
6665
? genMulti(DELIMITERS_ARRAY, ...returnNodes)
6766
: [returnNodes[0] || 'null']
68-
push(...(customReturns ? customReturns(returnsCode) : returnsCode))
67+
push(...returnsCode)
6968

7069
resetBlock()
7170
return frag

Diff for: packages/compiler-vapor/src/generators/expression.ts

+20-4
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ function canPrefix(name: string) {
230230
type DeclarationResult = {
231231
ids: Record<string, string>
232232
frag: CodeFragment[]
233+
varNames: string[]
233234
}
234235
type DeclarationValue = {
235236
name: string
@@ -243,6 +244,7 @@ type DeclarationValue = {
243244
export function processExpressions(
244245
context: CodegenContext,
245246
expressions: SimpleExpressionNode[],
247+
shouldDeclareConst: boolean,
246248
): DeclarationResult {
247249
// analyze variables
248250
const { seenVariable, variableToExpMap, expToVariableMap, seenIdentifier } =
@@ -266,7 +268,11 @@ export function processExpressions(
266268
varDeclarations,
267269
)
268270

269-
return genDeclarations([...varDeclarations, ...expDeclarations], context)
271+
return genDeclarations(
272+
[...varDeclarations, ...expDeclarations],
273+
context,
274+
shouldDeclareConst,
275+
)
270276
}
271277

272278
function analyzeExpressions(expressions: SimpleExpressionNode[]) {
@@ -507,31 +513,41 @@ function processRepeatedExpressions(
507513
function genDeclarations(
508514
declarations: DeclarationValue[],
509515
context: CodegenContext,
516+
shouldDeclareConst: boolean,
510517
): DeclarationResult {
511518
const [frag, push] = buildCodeFragment()
512519
const ids: Record<string, string> = Object.create(null)
520+
const varNames = new Set<string>()
513521

514522
// process identifiers first as expressions may rely on them
515523
declarations.forEach(({ name, isIdentifier, value }) => {
516524
if (isIdentifier) {
517525
const varName = (ids[name] = `_${name}`)
518-
push(`const ${varName} = `, ...genExpression(value, context), NEWLINE)
526+
varNames.add(varName)
527+
if (shouldDeclareConst) {
528+
push(`const `)
529+
}
530+
push(`${varName} = `, ...genExpression(value, context), NEWLINE)
519531
}
520532
})
521533

522534
// process expressions
523535
declarations.forEach(({ name, isIdentifier, value }) => {
524536
if (!isIdentifier) {
525537
const varName = (ids[name] = `_${name}`)
538+
varNames.add(varName)
539+
if (shouldDeclareConst) {
540+
push(`const `)
541+
}
526542
push(
527-
`const ${varName} = `,
543+
`${varName} = `,
528544
...context.withId(() => genExpression(value, context), ids),
529545
NEWLINE,
530546
)
531547
}
532548
})
533549

534-
return { ids, frag }
550+
return { ids, frag, varNames: [...varNames] }
535551
}
536552

537553
function escapeRegExp(string: string) {

0 commit comments

Comments
 (0)