]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
revert: "refactor: id rewrite of vapor v-for"
author三咲智子 Kevin Deng <sxzz@sxzz.moe>
Thu, 1 Feb 2024 11:12:29 +0000 (19:12 +0800)
committer三咲智子 Kevin Deng <sxzz@sxzz.moe>
Thu, 1 Feb 2024 11:12:29 +0000 (19:12 +0800)
This reverts commit 31f497b1d15a1d24a93d7fe91249f00319b5051f.

packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/vFor.spec.ts
packages/compiler-vapor/src/generate.ts
packages/compiler-vapor/src/generators/block.ts
packages/compiler-vapor/src/generators/event.ts
packages/compiler-vapor/src/generators/expression.ts
packages/compiler-vapor/src/generators/for.ts
packages/compiler-vapor/src/generators/operation.ts
packages/runtime-vapor/src/for.ts

index 5b69382d78ddac49b264c4d1bd5accd9409da7a2..ed3f04907305dcdb4db51707105aea41e03ebb88 100644 (file)
@@ -1,31 +1,29 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
 exports[`compiler: v-for > basic v-for 1`] = `
-"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';
+"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';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   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(item)))
-    _renderEffect(() => {
+    _on(n3, "click", $event => (_ctx.remove(_block.s[0])))
+    const _updateEffect = () => {
+      const [item] = _block.s
       _setText(n3, item)
-    })
-    return n2
+    }
+    _renderEffect(_updateEffect)
+    return [n2, _updateEffect]
   })
   _append(n0, n1)
   return n0
 }"
 `;
 
-exports[`compiler: v-for > no value 1`] = `
+exports[`compiler: v-for > basic v-for 2`] = `
 "import { template as _template, fragment as _fragment, createFor as _createFor, append as _append } from 'vue/vapor';
 
 export function render(_ctx) {
@@ -34,37 +32,7 @@ export function render(_ctx) {
   const n0 = t1()
   const n1 = _createFor(() => (_ctx.items), (_block) => {
     const n2 = t0()
-    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("<span></span>")
-  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
+    return [n2, () => {}]
   })
   _append(n0, n1)
   return n0
index 5ba78aec556a5536ec8c350a9938dfe6045c9207..bfa31ab53752ae8fbfc3a649ab92d12ea4f26773 100644 (file)
@@ -66,15 +66,8 @@ describe('compiler: v-for', () => {
     expect((ir.operation[0] as ForIRNode).render.effect).lengthOf(1)
   })
 
-  test('no value', () => {
+  test('basic v-for', () => {
     const { code } = compileWithVFor(`<div v-for=" of items">item</div>`)
     expect(code).matchSnapshot()
   })
-
-  test('object de-structured value', () => {
-    const { code } = compileWithVFor(
-      '<span v-for="({ id, value }) in items">{{ id }}{{ value }}</span>',
-    )
-    expect(code).matchSnapshot()
-  })
 })
index 4355535cf45b5fde0faa835658e7e11689f37c54..1753f163a62f813e037865858776ccc9c39e211f 100644 (file)
@@ -7,9 +7,9 @@ import {
   advancePositionWithMutation,
   locStub,
 } from '@vue/compiler-dom'
-import type { RootIRNode, VaporHelper } from './ir'
+import type { IREffect, RootIRNode, VaporHelper } from './ir'
 import { SourceMapGenerator } from 'source-map-js'
-import { extend, isString } from '@vue/shared'
+import { extend, isString, remove } from '@vue/shared'
 import type { ParserPlugin } from '@babel/parser'
 import { genTemplate } from './generators/template'
 import { genBlockFunctionContent } from './generators/block'
@@ -69,19 +69,22 @@ export class CodegenContext {
     return `_${name}`
   }
 
-  identifiers: Record<string, number> = Object.create(null)
-  withId = <T>(fn: () => T, ids: string[]): T => {
+  identifiers: Record<string, string[]> = Object.create(null)
+  withId = <T>(fn: () => T, map: Record<string, string | null>): T => {
     const { identifiers } = this
+    const ids = Object.keys(map)
+
     for (const id of ids) {
-      if (identifiers[id] === undefined) identifiers[id] = 0
-      identifiers[id]!++
+      identifiers[id] ||= []
+      identifiers[id].unshift(map[id] || id)
     }
 
     const ret = fn()
-    ids.forEach(id => identifiers[id]!--)
+    ids.forEach(id => remove(identifiers[id], map[id] || id))
 
     return ret
   }
+  genEffect?: (effects: IREffect[]) => CodeFragment[]
 
   constructor(
     public ir: RootIRNode,
index 94d0b05549504b4a36d428c8129b9f4c34895be4..ddae02f025467eec65f518c6be56fb0e257b96be 100644 (file)
@@ -21,15 +21,13 @@ export function genBlockFunction(
   oper: BlockFunctionIRNode,
   context: CodegenContext,
   args: CodeFragment[] = [],
-  preamble: CodeFragment[] = [],
-  returnValue?: CodeFragment[],
+  returnValue?: () => CodeFragment[],
 ): CodeFragment[] {
   return [
     '(',
     ...args,
     ') => {',
     INDENT_START,
-    ...preamble,
     ...genBlockFunctionContent(oper, context, returnValue),
     INDENT_END,
     NEWLINE,
@@ -40,7 +38,7 @@ export function genBlockFunction(
 export function genBlockFunctionContent(
   ir: BlockFunctionIRNode | RootIRNode,
   context: CodegenContext,
-  returnValue?: CodeFragment[],
+  returnValue?: () => CodeFragment[],
 ): CodeFragment[] {
   const { vaporHelper } = context
   const [frag, push] = buildCodeFragment(
@@ -67,7 +65,11 @@ export function genBlockFunctionContent(
   push(...genOperations(ir.operation, context))
   push(...genEffects(ir.effect, context))
 
-  push(NEWLINE, 'return ', ...(returnValue || [`n${ir.dynamic.id}`]))
+  push(
+    NEWLINE,
+    'return ',
+    ...(returnValue ? returnValue() : [`n${ir.dynamic.id}`]),
+  )
 
   return frag
 }
index d2fd8c37daad6cbad5809caf6804561c340ce5f4..77a68433eba5004891ce584090e9a2e405f034e2 100644 (file)
@@ -45,10 +45,9 @@ export function genSetEvent(
       const hasMultipleStatements = exp.content.includes(`;`)
 
       if (isInlineStatement) {
-        const expr = context.withId(
-          () => genExpression(exp, context),
-          ['$event'],
-        )
+        const expr = context.withId(() => genExpression(exp, context), {
+          $event: null,
+        })
         return [
           '$event => ',
           hasMultipleStatements ? '{' : '(',
index 79aadf95e6ae24072c02af64229da81f84e970c0..8cf5f30e775e8acf07165e5657d3c61bb6571ed2 100644 (file)
@@ -88,8 +88,9 @@ function genIdentifier(
   const { inline, bindingMetadata } = options
   let name: string | undefined = id
 
-  if (identifiers[id]) {
-    return [id, NewlineType.None, loc]
+  const idMap = identifiers[id]
+  if (idMap && idMap.length) {
+    return [idMap[0], NewlineType.None, loc]
   }
 
   if (inline) {
index 507d37054f0168eaedb2b44a339d1ece4d87b984..9640553e83958c57b76544e6e84227df0d0eef4d 100644 (file)
@@ -6,14 +6,11 @@ import {
   INDENT_END,
   INDENT_START,
   NEWLINE,
+  buildCodeFragment,
 } from '../generate'
-import type { ForIRNode } from '../ir'
-import {
-  NewlineType,
-  type SimpleExpressionNode,
-  walkIdentifiers,
-} from '@vue/compiler-dom'
-import type { ArrowFunctionExpression } from '@babel/types'
+import type { ForIRNode, IREffect } from '../ir'
+import { genOperations } from './operation'
+import { NewlineType } from '@vue/compiler-dom'
 
 export function genFor(
   oper: ForIRNode,
@@ -22,63 +19,73 @@ 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), ')']
-  const valueIds = value ? extractParams(value) : new Set<string>()
-  const keyIds = key ? extractParams(key) : new Set<string>()
-  const ids = [...valueIds, ...keyIds]
+  let updateFn = '_updateEffect'
+  context.genEffect = genEffectInFor
 
-  let preamble: CodeFragment[] = []
-  if (value || rawKey) {
-    const assignment: CodeFragment[] = ['let ', ids.join(', ')]
+  const idMap: Record<string, string> = {}
+  if (rawValue) idMap[rawValue] = `_block.s[0]`
+  if (rawKey) idMap[rawKey] = `_block.s[1]`
 
-    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!}, ${updateFn}]`,
+  ]
 
-  const blockRet: CodeFragment[] = [`n${render.dynamic.id!}`]
   const blockFn = context.withId(
-    () => genBlockFunction(render, context, ['_block'], preamble, blockRet),
-    ids,
+    () => genBlockFunction(render, context, ['_block'], blockRet),
+    idMap,
   )
 
+  context.genEffect = undefined
+
   return [
     NEWLINE,
     `const n${oper.id} = `,
     ...call(vaporHelper('createFor'), sourceExpr, blockFn),
   ]
-}
 
-function extractParams(node: SimpleExpressionNode) {
-  const ids = new Set<string>()
-  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,
-    )
+  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<string, string | null> = {}
+    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})`,
+    ]
   }
-  return ids
 }
index c672281ce79af11ba76abe05d4eefbc3534f34f7..d1d5575702f56cfbfc3e6a69797ed2ae896943c6 100644 (file)
@@ -25,7 +25,7 @@ export function genOperations(opers: OperationNode[], context: CodegenContext) {
   return frag
 }
 
-export function genOperation(
+function genOperation(
   oper: OperationNode,
   context: CodegenContext,
 ): CodeFragment[] {
@@ -60,6 +60,9 @@ export 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))
@@ -67,7 +70,7 @@ export function genEffects(effects: IREffect[], context: CodegenContext) {
   return frag
 }
 
-export function genEffect({ operations }: IREffect, context: CodegenContext) {
+function genEffect({ operations }: IREffect, context: CodegenContext) {
   const { vaporHelper } = context
   const [frag, push] = buildCodeFragment(
     NEWLINE,
index c486643c831f517b948f4e0f1562814ea5d6a720..ed68491ead9bdeb2464d542baf15a5a7ed1903ed 100644 (file)
@@ -15,7 +15,7 @@ interface ForBlock extends Fragment {
 
 export const createFor = (
   src: () => any[] | Record<string, string> | Set<any> | Map<any, any>,
-  renderItem: (block: ForBlock) => Block,
+  renderItem: (block: ForBlock) => [Block, () => void],
   getKey: ((item: any, index: number) => any) | null,
   getMemo?: (item: any) => any[],
   hydrationNode?: Node,
@@ -47,8 +47,9 @@ export const createFor = (
       memo: getMemo && getMemo(item),
       [fragmentKey]: true,
     })
-    block.nodes = scope.run(() => renderItem(block))!
-    block.update = () => scope.effects.forEach(effect => effect.run())
+    const res = scope.run(() => renderItem(block))!
+    block.nodes = res[0]
+    block.update = res[1]
     if (getMemo) block.update()
     if (parent) insert(block.nodes, parent, anchor)
     return block