Skip to content

Commit 3205af5

Browse files
committed
wip: useSelectorPattern
1 parent 8178e99 commit 3205af5

File tree

5 files changed

+227
-6
lines changed

5 files changed

+227
-6
lines changed

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

+23
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,29 @@ export function render(_ctx) {
115115
}"
116116
`;
117117

118+
exports[`compiler: v-for > selector pattern 1`] = `
119+
"import { setClass as _setClass, useSelectorPattern as _useSelectorPattern, child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
120+
const t0 = _template("<tr> </tr>", true)
121+
122+
export function render(_ctx) {
123+
const _selector0 = _useSelectorPattern(() => _ctx.selected, (..._args) => {
124+
_setClass(_args[0], _args[1])
125+
}, 'danger', '')
126+
const n0 = _createFor(() => (_ctx.rows), (_for_item0) => {
127+
_selector0.register(_for_item0.value.id, _for_item0)
128+
const n2 = t0()
129+
const x2 = _child(n2)
130+
_renderEffect(() => {
131+
const _row = _for_item0.value
132+
const _row_id = _row.id
133+
_setText(x2, _toDisplayString(_row_id))
134+
})
135+
return n2
136+
}, (row) => (row.id))
137+
return n0
138+
}"
139+
`;
140+
118141
exports[`compiler: v-for > v-for aliases w/ complex expressions 1`] = `
119142
"import { getDefaultValue as _getDefaultValue, child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
120143
const t0 = _template("<div> </div>", true)

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

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

