From: Evan You Date: Thu, 30 Jan 2025 12:06:41 +0000 (+0800) Subject: wip: v-for destructure expression rewrite (part 1) X-Git-Tag: v3.6.0-alpha.1~16^2~123 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=fca1aef896386907aeb5f8863b382434aca2cab6;p=thirdparty%2Fvuejs%2Fcore.git wip: v-for destructure expression rewrite (part 1) --- diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap index 4edbec43f0..d67038943b 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap @@ -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("
", 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 (with rest) 1`] = ` +"import { setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; +const t0 = _template("
", 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("", 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("
", 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 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap index 39384169e1..15fcd2182a 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap @@ -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 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts b/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts index f0de4c32f5..e14380d766 100644 --- a/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts @@ -79,13 +79,6 @@ describe('compiler: v-for', () => { expect(code).matchSnapshot() }) - test.todo('object de-structured value', () => { - const { code } = compileWithVFor( - '{{ id }}{{ value }}', - ) - expect(code).matchSnapshot() - }) - test('nested v-for', () => { const { code, ir } = compileWithVFor( `
{{ j+i }}
`, @@ -124,12 +117,38 @@ describe('compiler: v-for', () => { }) test('object de-structured value', () => { + const { code, ir } = compileWithVFor( + '{{ id }}{{ value }}', + ) + 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( `
{{ id + other + index }}
`, ) 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( + `
{{ id + other + index }}
`, + ) + 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( `
{{ id + other + index }}
`, ) 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( `
{{ foo + bar + baz + qux + quux }} @@ -222,17 +270,4 @@ describe('compiler: v-for', () => { index: undefined, }) }) - - test('function params w/ prefixIdentifiers: false', () => { - const { code } = compileWithVFor( - `
{{ item }}
`, - { - prefixIdentifiers: false, - }, - ) - - expect(code).contains(`_createFor(() => (items), ([item, __, k]) => {`) - expect(code).contain(`_setText(n2, item)`) - expect(code).matchSnapshot() - }) }) diff --git a/packages/compiler-vapor/src/generators/for.ts b/packages/compiler-vapor/src/generators/for.ts index d061ec0dcd..9e42789958 100644 --- a/packages/compiler-vapor/src/generators/for.ts +++ b/packages/compiler-vapor/src/generators/for.ts @@ -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 = genSimpleIdMap() + const idToPathMap = parseValueDestructure() - if (isDestructure) { - const idMap: Record = {} - 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 = {} - 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() + // construct a id -> accessor path map. + // e.g. `{ x: { y: [z] }}` -> `Map{ 'z' => '.x.y[0]' }` + function parseValueDestructure() { + const map = new Map() 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 = {} - 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 = {} - 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 = {} + if (rawKey) idMap[rawKey] = null + if (rawIndex) idMap[rawIndex] = null + idToPathMap.forEach((_, id) => (idMap[id] = null)) + return idMap + } } diff --git a/packages/compiler-vapor/src/ir/index.ts b/packages/compiler-vapor/src/ir/index.ts index 05d7b51d27..3af8af961d 100644 --- a/packages/compiler-vapor/src/ir/index.ts +++ b/packages/compiler-vapor/src/ir/index.ts @@ -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 { diff --git a/packages/compiler-vapor/src/transforms/vFor.ts b/packages/compiler-vapor/src/transforms/vFor.ts index 020822478a..7b47788223 100644 --- a/packages/compiler-vapor/src/transforms/vFor.ts +++ b/packages/compiler-vapor/src/transforms/vFor.ts @@ -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, }) } }