Просмотр исходного кода

refactor(compiler-vapor): avoid mutating v-for render effects in codegen

Keep v-for selector pattern matching as a codegen optimization, but stop
removing matched effects from the render block IR during generation.

Instead, pass the matched effect indexes to block generation so they are skipped
only in emitted code. This preserves the original render IR and avoids the
extra effect boundary rewrites previously needed after mutating render.effect.
daiwei 5 дней назад
Родитель
Сommit
f8719bb3cb
2 измененных файлов с 86 добавлено и 80 удалено
  1. 25 2
      packages/compiler-vapor/src/generators/block.ts
  2. 61 78
      packages/compiler-vapor/src/generators/for.ts

+ 25 - 2
packages/compiler-vapor/src/generators/block.ts

@@ -56,6 +56,7 @@ export function genBlockContent(
   context: CodegenContext,
   context: CodegenContext,
   root?: boolean,
   root?: boolean,
   genEffectsExtraFrag?: () => CodeFragment[],
   genEffectsExtraFrag?: () => CodeFragment[],
+  skippedEffectIndexes?: Set<number>,
 ): CodeFragment[] {
 ): CodeFragment[] {
   const [frag, push] = buildCodeFragment()
   const [frag, push] = buildCodeFragment()
   const { dynamic, effect, operation, returns } = block
   const { dynamic, effect, operation, returns } = block
@@ -109,7 +110,7 @@ export function genBlockContent(
     }
     }
 
 
     if (effectIndex < effectEnd) {
     if (effectIndex < effectEnd) {
-      push(...genEffects(effect.slice(effectIndex, effectEnd), context))
+      push(...genEffectRange(effectIndex, effectEnd))
       effectIndex = effectEnd
       effectIndex = effectEnd
     }
     }
   }
   }
@@ -154,7 +155,7 @@ export function genBlockContent(
     push(...genOperations(operation.slice(operationIndex), context))
     push(...genOperations(operation.slice(operationIndex), context))
   }
   }
   if (effectIndex < effect.length) {
   if (effectIndex < effect.length) {
-    push(...genEffects(effect.slice(effectIndex), context, genEffectsExtraFrag))
+    push(...genEffectRange(effectIndex, effect.length, genEffectsExtraFrag))
   } else if (genEffectsExtraFrag) {
   } else if (genEffectsExtraFrag) {
     push(...genEffects([], context, genEffectsExtraFrag))
     push(...genEffects([], context, genEffectsExtraFrag))
   }
   }
@@ -172,6 +173,28 @@ export function genBlockContent(
   context.singleUseAssetComponentNames = prevSingleUseAssetComponentNames
   context.singleUseAssetComponentNames = prevSingleUseAssetComponentNames
   return frag
   return frag
 
 
+  function genEffectRange(
+    start: number,
+    end: number,
+    genExtraFrag?: () => CodeFragment[],
+  ): CodeFragment[] {
+    if (!skippedEffectIndexes) {
+      return genEffects(effect.slice(start, end), context, genExtraFrag)
+    }
+
+    const effects: typeof effect = []
+    for (let i = start; i < end; i++) {
+      if (!skippedEffectIndexes.has(i)) {
+        effects.push(effect[i])
+      }
+    }
+
+    if (effects.length || genExtraFrag) {
+      return genEffects(effects, context, genExtraFrag)
+    }
+    return []
+  }
+
   function genResolveAssets(
   function genResolveAssets(
     kind: 'component' | 'directive',
     kind: 'component' | 'directive',
     helper: CoreHelper,
     helper: CoreHelper,

+ 61 - 78
packages/compiler-vapor/src/generators/for.ts

@@ -12,7 +12,6 @@ import {
   type IRDynamicInfo,
   type IRDynamicInfo,
   type IREffect,
   type IREffect,
   IRNodeTypes,
   IRNodeTypes,
-  isBlockOperation,
 } from '../ir'
 } from '../ir'
 import {
 import {
   type CodeFragment,
   type CodeFragment,
@@ -78,12 +77,8 @@ export function genFor(
     idMap[indexVar] = null
     idMap[indexVar] = null
   }
   }
 
 
-  const { selectorPatterns, keyOnlyBindingPatterns } = matchPatterns(
-    render,
-    keyProp,
-    idMap,
-    context,
-  )
+  const { selectorPatterns, keyOnlyBindingPatterns, skippedEffectIndexes } =
+    matchPatterns(render, keyProp, idMap, context)
   const selectorDeclarations: CodeFragment[] = []
   const selectorDeclarations: CodeFragment[] = []
   const selectorName = (i: number) =>
   const selectorName = (i: number) =>
     selectorPatterns.length > 1 ? `_selector${id}_${i}` : `_selector${id}`
     selectorPatterns.length > 1 ? `_selector${id}_${i}` : `_selector${id}`
@@ -105,32 +100,38 @@ export function genFor(
     frag.push('(', ...args, ') => {', INDENT_START)
     frag.push('(', ...args, ') => {', INDENT_START)
     if (selectorPatterns.length || keyOnlyBindingPatterns.length) {
     if (selectorPatterns.length || keyOnlyBindingPatterns.length) {
       frag.push(
       frag.push(
-        ...genBlockContent(render, context, false, () => {
-          const patternFrag: CodeFragment[] = []
-
-          for (let i = 0; i < selectorPatterns.length; i++) {
-            const { effect } = selectorPatterns[i]
-            patternFrag.push(
-              NEWLINE,
-              `${selectorName(i)}(`,
-              ...genExpression(keyProp!, context),
-              `, () => {`,
-              INDENT_START,
-            )
-            for (const oper of effect.operations) {
-              patternFrag.push(...genOperation(oper, context))
+        ...genBlockContent(
+          render,
+          context,
+          false,
+          () => {
+            const patternFrag: CodeFragment[] = []
+
+            for (let i = 0; i < selectorPatterns.length; i++) {
+              const { effect } = selectorPatterns[i]
+              patternFrag.push(
+                NEWLINE,
+                `${selectorName(i)}(`,
+                ...genExpression(keyProp!, context),
+                `, () => {`,
+                INDENT_START,
+              )
+              for (const oper of effect.operations) {
+                patternFrag.push(...genOperation(oper, context))
+              }
+              patternFrag.push(INDENT_END, NEWLINE, `})`)
             }
             }
-            patternFrag.push(INDENT_END, NEWLINE, `})`)
-          }
 
 
-          for (const { effect } of keyOnlyBindingPatterns) {
-            for (const oper of effect.operations) {
-              patternFrag.push(...genOperation(oper, context))
+            for (const { effect } of keyOnlyBindingPatterns) {
+              for (const oper of effect.operations) {
+                patternFrag.push(...genOperation(oper, context))
+              }
             }
             }
-          }
 
 
-          return patternFrag
-        }),
+            return patternFrag
+          },
+          skippedEffectIndexes,
+        ),
       )
       )
     } else {
     } else {
       frag.push(...genBlockContent(render, context))
       frag.push(...genBlockContent(render, context))
@@ -383,65 +384,47 @@ function matchPatterns(
   const keyOnlyBindingPatterns: NonNullable<
   const keyOnlyBindingPatterns: NonNullable<
     ReturnType<typeof matchKeyOnlyBindingPattern>
     ReturnType<typeof matchKeyOnlyBindingPattern>
   >[] = []
   >[] = []
-  const removedEffectIndexes: number[] = []
-
-  render.effect = render.effect.filter((effect, index) => {
-    if (keyProp !== undefined) {
-      const selector = matchSelectorPattern(
-        effect,
-        keyProp.content,
-        idMap,
-        context,
-      )
-      if (selector) {
-        selectorPatterns.push(selector)
-        removedEffectIndexes.push(index)
-        return false
-      }
-      const keyOnly = matchKeyOnlyBindingPattern(effect, keyProp.content)
-      if (keyOnly) {
-        keyOnlyBindingPatterns.push(keyOnly)
-        removedEffectIndexes.push(index)
-        return false
-      }
-    }
+  let skippedEffectIndexes: Set<number> | undefined
 
 
-    return true
-  })
+  if (keyProp === undefined) {
+    return {
+      keyOnlyBindingPatterns,
+      selectorPatterns,
+      skippedEffectIndexes,
+    }
+  }
 
 
-  if (removedEffectIndexes.length) {
-    shiftEffectBoundaries(render.dynamic, removedEffectIndexes)
+  for (let index = 0; index < render.effect.length; index++) {
+    const effect = render.effect[index]
+    const selector = matchSelectorPattern(
+      effect,
+      keyProp.content,
+      idMap,
+      context,
+    )
+    if (selector) {
+      selectorPatterns.push(selector)
+      skipEffect(index)
+      continue
+    }
+    const keyOnly = matchKeyOnlyBindingPattern(effect, keyProp.content)
+    if (keyOnly) {
+      keyOnlyBindingPatterns.push(keyOnly)
+      skipEffect(index)
+    }
   }
   }
 
 
   return {
   return {
     keyOnlyBindingPatterns,
     keyOnlyBindingPatterns,
     selectorPatterns,
     selectorPatterns,
+    skippedEffectIndexes,
   }
   }
-}
 
 
-function shiftEffectBoundaries(
-  dynamic: IRDynamicInfo,
-  removedEffectIndexes: number[],
-): void {
-  const operation = dynamic.operation
-  if (
-    operation &&
-    isBlockOperation(operation) &&
-    operation.effectIndex !== undefined
-  ) {
-    let offset = 0
-    for (const removedIndex of removedEffectIndexes) {
-      if (removedIndex < operation.effectIndex) {
-        offset++
-      } else {
-        break
-      }
+  function skipEffect(index: number): void {
+    if (!skippedEffectIndexes) {
+      skippedEffectIndexes = new Set()
     }
     }
-    operation.effectIndex -= offset
-  }
-
-  for (const child of dynamic.children) {
-    shiftEffectBoundaries(child, removedEffectIndexes)
+    skippedEffectIndexes.add(index)
   }
   }
 }
 }