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

fix(compiler-vapor): track close tags during template abbreviation

daiwei 1 месяц назад
Родитель
Сommit
778614fd85

+ 5 - 31
packages/compiler-vapor/src/transform.ts

@@ -14,14 +14,7 @@ import {
   getSelfName,
   isVSlot,
 } from '@vue/compiler-dom'
-import {
-  EMPTY_OBJ,
-  NOOP,
-  extend,
-  isArray,
-  isInlineTag,
-  isString,
-} from '@vue/shared'
+import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared'
 import {
   type BlockIRNode,
   DynamicFlag,
@@ -111,13 +104,13 @@ export class TransformContext<T extends AllNode = AllNode> {
   // whether this node is on the rightmost path of the tree
   // (all ancestors are also last effective children)
   isOnRightmostPath: boolean = true
-  // whether there is an inline ancestor that needs closing
-  // (i.e. is an inline tag and not on the rightmost path)
-  hasInlineAncestorNeedingClose: boolean = false
   // If an ancestor in the same template must close explicitly, descendants
   // with matching tags must also close so the browser doesn't consume the
   // ancestor close tag for the descendant.
   templateCloseTags: Set<string> | undefined = undefined
+  // Inline ancestors with explicit close tags also require block descendants
+  // in the same template to close explicitly.
+  templateCloseBlocks: boolean = false
 
   private globalId = 0
   private nextIdMap: Map<number, number> | null = null
@@ -329,25 +322,6 @@ export class TransformContext<T extends AllNode = AllNode> {
     const isLastEffectiveChild = this.isEffectivelyLastChild(index)
     const isOnRightmostPath = this.isOnRightmostPath && isLastEffectiveChild
 
-    // propagate the inline ancestor status
-    let hasInlineAncestorNeedingClose = this.hasInlineAncestorNeedingClose
-    if (this.node.type === NodeTypes.ELEMENT) {
-      if (this.node.tag === 'template') {
-        // <template> acts as a boundary ensuring its content is parsed as a fragment,
-        // protecting inner blocks from outer inline contexts.
-        hasInlineAncestorNeedingClose = false
-      } else if (
-        !hasInlineAncestorNeedingClose &&
-        !this.isOnRightmostPath &&
-        isInlineTag(this.node.tag)
-      ) {
-        // Logic: if current node (parent of the node being created) is inline
-        // AND it's not on the rightmost path, then it needs closing.
-        // Any block child inside will need to be careful.
-        hasInlineAncestorNeedingClose = true
-      }
-    }
-
     return Object.assign(Object.create(TransformContext.prototype), this, {
       node,
       parent: this as any,
@@ -363,8 +337,8 @@ export class TransformContext<T extends AllNode = AllNode> {
       effectiveParent,
       isLastEffectiveChild,
       isOnRightmostPath,
-      hasInlineAncestorNeedingClose,
       templateCloseTags: this.templateCloseTags,
+      templateCloseBlocks: this.templateCloseBlocks,
     } satisfies Partial<TransformContext<T>>)
   }
 

+ 11 - 10
packages/compiler-vapor/src/transforms/transformChildren.ts

@@ -11,7 +11,7 @@ import {
   isBlockOperation,
 } from '../ir'
 import {
-  getChildTemplateCloseTags,
+  getChildTemplateCloseState,
   isInSameTemplateAsParent,
   shouldUseCreateElement,
 } from './transformElement'
@@ -28,23 +28,24 @@ export const transformChildren: NodeTransform = (node, context) => {
   const useCreateElement =
     node.type === NodeTypes.ELEMENT &&
     shouldUseCreateElement(node, context as TransformContext<ElementNode>)
-  const childTemplateCloseTags =
+  const childTemplateCloseState =
     !isFragment && !useCreateElement
-      ? getChildTemplateCloseTags(context as TransformContext<ElementNode>)
+      ? getChildTemplateCloseState(context as TransformContext<ElementNode>)
       : undefined
 
   for (const [i, child] of node.children.entries()) {
     const childContext = context.create(child, i)
-    childContext.templateCloseTags =
-      childTemplateCloseTags &&
+    const isInSameTemplate =
+      childTemplateCloseState &&
       child.type === NodeTypes.ELEMENT &&
       child.tagType === ElementTypes.ELEMENT &&
       isInSameTemplateAsParent(childContext as TransformContext<ElementNode>)
-        ? childTemplateCloseTags
-        : undefined
-    if (isFragment || useCreateElement) {
-      childContext.hasInlineAncestorNeedingClose = false
-    }
+    childContext.templateCloseTags = isInSameTemplate
+      ? childTemplateCloseState.tags
+      : undefined
+    childContext.templateCloseBlocks = isInSameTemplate
+      ? childTemplateCloseState.blocks
+      : false
     transformNode(childContext)
 
     const childDynamic = childContext.dynamic

+ 24 - 16
packages/compiler-vapor/src/transforms/transformElement.ts

@@ -24,6 +24,7 @@ import {
   isBlockTag,
   isBuiltInDirective,
   isFormattingTag,
+  isInlineTag,
   isVoidTag,
   makeMap,
 } from '@vue/shared'
@@ -161,10 +162,11 @@ function canOmitEndTag(
   }
 
   if (
-    context.templateCloseTags &&
-    (context.templateCloseTags.has(node.tag) ||
-      isAlwaysCloseTag(node.tag) ||
-      isFormattingTag(node.tag))
+    (context.templateCloseTags &&
+      (context.templateCloseTags.has(node.tag) ||
+        isAlwaysCloseTag(node.tag) ||
+        isFormattingTag(node.tag))) ||
+    (context.templateCloseBlocks && isBlockTag(node.tag))
   ) {
     return false
   }
@@ -186,18 +188,17 @@ function canOmitEndTag(
     return context.isOnRightmostPath
   }
 
-  // For inline element containing block element, if the inline ancestor
-  // is not on rightmost path, the block must close to avoid parsing issues
-  if (isBlockTag(node.tag) && context.hasInlineAncestorNeedingClose) {
-    return false
-  }
-
   return context.isLastEffectiveChild
 }
 
-export function getChildTemplateCloseTags(
+interface TemplateCloseState {
+  tags: Set<string> | undefined
+  blocks: boolean
+}
+
+export function getChildTemplateCloseState(
   context: TransformContext<ElementNode>,
-): Set<string> | undefined {
+): TemplateCloseState | undefined {
   const { node } = context
   if (
     node.type !== NodeTypes.ELEMENT ||
@@ -207,21 +208,28 @@ export function getChildTemplateCloseTags(
     return
   }
 
-  const inherited = isInSameTemplateAsParent(context)
+  const inSameTemplateAsParent = isInSameTemplateAsParent(context)
+  const inheritedTags = inSameTemplateAsParent
     ? context.templateCloseTags
     : undefined
+  const inheritedBlocks = inSameTemplateAsParent && context.templateCloseBlocks
 
   const omitEndTag =
     context.root === context.effectiveParent ||
     canOmitEndTag(node as PlainElementNode, context)
 
   if (omitEndTag || isVoidTag(node.tag)) {
-    return inherited
+    return inheritedTags || inheritedBlocks
+      ? { tags: inheritedTags, blocks: inheritedBlocks }
+      : undefined
   }
 
-  const tags = new Set(inherited)
+  const tags = new Set(inheritedTags)
   tags.add(node.tag)
-  return tags
+  return {
+    tags,
+    blocks: inheritedBlocks || isInlineTag(node.tag),
+  }
 }
 
 export function isInSameTemplateAsParent(