From 31f497b1d15a1d24a93d7fe91249f00319b5051f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Thu, 1 Feb 2024 17:42:46 +0800 Subject: [PATCH] refactor: id rewrite of vapor v-for --- .../__snapshots__/vFor.spec.ts.snap | 50 ++++++-- .../__tests__/transforms/vFor.spec.ts | 9 +- packages/compiler-vapor/src/generate.ts | 17 ++- .../compiler-vapor/src/generators/block.ts | 12 +- .../compiler-vapor/src/generators/event.ts | 7 +- .../src/generators/expression.ts | 5 +- packages/compiler-vapor/src/generators/for.ts | 113 ++++++++---------- .../src/generators/operation.ts | 7 +- packages/runtime-vapor/src/for.ts | 7 +- 9 files changed, 125 insertions(+), 102 deletions(-) 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 ed3f04907..5b69382d7 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap @@ -1,29 +1,31 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compiler: v-for > basic v-for 1`] = ` -"import { template as _template, fragment as _fragment, children as _children, on as _on, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, append as _append } from 'vue/vapor'; +"import { template as _template, fragment as _fragment, renderEffect as _renderEffect, children as _children, on as _on, setText as _setText, createFor as _createFor, append as _append } from 'vue/vapor'; export function render(_ctx) { const t0 = _template("
") const t1 = _fragment() const n0 = t1() const n1 = _createFor(() => (_ctx.items), (_block) => { + let item + _renderEffect(() => { + ([item] = _block.s); + }) const n2 = t0() const { 0: [n3],} = _children(n2) - _on(n3, "click", $event => (_ctx.remove(_block.s[0]))) - const _updateEffect = () => { - const [item] = _block.s + _on(n3, "click", $event => (_ctx.remove(item))) + _renderEffect(() => { _setText(n3, item) - } - _renderEffect(_updateEffect) - return [n2, _updateEffect] + }) + return n2 }) _append(n0, n1) return n0 }" `; -exports[`compiler: v-for > basic v-for 2`] = ` +exports[`compiler: v-for > no value 1`] = ` "import { template as _template, fragment as _fragment, createFor as _createFor, append as _append } from 'vue/vapor'; export function render(_ctx) { @@ -32,7 +34,37 @@ export function render(_ctx) { const n0 = t1() const n1 = _createFor(() => (_ctx.items), (_block) => { const n2 = t0() - return [n2, () => {}] + return n2 + }) + _append(n0, n1) + return n0 +}" +`; + +exports[`compiler: v-for > object de-structured value 1`] = ` +"import { template as _template, fragment as _fragment, renderEffect as _renderEffect, children as _children, createTextNode as _createTextNode, append as _append, setText as _setText, createFor as _createFor } from 'vue/vapor'; + +export function render(_ctx) { + const t0 = _template("") + const t1 = _fragment() + const n0 = t1() + const n1 = _createFor(() => (_ctx.items), (_block) => { + let id, value + _renderEffect(() => { + ([{ id, value }] = _block.s); + }) + const n2 = t0() + const { 0: [n5],} = _children(n2) + const n3 = _createTextNode() + const n4 = _createTextNode() + _append(n5, n3, n4) + _renderEffect(() => { + _setText(n3, id) + }) + _renderEffect(() => { + _setText(n4, value) + }) + return n2 }) _append(n0, n1) return n0 diff --git a/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts b/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts index bfa31ab53..5ba78aec5 100644 --- a/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts @@ -66,8 +66,15 @@ describe('compiler: v-for', () => { expect((ir.operation[0] as ForIRNode).render.effect).lengthOf(1) }) - test('basic v-for', () => { + test('no value', () => { const { code } = compileWithVFor(`
item
`) expect(code).matchSnapshot() }) + + test('object de-structured value', () => { + const { code } = compileWithVFor( + '{{ id }}{{ value }}', + ) + expect(code).matchSnapshot() + }) }) diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts index 1753f163a..4355535cf 100644 --- a/packages/compiler-vapor/src/generate.ts +++ b/packages/compiler-vapor/src/generate.ts @@ -7,9 +7,9 @@ import { advancePositionWithMutation, locStub, } from '@vue/compiler-dom' -import type { IREffect, RootIRNode, VaporHelper } from './ir' +import type { RootIRNode, VaporHelper } from './ir' import { SourceMapGenerator } from 'source-map-js' -import { extend, isString, remove } from '@vue/shared' +import { extend, isString } from '@vue/shared' import type { ParserPlugin } from '@babel/parser' import { genTemplate } from './generators/template' import { genBlockFunctionContent } from './generators/block' @@ -69,22 +69,19 @@ export class CodegenContext { return `_${name}` } - identifiers: Record = Object.create(null) - withId = (fn: () => T, map: Record): T => { + identifiers: Record = Object.create(null) + withId = (fn: () => T, ids: string[]): T => { const { identifiers } = this - const ids = Object.keys(map) - for (const id of ids) { - identifiers[id] ||= [] - identifiers[id].unshift(map[id] || id) + if (identifiers[id] === undefined) identifiers[id] = 0 + identifiers[id]!++ } const ret = fn() - ids.forEach(id => remove(identifiers[id], map[id] || id)) + ids.forEach(id => identifiers[id]!--) return ret } - genEffect?: (effects: IREffect[]) => CodeFragment[] constructor( public ir: RootIRNode, diff --git a/packages/compiler-vapor/src/generators/block.ts b/packages/compiler-vapor/src/generators/block.ts index ddae02f02..94d0b0554 100644 --- a/packages/compiler-vapor/src/generators/block.ts +++ b/packages/compiler-vapor/src/generators/block.ts @@ -21,13 +21,15 @@ export function genBlockFunction( oper: BlockFunctionIRNode, context: CodegenContext, args: CodeFragment[] = [], - returnValue?: () => CodeFragment[], + preamble: CodeFragment[] = [], + returnValue?: CodeFragment[], ): CodeFragment[] { return [ '(', ...args, ') => {', INDENT_START, + ...preamble, ...genBlockFunctionContent(oper, context, returnValue), INDENT_END, NEWLINE, @@ -38,7 +40,7 @@ export function genBlockFunction( export function genBlockFunctionContent( ir: BlockFunctionIRNode | RootIRNode, context: CodegenContext, - returnValue?: () => CodeFragment[], + returnValue?: CodeFragment[], ): CodeFragment[] { const { vaporHelper } = context const [frag, push] = buildCodeFragment( @@ -65,11 +67,7 @@ export function genBlockFunctionContent( push(...genOperations(ir.operation, context)) push(...genEffects(ir.effect, context)) - push( - NEWLINE, - 'return ', - ...(returnValue ? returnValue() : [`n${ir.dynamic.id}`]), - ) + push(NEWLINE, 'return ', ...(returnValue || [`n${ir.dynamic.id}`])) return frag } diff --git a/packages/compiler-vapor/src/generators/event.ts b/packages/compiler-vapor/src/generators/event.ts index 77a68433e..d2fd8c37d 100644 --- a/packages/compiler-vapor/src/generators/event.ts +++ b/packages/compiler-vapor/src/generators/event.ts @@ -45,9 +45,10 @@ export function genSetEvent( const hasMultipleStatements = exp.content.includes(`;`) if (isInlineStatement) { - const expr = context.withId(() => genExpression(exp, context), { - $event: null, - }) + const expr = context.withId( + () => genExpression(exp, context), + ['$event'], + ) return [ '$event => ', hasMultipleStatements ? '{' : '(', diff --git a/packages/compiler-vapor/src/generators/expression.ts b/packages/compiler-vapor/src/generators/expression.ts index 8cf5f30e7..79aadf95e 100644 --- a/packages/compiler-vapor/src/generators/expression.ts +++ b/packages/compiler-vapor/src/generators/expression.ts @@ -88,9 +88,8 @@ function genIdentifier( const { inline, bindingMetadata } = options let name: string | undefined = id - const idMap = identifiers[id] - if (idMap && idMap.length) { - return [idMap[0], NewlineType.None, loc] + if (identifiers[id]) { + return [id, NewlineType.None, loc] } if (inline) { diff --git a/packages/compiler-vapor/src/generators/for.ts b/packages/compiler-vapor/src/generators/for.ts index 9640553e8..507d37054 100644 --- a/packages/compiler-vapor/src/generators/for.ts +++ b/packages/compiler-vapor/src/generators/for.ts @@ -6,11 +6,14 @@ import { INDENT_END, INDENT_START, NEWLINE, - buildCodeFragment, } from '../generate' -import type { ForIRNode, IREffect } from '../ir' -import { genOperations } from './operation' -import { NewlineType } from '@vue/compiler-dom' +import type { ForIRNode } from '../ir' +import { + NewlineType, + type SimpleExpressionNode, + walkIdentifiers, +} from '@vue/compiler-dom' +import type { ArrowFunctionExpression } from '@babel/types' export function genFor( oper: ForIRNode, @@ -19,73 +22,63 @@ export function genFor( const { call, vaporHelper } = context const { source, value, key, render } = oper - const rawValue = value && value.content const rawKey = key && key.content - const sourceExpr = ['() => (', ...genExpression(source, context), ')'] - let updateFn = '_updateEffect' - context.genEffect = genEffectInFor + const valueIds = value ? extractParams(value) : new Set() + const keyIds = key ? extractParams(key) : new Set() + const ids = [...valueIds, ...keyIds] - const idMap: Record = {} - if (rawValue) idMap[rawValue] = `_block.s[0]` - if (rawKey) idMap[rawKey] = `_block.s[1]` + let preamble: CodeFragment[] = [] + if (value || rawKey) { + const assignment: CodeFragment[] = ['let ', ids.join(', ')] - const blockRet = (): CodeFragment[] => [ - `[n${render.dynamic.id!}, ${updateFn}]`, - ] + preamble = [ + NEWLINE, + ...assignment, + NEWLINE, + ...call(vaporHelper('renderEffect'), [ + '() => {', + INDENT_START, + NEWLINE, + '(', + '[', + value && [value.content, NewlineType.None, value.loc], + rawKey && ', ', + rawKey && [rawKey, NewlineType.None, key.loc], + '] = _block.s', + ');', + INDENT_END, + NEWLINE, + '}', + ]), + ] + } + const blockRet: CodeFragment[] = [`n${render.dynamic.id!}`] const blockFn = context.withId( - () => genBlockFunction(render, context, ['_block'], blockRet), - idMap, + () => genBlockFunction(render, context, ['_block'], preamble, blockRet), + ids, ) - context.genEffect = undefined - return [ NEWLINE, `const n${oper.id} = `, ...call(vaporHelper('createFor'), sourceExpr, blockFn), ] - - function genEffectInFor(effects: IREffect[]): CodeFragment[] { - if (!effects.length) { - updateFn = '() => {}' - return [] - } - - const [frag, push] = buildCodeFragment(INDENT_START) - // const [value, key] = _block.s - if (rawValue || rawKey) { - push( - NEWLINE, - 'const ', - '[', - rawValue && [rawValue, NewlineType.None, value.loc], - rawKey && ', ', - rawKey && [rawKey, NewlineType.None, key.loc], - '] = _block.s', - ) - } - - const idMap: Record = {} - if (value) idMap[value.content] = null - if (key) idMap[key.content] = null - context.withId(() => { - effects.forEach(effect => - push(...genOperations(effect.operations, context)), - ) - }, idMap) - - push(INDENT_END) - - return [ - NEWLINE, - `const ${updateFn} = () => {`, - ...frag, - NEWLINE, - '}', - NEWLINE, - `${vaporHelper('renderEffect')}(${updateFn})`, - ] - } +} + +function extractParams(node: SimpleExpressionNode) { + const ids = new Set() + if (node.ast === null || node.ast === false) { + ids.add(node.content) + } else { + walkIdentifiers( + node.ast as ArrowFunctionExpression, + (id, parent, parentStack, isReference, isLocal) => { + if (isLocal) ids.add(id.name) + }, + true, + ) + } + return ids } diff --git a/packages/compiler-vapor/src/generators/operation.ts b/packages/compiler-vapor/src/generators/operation.ts index d1d557570..c672281ce 100644 --- a/packages/compiler-vapor/src/generators/operation.ts +++ b/packages/compiler-vapor/src/generators/operation.ts @@ -25,7 +25,7 @@ export function genOperations(opers: OperationNode[], context: CodegenContext) { return frag } -function genOperation( +export function genOperation( oper: OperationNode, context: CodegenContext, ): CodeFragment[] { @@ -60,9 +60,6 @@ function genOperation( } export function genEffects(effects: IREffect[], context: CodegenContext) { - if (context.genEffect) { - return context.genEffect(effects) - } const [frag, push] = buildCodeFragment() for (const effect of effects) { push(...genEffect(effect, context)) @@ -70,7 +67,7 @@ export function genEffects(effects: IREffect[], context: CodegenContext) { return frag } -function genEffect({ operations }: IREffect, context: CodegenContext) { +export function genEffect({ operations }: IREffect, context: CodegenContext) { const { vaporHelper } = context const [frag, push] = buildCodeFragment( NEWLINE, diff --git a/packages/runtime-vapor/src/for.ts b/packages/runtime-vapor/src/for.ts index ed68491ea..c486643c8 100644 --- a/packages/runtime-vapor/src/for.ts +++ b/packages/runtime-vapor/src/for.ts @@ -15,7 +15,7 @@ interface ForBlock extends Fragment { export const createFor = ( src: () => any[] | Record | Set | Map, - renderItem: (block: ForBlock) => [Block, () => void], + renderItem: (block: ForBlock) => Block, getKey: ((item: any, index: number) => any) | null, getMemo?: (item: any) => any[], hydrationNode?: Node, @@ -47,9 +47,8 @@ export const createFor = ( memo: getMemo && getMemo(item), [fragmentKey]: true, }) - const res = scope.run(() => renderItem(block))! - block.nodes = res[0] - block.update = res[1] + block.nodes = scope.run(() => renderItem(block))! + block.update = () => scope.effects.forEach(effect => effect.run()) if (getMemo) block.update() if (parent) insert(block.nodes, parent, anchor) return block