From: Evan You Date: Thu, 30 Jan 2025 13:15:25 +0000 (+0800) Subject: wip(vapor): support rest elements in v-for destructure X-Git-Tag: v3.6.0-alpha.1~16^2~120 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=95cf749f8bb8678cada90e00c762bcf3ec6d278d;p=thirdparty%2Fvuejs%2Fcore.git wip(vapor): support rest elements in v-for destructure --- 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 d67038943b..93be46fc7e 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap @@ -1,5 +1,19 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`compiler: v-for > array 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[0] + _ctx0[0].value.slice(1) + _ctx0[1].value)) + return n2 + }, ([id, ...other], index) => (id)) + return n0 +}" +`; + exports[`compiler: v-for > array de-structured value 1`] = ` "import { setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; const t0 = _template("
", true) @@ -30,20 +44,6 @@ export function render(_ctx) { }" `; -exports[`compiler: v-for > function params w/ prefixIdentifiers: false 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(() => (items), ([item, __, k]) => { - const n2 = t0() - _renderEffect(() => _setText(n2, item)) - return n2 - }, (item, __, k) => (k)) - return n0 -}" -`; - exports[`compiler: v-for > multi effect 1`] = ` "import { setProp as _setProp, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; const t0 = _template("
", true) @@ -82,13 +82,13 @@ 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'; +"import { getRestElement as _getRestElement, 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)) + _renderEffect(() => _setText(n2, _ctx0[0].value.id + _getRestElement(_ctx0[0].value, ["id"]) + _ctx0[1].value)) return n2 }, ({ id, ...other }, index) => (id)) return n0 @@ -109,34 +109,6 @@ export function render(_ctx) { }" `; -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), (_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 > v-for aliases w/ complex expressions 1`] = ` -"import { setText as _setText, renderEffect as _renderEffect, withDestructure as _withDestructure, createFor as _createFor, template as _template } from 'vue'; -const t0 = _template("
", true) - -export function render(_ctx) { - const n0 = _createFor(() => (_ctx.list), _withDestructure(([{ foo = bar, baz: [qux = quux] }]) => [foo, qux], (_ctx0) => { - const n2 = t0() - _renderEffect(() => _setText(n2, _ctx0[0] + _ctx.bar + _ctx.baz + _ctx0[1] + _ctx.quux)) - return n2 - })) - return n0 -}" -`; - exports[`compiler: v-for > w/o value 1`] = ` "import { createFor as _createFor, template as _template } from 'vue'; const t0 = _template("
item
", true) diff --git a/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts b/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts index e14380d766..59998d844b 100644 --- a/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts @@ -144,11 +144,12 @@ describe('compiler: v-for', () => { }) }) - test.todo('object de-structured value (with rest)', () => { + test('object de-structured value (with rest)', () => { const { code, ir } = compileWithVFor( `
{{ id + other + index }}
`, ) expect(code).matchSnapshot() + expect(code).toContain('_getRestElement(_ctx0[0].value, ["id"])') expect(ir.block.operation[0]).toMatchObject({ type: IRNodeTypes.FOR, source: { @@ -206,11 +207,12 @@ describe('compiler: v-for', () => { }) }) - test.todo('array de-structured value (with rest)', () => { + test('array de-structured value (with rest)', () => { const { code, ir } = compileWithVFor( `
{{ id + other + index }}
`, ) expect(code).matchSnapshot() + expect(code).toContain('_ctx0[0].value.slice(1)') expect(ir.block.operation[0]).toMatchObject({ type: IRNodeTypes.FOR, source: { diff --git a/packages/compiler-vapor/src/generators/for.ts b/packages/compiler-vapor/src/generators/for.ts index af429c0e67..1d2b379e7b 100644 --- a/packages/compiler-vapor/src/generators/for.ts +++ b/packages/compiler-vapor/src/generators/for.ts @@ -31,13 +31,20 @@ export function genFor( const idMap: Record = {} idToPathMap.forEach((pathInfo, id) => { - const path = `${propsName}[0].value${pathInfo ? pathInfo.path : ''}` - if (pathInfo && pathInfo.dynamic) { - const node = (idMap[id] = createSimpleExpression(path)) - const plugins = context.options.expressionPlugins - node.ast = parseExpression(`(${path})`, { - plugins: plugins ? [...plugins, 'typescript'] : ['typescript'], - }) + let path = `${propsName}[0].value${pathInfo ? pathInfo.path : ''}` + if (pathInfo) { + if (pathInfo.helper) { + path = `${pathInfo.helper}(${path}, ${pathInfo.helperArgs})` + } + if (pathInfo.dynamic) { + const node = (idMap[id] = createSimpleExpression(path)) + const plugins = context.options.expressionPlugins + node.ast = parseExpression(`(${path})`, { + plugins: plugins ? [...plugins, 'typescript'] : ['typescript'], + }) + } else { + idMap[id] = path + } } else { idMap[id] = path } @@ -68,7 +75,15 @@ export function genFor( // construct a id -> accessor path map. // e.g. `{ x: { y: [z] }}` -> `Map{ 'z' => '.x.y[0]' }` function parseValueDestructure() { - const map = new Map() + const map = new Map< + string, + { + path: string + dynamic: boolean + helper?: string + helperArgs?: string + } | null + >() if (value) { rawValue = value && value.content if (value.ast) { @@ -78,32 +93,62 @@ export function genFor( if (isLocal) { let path = '' let isDynamic = false + let helper + let helperArgs 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') { + if (parent.key.type === 'StringLiteral') { + path += `[${JSON.stringify(parent.key.value)}]` + } else if (parent.computed) { isDynamic = true path += `[${value.content.slice( parent.key.start! - 1, parent.key.end! - 1, )}]` - } 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}]` + if (child.type === 'RestElement') { + path += `.slice(${index})` + } else { + path += `[${index}]` + } + } else if ( + parent.type === 'ObjectPattern' && + child.type === 'RestElement' + ) { + helper = context.helper('getRestElement') + helperArgs = + '[' + + parent.properties + .filter(p => p.type === 'ObjectProperty') + .map(p => { + if (p.key.type === 'StringLiteral') { + return JSON.stringify(p.key.value) + } else if (p.computed) { + isDynamic = true + return value.content.slice( + p.key.start! - 1, + p.key.end! - 1, + ) + } else { + return JSON.stringify((p.key as Identifier).name) + } + }) + .join(', ') + + ']' } - // TODO handle rest spread } - map.set(id.name, { path, dynamic: isDynamic }) + map.set(id.name, { path, dynamic: isDynamic, helper, helperArgs }) } }, true, @@ -115,7 +160,6 @@ export function genFor( return map } - // TODO this should be looked at for destructure cases function genCallback(expr: SimpleExpressionNode | undefined) { if (!expr) return false const res = context.withId( diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts index 2f27fe6b5e..ceff769669 100644 --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ b/packages/runtime-vapor/src/apiCreateFor.ts @@ -380,3 +380,12 @@ function normalizeAnchor(node: Block): Node { return normalizeAnchor(node.nodes!) } } + +// runtime helper for rest element destructure +export function getRestElement(val: any, keys: string[]): any { + const res: any = {} + for (const key in val) { + if (!keys.includes(key)) res[key] = val[key] + } + return res +} diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index 4646f4ebec..8e4679a334 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -22,5 +22,5 @@ export { } from './dom/prop' export { on, delegate, delegateEvents, setDynamicEvents } from './dom/event' export { createIf } from './apiCreateIf' -export { createFor, createForSlots } from './apiCreateFor' +export { createFor, createForSlots, getRestElement } from './apiCreateFor' export { createTemplateRefSetter } from './apiTemplateRef'