mirror of https://github.com/vuejs/core.git
wip: v-for destructure expression rewrite (part 1)
This commit is contained in:
parent
e49c5a17da
commit
fca1aef896
|
@ -1,15 +1,15 @@
|
|||
// 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
|
||||
}"
|
||||
`;
|
||||
|
@ -73,7 +73,7 @@ export function render(_ctx) {
|
|||
const n4 = t0()
|
||||
_renderEffect(() => _setText(n4, _ctx1[0].value+_ctx0[0].value))
|
||||
return n4
|
||||
}, null, n5)
|
||||
})
|
||||
_insert(n2, n5)
|
||||
return n5
|
||||
})
|
||||
|
@ -81,16 +81,44 @@ export function render(_ctx) {
|
|||
}"
|
||||
`;
|
||||
|
||||
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';
|
||||
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), _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
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-for > object de-structured value 1`] = `
|
||||
"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), (_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
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -68,7 +68,7 @@ export function render(_ctx) {
|
|||
const n0 = _createFor(() => (_ctx.list), (_ctx0) => {
|
||||
const n2 = t0()
|
||||
return n2
|
||||
}, null, null, null, true)
|
||||
}, null, null, true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -79,13 +79,6 @@ describe('compiler: v-for', () => {
|
|||
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>`,
|
||||
|
@ -124,12 +117,38 @@ describe('compiler: v-for', () => {
|
|||
})
|
||||
|
||||
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: {
|
||||
|
@ -157,12 +176,41 @@ describe('compiler: v-for', () => {
|
|||
})
|
||||
|
||||
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: {
|
||||
|
@ -189,7 +237,7 @@ describe('compiler: v-for', () => {
|
|||
})
|
||||
})
|
||||
|
||||
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 }}
|
||||
|
@ -222,17 +270,4 @@ describe('compiler: v-for', () => {
|
|||
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()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -3,56 +3,39 @@ import { genBlock } from './block'
|
|||
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,
|
||||
|
@ -62,67 +45,68 @@ export function genFor(
|
|||
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(
|
||||
['(', ')', ', '],
|
||||
|
@ -135,4 +119,12 @@ export function genFor(
|
|||
')',
|
||||
]
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@ export interface ForIRNode extends BaseIRNode, IRFor {
|
|||
keyProp?: SimpleExpressionNode
|
||||
render: BlockIRNode
|
||||
once: boolean
|
||||
container?: number
|
||||
component: boolean
|
||||
}
|
||||
|
||||
export interface SetPropIRNode extends BaseIRNode {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
type ElementNode,
|
||||
ElementTypes,
|
||||
ErrorCodes,
|
||||
type SimpleExpressionNode,
|
||||
createCompilerError,
|
||||
|
@ -46,6 +47,7 @@ export function processFor(
|
|||
|
||||
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()
|
||||
|
@ -55,15 +57,6 @@ export function processFor(
|
|||
|
||||
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,
|
||||
|
@ -74,7 +67,7 @@ export function processFor(
|
|||
keyProp: keyProperty,
|
||||
render,
|
||||
once: context.inVOnce,
|
||||
container,
|
||||
component: isComponent,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue