// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: v-for > array de-structured value 1`] = `
-"import { setText as _setText, renderEffect as _renderEffect, withDestructure as _withDestructure, createFor as _createFor, template as _template } from 'vue';
+"import { setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
- const n0 = _createFor(() => (_ctx.list), _withDestructure(([[id, ...other], index]) => [id, other, index], (_ctx0) => {
+ const n0 = _createFor(() => (_ctx.list), (_ctx0) => {
const n2 = t0()
- _renderEffect(() => _setText(n2, _ctx0[0] + _ctx0[1] + _ctx0[2]))
+ _renderEffect(() => _setText(n2, _ctx0[0].value[0] + _ctx0[0].value[1] + _ctx0[1].value))
return n2
- }), ([id, ...other], index) => (id))
+ }, ([id, other], index) => (id))
return n0
}"
`;
const n4 = t0()
_renderEffect(() => _setText(n4, _ctx1[0].value+_ctx0[0].value))
return n4
- }, null, n5)
+ })
_insert(n2, n5)
return n5
})
}"
`;
+exports[`compiler: v-for > object de-structured value (with rest) 1`] = `
+"import { setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
+const t0 = _template("<div></div>", true)
+
+export function render(_ctx) {
+ const n0 = _createFor(() => (_ctx.list), (_ctx0) => {
+ const n2 = t0()
+ _renderEffect(() => _setText(n2, _ctx0[0].value.id + _ctx0[0].value + _ctx0[1].value))
+ return n2
+ }, ({ id, ...other }, index) => (id))
+ return n0
+}"
+`;
+
exports[`compiler: v-for > object de-structured value 1`] = `
-"import { setText as _setText, renderEffect as _renderEffect, withDestructure as _withDestructure, createFor as _createFor, template as _template } from 'vue';
+"import { setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
+const t0 = _template("<span></span>", true)
+
+export function render(_ctx) {
+ const n0 = _createFor(() => (_ctx.items), (_ctx0) => {
+ const n2 = t0()
+ _renderEffect(() => _setText(n2, _ctx0[0].value.id, _ctx0[0].value.value))
+ return n2
+ }, ({ id, value }) => (id))
+ return n0
+}"
+`;
+
+exports[`compiler: v-for > object de-structured value 2`] = `
+"import { setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
- const n0 = _createFor(() => (_ctx.list), _withDestructure(([{ id, ...other }, index]) => [id, other, index], (_ctx0) => {
+ const n0 = _createFor(() => (_ctx.list), (_ctx0) => {
const n2 = t0()
- _renderEffect(() => _setText(n2, _ctx0[0] + _ctx0[1] + _ctx0[2]))
+ _renderEffect(() => _setText(n2, _ctx0[0].value.id + _ctx0[0].value + _ctx0[1].value))
return n2
- }), ({ id, ...other }, index) => (id))
+ }, ({ id, ...other }, index) => (id))
return n0
}"
`;
const n0 = _createFor(() => (_ctx.list), (_ctx0) => {
const n2 = t0()
return n2
- }, null, null, null, true)
+ }, null, null, true)
return n0
}"
`;
expect(code).matchSnapshot()
})
- test.todo('object de-structured value', () => {
- const { code } = compileWithVFor(
- '<span v-for="({ id, value }) in items">{{ id }}{{ value }}</span>',
- )
- expect(code).matchSnapshot()
- })
-
test('nested v-for', () => {
const { code, ir } = compileWithVFor(
`<div v-for="i in list"><span v-for="j in i">{{ j+i }}</span></div>`,
})
test('object de-structured value', () => {
+ const { code, ir } = compileWithVFor(
+ '<span v-for="({ id, value }) in items" :key="id">{{ id }}{{ value }}</span>',
+ )
+ expect(code).matchSnapshot()
+ expect(ir.block.operation[0]).toMatchObject({
+ type: IRNodeTypes.FOR,
+ source: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: 'items',
+ },
+ value: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: '{ id, value }',
+ ast: {
+ type: 'ArrowFunctionExpression',
+ params: [
+ {
+ type: 'ObjectPattern',
+ },
+ ],
+ },
+ },
+ key: undefined,
+ index: undefined,
+ })
+ })
+
+ test.todo('object de-structured value (with rest)', () => {
const { code, ir } = compileWithVFor(
`<div v-for="( { id, ...other }, index) in list" :key="id">{{ id + other + index }}</div>`,
)
expect(code).matchSnapshot()
- expect(code).contains(`([{ id, ...other }, index]) => [id, other, index]`)
- expect(code).contains(`_ctx0[0] + _ctx0[1] + _ctx0[2]`)
expect(ir.block.operation[0]).toMatchObject({
type: IRNodeTypes.FOR,
source: {
})
test('array de-structured value', () => {
+ const { code, ir } = compileWithVFor(
+ `<div v-for="([id, other], index) in list" :key="id">{{ id + other + index }}</div>`,
+ )
+ expect(code).matchSnapshot()
+ expect(ir.block.operation[0]).toMatchObject({
+ type: IRNodeTypes.FOR,
+ source: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: 'list',
+ },
+ value: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: '[id, other]',
+ ast: {
+ type: 'ArrowFunctionExpression',
+ params: [
+ {
+ type: 'ArrayPattern',
+ },
+ ],
+ },
+ },
+ key: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: 'index',
+ },
+ index: undefined,
+ })
+ })
+
+ test.todo('array de-structured value (with rest)', () => {
const { code, ir } = compileWithVFor(
`<div v-for="([id, ...other], index) in list" :key="id">{{ id + other + index }}</div>`,
)
expect(code).matchSnapshot()
- expect(code).contains(`([[id, ...other], index]) => [id, other, index]`)
- expect(code).contains(`_ctx0[0] + _ctx0[1] + _ctx0[2]`)
expect(ir.block.operation[0]).toMatchObject({
type: IRNodeTypes.FOR,
source: {
})
})
- test('v-for aliases w/ complex expressions', () => {
+ test.todo('v-for aliases w/ complex expressions', () => {
const { code, ir } = compileWithVFor(
`<div v-for="({ foo = bar, baz: [qux = quux] }) in list">
{{ foo + bar + baz + qux + quux }}
index: undefined,
})
})
-
- test('function params w/ prefixIdentifiers: false', () => {
- const { code } = compileWithVFor(
- `<div v-for="(item, , k) of items" :key="k">{{ item }}</div>`,
- {
- prefixIdentifiers: false,
- },
- )
-
- expect(code).contains(`_createFor(() => (items), ([item, __, k]) => {`)
- expect(code).contain(`_setText(n2, item)`)
- expect(code).matchSnapshot()
- })
})
import { genExpression } from './expression'
import type { CodegenContext } from '../generate'
import type { ForIRNode } from '../ir'
-import {
- type CodeFragment,
- DELIMITERS_ARRAY,
- NEWLINE,
- genCall,
- genMulti,
-} from './utils'
+import { type CodeFragment, NEWLINE, genCall, genMulti } from './utils'
+import type { Identifier } from '@babel/types'
export function genFor(
oper: ForIRNode,
context: CodegenContext,
): CodeFragment[] {
const { helper } = context
- const { source, value, key, index, render, keyProp, once, id, container } =
+ const { source, value, key, index, render, keyProp, once, id, component } =
oper
- let isDestructure = false
let rawValue: string | null = null
const rawKey = key && key.content
const rawIndex = index && index.content
const sourceExpr = ['() => (', ...genExpression(source, context), ')']
- const idsInValue = getIdsInValue()
- let blockFn = genBlockFn()
- const simpleIdMap: Record<string, null> = genSimpleIdMap()
+ const idToPathMap = parseValueDestructure()
- if (isDestructure) {
- const idMap: Record<string, null> = {}
- idsInValue.forEach(id => (idMap[id] = null))
- if (rawKey) idMap[rawKey] = null
- if (rawIndex) idMap[rawIndex] = null
- const destructureAssignmentFn: CodeFragment[] = [
- '(',
- ...genMulti(
- DELIMITERS_ARRAY,
- rawValue ? rawValue : rawKey || rawIndex ? '_' : undefined,
- rawKey ? rawKey : rawIndex ? '__' : undefined,
- rawIndex,
- ),
- ') => ',
- ...genMulti(DELIMITERS_ARRAY, ...idsInValue, rawKey, rawIndex),
- ]
+ const [depth, exitScope] = context.enterScope()
+ const propsName = `_ctx${depth}`
+ const idMap: Record<string, string | null> = {}
- blockFn = genCall(
- // @ts-expect-error
- helper('withDestructure'),
- destructureAssignmentFn,
- blockFn,
- )
- }
+ idToPathMap.forEach((path, id) => {
+ idMap[id] = `${propsName}[0].value${path}`
+ })
+ if (rawKey) idMap[rawKey] = `${propsName}[1].value`
+ if (rawIndex) idMap[rawIndex] = `${propsName}[2].value`
+
+ const blockFn = context.withId(
+ () => genBlock(render, context, [propsName]),
+ idMap,
+ )
+ exitScope()
return [
NEWLINE,
sourceExpr,
blockFn,
genCallback(keyProp),
- container != null && `n${container}`,
- false, // todo: hydrationNode
+ component && 'true',
once && 'true',
+ // todo: hydrationNode
),
]
- function getIdsInValue() {
- const idsInValue = new Set<string>()
+ // construct a id -> accessor path map.
+ // e.g. `{ x: { y: [z] }}` -> `Map{ 'z' => '.x.y[0]' }`
+ function parseValueDestructure() {
+ const map = new Map<string, string>()
if (value) {
rawValue = value && value.content
- if ((isDestructure = !!value.ast)) {
+ if (value.ast) {
walkIdentifiers(
value.ast,
- (id, _, __, ___, isLocal) => {
- if (isLocal) idsInValue.add(id.name)
+ (id, _, parentStack, ___, isLocal) => {
+ if (isLocal) {
+ let path = ''
+ for (let i = 0; i < parentStack.length; i++) {
+ const parent = parentStack[i]
+ const child = parentStack[i + 1] || id
+ if (
+ parent.type === 'ObjectProperty' &&
+ parent.value === child
+ ) {
+ if (parent.computed && parent.key.type !== 'StringLiteral') {
+ // TODO need to process this
+ path += `[${value.content.slice(
+ parent.key.start!,
+ parent.key.end!,
+ )}]`
+ } else if (parent.key.type === 'StringLiteral') {
+ path += `[${JSON.stringify(parent.key.value)}]`
+ } else {
+ // non-computed, can only be identifier
+ path += `.${(parent.key as Identifier).name}`
+ }
+ } else if (parent.type === 'ArrayPattern') {
+ const index = parent.elements.indexOf(child as any)
+ path += `[${index}]`
+ }
+ // TODO handle rest spread
+ }
+ map.set(id.name, path)
+ }
},
true,
)
} else {
- idsInValue.add(rawValue)
+ map.set(rawValue, '')
}
}
- return idsInValue
- }
-
- function genBlockFn() {
- const [depth, exitScope] = context.enterScope()
- let propsName: string
- const idMap: Record<string, string | null> = {}
- if (context.options.prefixIdentifiers) {
- propsName = `_ctx${depth}`
- let suffix = isDestructure ? '' : '.value'
- Array.from(idsInValue).forEach(
- (id, idIndex) => (idMap[id] = `${propsName}[${idIndex}]${suffix}`),
- )
- if (rawKey) idMap[rawKey] = `${propsName}[${idsInValue.size}]${suffix}`
- if (rawIndex)
- idMap[rawIndex] = `${propsName}[${idsInValue.size + 1}]${suffix}`
- } else {
- propsName = `[${[rawValue || ((rawKey || rawIndex) && '_'), rawKey || (rawIndex && '__'), rawIndex].filter(Boolean).join(', ')}]`
- }
-
- const blockFn = context.withId(
- () => genBlock(render, context, [propsName]),
- idMap,
- )
- exitScope()
- return blockFn
- }
-
- function genSimpleIdMap() {
- const idMap: Record<string, null> = {}
- if (rawKey) idMap[rawKey] = null
- if (rawIndex) idMap[rawIndex] = null
- idsInValue.forEach(id => (idMap[id] = null))
- return idMap
+ return map
}
+ // TODO this should be looked at for destructure cases
function genCallback(expr: SimpleExpressionNode | undefined) {
if (!expr) return false
- const res = context.withId(() => genExpression(expr, context), simpleIdMap)
+ const res = context.withId(
+ () => genExpression(expr, context),
+ genSimpleIdMap(),
+ )
return [
...genMulti(
['(', ')', ', '],
')',
]
}
+
+ function genSimpleIdMap() {
+ const idMap: Record<string, null> = {}
+ if (rawKey) idMap[rawKey] = null
+ if (rawIndex) idMap[rawIndex] = null
+ idToPathMap.forEach((_, id) => (idMap[id] = null))
+ return idMap
+ }
}
keyProp?: SimpleExpressionNode
render: BlockIRNode
once: boolean
- container?: number
+ component: boolean
}
export interface SetPropIRNode extends BaseIRNode {
import {
type ElementNode,
+ ElementTypes,
ErrorCodes,
type SimpleExpressionNode,
createCompilerError,
const keyProp = findProp(node, 'key')
const keyProperty = keyProp && propToExpression(keyProp)
+ const isComponent = node.tagType === ElementTypes.COMPONENT
context.node = node = wrapTemplate(node, ['for'])
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT
const id = context.reference()
return (): void => {
exitBlock()
- const { parent } = context
- let container: number | undefined
- if (
- parent &&
- parent.block.node !== parent.node &&
- parent.node.children.length === 1
- ) {
- container = parent.reference()
- }
context.registerOperation({
type: IRNodeTypes.FOR,
id,
keyProp: keyProperty,
render,
once: context.inVOnce,
- container,
+ component: isComponent,
})
}
}