70+
test('selector pattern', () => {
71+
const { code, helpers } = compileWithVFor(
72+
`
73+
<tr
74+
v-for="row of rows"
75+
:key="row.id"
76+
:class="selected === row.id ? 'danger' : ''"
77+
>
78+
{{ row.id }}
79+
</tr>
80+
`,
81+
)
82+
83+
expect(code).matchSnapshot()
84+
expect(helpers).contains('useSelectorPattern')
85+
})
86+
7087
test('multi effect', () => {
7188
const { code } = compileWithVFor(
7289
`<div v-for="(item, index) of items" :item="item" :index="index" />`,

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

+141-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
import {
22
type SimpleExpressionNode,
33
createSimpleExpression,
4+
isStaticNode,
45
walkIdentifiers,
56
} from '@vue/compiler-dom'
6-
import { genBlock } from './block'
7+
import { genBlockContent } from './block'
78
import { genExpression } from './expression'
89
import type { CodegenContext } from '../generate'
9-
import type { ForIRNode } from '../ir'
10-
import { type CodeFragment, NEWLINE, genCall, genMulti } from './utils'
11-
import type { Identifier } from '@babel/types'
10+
import type { BlockIRNode, ForIRNode, IREffect } from '../ir'
11+
import {
12+
type CodeFragment,
13+
INDENT_END,
14+
INDENT_START,
15+
NEWLINE,
16+
genCall,
17+
genMulti,
18+
} from './utils'
19+
import { isNodesEquivalent, type Identifier } from '@babel/types'
1220
import { parseExpression } from '@babel/parser'
1321
import { VaporVForFlags } from '../../../shared/src/vaporFlags'
22+
import { getRuntimeHelper } from './prop'
1423

1524
export function genFor(
1625
oper: ForIRNode,
@@ -78,7 +87,53 @@ export function genFor(
7887
idMap[indexVar] = null
7988
}
8089

81-
const blockFn = context.withId(() => genBlock(render, context, args), idMap)
90+
const { selectorPatterns } = matchPatterns(render, keyProp)
91+
const patternFrag: CodeFragment[] = []
92+
93+
for (let i = 0; i < selectorPatterns.length; i++) {
94+
const { selector, trueValue, falseValue, effect } = selectorPatterns[i]
95+
const selectorName = `_selector${i}`
96+
const args: CodeFragment[] = [
97+
`() => `,
98+
...genExpression(selector, context),
99+
`, (..._args) => {`,
100+
]
101+
for (const oper of effect.operations) {
102+
const resolvedHelper = getRuntimeHelper(
103+
oper.tag,
104+
oper.prop.key.content,
105+
oper.prop.modifier,
106+
)
107+
args.push(
108+
NEWLINE,
109+
...genCall([helper(resolvedHelper.name), null], `_args[0]`, `_args[1]`),
110+
)
111+
}
112+
args.push(NEWLINE, `}, `, trueValue, `, `, falseValue)
113+
patternFrag.push(
114+
NEWLINE,
115+
`const ${selectorName} = `,
116+
...genCall(helper('useSelectorPattern'), args),
117+
)
118+
}
119+
120+
const blockFn = context.withId(() => {
121+
const frag: CodeFragment[] = []
122+
frag.push('(', ...args, ') => {', INDENT_START)
123+
for (let i = 0; i < selectorPatterns.length; i++) {
124+
frag.push(
125+
NEWLINE,
126+
`_selector${i}.register(`,
127+
...genExpression(keyProp!, context),
128+
`, `,
129+
itemVar,
130+
`)`,
131+
)
132+
}
133+
frag.push(...genBlockContent(render, context))
134+
frag.push(INDENT_END, NEWLINE, '}')
135+
return frag
136+
}, idMap)
82137
exitScope()
83138

84139
let flags = 0
@@ -93,6 +148,7 @@ export function genFor(
93148
}
94149

95150
return [
151+
...patternFrag,
96152
NEWLINE,
97153
`const n${id} = `,
98154
...genCall(
@@ -234,3 +290,83 @@ export function genFor(
234290
return idMap
235291
}
236292
}
293+
294+
function matchPatterns(
295+
render: BlockIRNode,
296+
keyProp: SimpleExpressionNode | undefined,
297+
) {
298+
const selectorPatterns: NonNullable<
299+
ReturnType<typeof matchSelectorPattern>
300+
>[] = []
301+
302+
render.effect = render.effect.filter(effect => {
303+
if (keyProp !== undefined) {
304+
const pattern = matchSelectorPattern(effect, keyProp)
305+
if (pattern) {
306+
selectorPatterns.push(pattern)
307+
return false
308+
}
309+
}
310+
return true
311+
})
312+
313+
return {
314+
selectorPatterns,
315+
}
316+
}
317+
318+
function matchSelectorPattern(
319+
effect: IREffect,
320+
keyProp: SimpleExpressionNode,
321+
):
322+
| {
323+
effect: IREffect
324+
selector: SimpleExpressionNode
325+
trueValue: string
326+
falseValue: string
327+
}
328+
| undefined {
329+
// TODO: expressions can be multiple?
330+
if (effect.expressions.length === 1) {
331+
const ast = effect.expressions[0].ast
332+
const content = effect.expressions[0].content
333+
if (
334+
keyProp !== undefined &&
335+
typeof ast === 'object' &&
336+
ast?.type === 'ConditionalExpression' &&
337+
ast.test.type === 'BinaryExpression' &&
338+
ast.test.operator === '===' &&
339+
ast.test.left.type !== 'PrivateName' &&
340+
isStaticNode(ast.consequent) &&
341+
isStaticNode(ast.alternate)
342+
) {
343+
const left = ast.test.left
344+
const right = ast.test.right
345+
for (const [a, b] of [
346+
[left, right],
347+
[right, left],
348+
]) {
349+
if (isNodesEquivalent(a, keyProp.ast)) {
350+
return {
351+
effect,
352+
trueValue: content.slice(
353+
ast.consequent.start! - 1,
354+
ast.consequent.end! - 1,
355+
),
356+
falseValue: content.slice(
357+
ast.alternate.start! - 1,
358+
ast.alternate.end! - 1,
359+
),
360+
selector: {
361+
content: content.slice(b.start! - 1, b.end! - 1),
362+
ast: b,
363+
loc: b.loc as any,
364+
isStatic: false,
365+
} as any,
366+
}
367+
}
368+
}
369+
}
370+
}
371+
return
372+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ export function genPropValue(
163163
)
164164
}
165165

166-
function getRuntimeHelper(
166+
export function getRuntimeHelper(
167167
tag: string,
168168
key: string,
169169
modifier: '.' | '^' | undefined,

Diff for: packages/runtime-vapor/src/index.ts

+45
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,48 @@ export { withVaporDirectives } from './directives/custom'
5454
* @internal
5555
*/
5656
export { simpleSetCurrentInstance } from '@vue/runtime-core'
57+
58+
import { watch } from '@vue/reactivity'
59+
60+
export function useSelectorPattern(
61+
getActiveKey: () => any,
62+
setter: (element: any, value: any) => void,
63+
trueValue: any,
64+
falseValue: any,
65+
): {
66+
register: (key: any, element: any) => void
67+
} {
68+
const nodeMap = new Map()
69+
let trueElements: any[] | undefined
70+
71+
watch(getActiveKey, newValue => {
72+
if (trueElements !== undefined) {
73+
for (const n of trueElements) {
74+
setter(n, falseValue)
75+
}
76+
}
77+
trueElements = nodeMap.get(newValue)
78+
if (trueElements !== undefined) {
79+
for (const n of trueElements) {
80+
setter(n, trueValue)
81+
}
82+
}
83+
})
84+
85+
return { register }
86+
87+
function register(key: any, element: any): void {
88+
const isTrue = getActiveKey() === key
89+
setter(element, isTrue ? trueValue : falseValue)
90+
let elements = nodeMap.get(key)
91+
if (elements !== undefined) {
92+
elements.push(element)
93+
} else {
94+
elements = [element]
95+
nodeMap.set(key, elements)
96+
if (isTrue) {
97+
trueElements = elements
98+
}
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)