]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-vapor): don't generate default slot for whitespace when preserved (...
authorzhiyuanzmj <260480378@qq.com>
Fri, 20 Jun 2025 00:11:05 +0000 (08:11 +0800)
committerGitHub <noreply@github.com>
Fri, 20 Jun 2025 00:11:05 +0000 (08:11 +0800)
packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts
packages/compiler-vapor/src/transforms/transformText.ts
packages/compiler-vapor/src/transforms/vSlot.ts

index 5d31be656ab7de04724ea2a4f682174f8c64b286..f296d7257d0e8cf4e45fb1e81939466d43cac4b4 100644 (file)
@@ -274,3 +274,68 @@ export function render(_ctx) {
   return n6
 }"
 `;
+
+exports[`compiler: transform slot > with whitespace: 'preserve' > implicit default slot 1`] = `
+"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
+const t0 = _template(" Header ")
+const t1 = _template(" ")
+const t2 = _template("<p></p>")
+
+export function render(_ctx) {
+  const _component_Comp = _resolveComponent("Comp")
+  const n4 = _createComponentWithFallback(_component_Comp, null, {
+    "header": () => {
+      const n0 = t0()
+      return n0
+    }, 
+    "default": () => {
+      const n2 = t1()
+      const n3 = t2()
+      return [n2, n3]
+    }
+  }, true)
+  return n4
+}"
+`;
+
+exports[`compiler: transform slot > with whitespace: 'preserve' > named default slot + implicit whitespace content 1`] = `
+"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
+const t0 = _template(" Header ")
+const t1 = _template(" Default ")
+
+export function render(_ctx) {
+  const _component_Comp = _resolveComponent("Comp")
+  const n5 = _createComponentWithFallback(_component_Comp, null, {
+    "header": () => {
+      const n0 = t0()
+      return n0
+    }, 
+    "default": () => {
+      const n3 = t1()
+      return n3
+    }
+  }, true)
+  return n5
+}"
+`;
+
+exports[`compiler: transform slot > with whitespace: 'preserve' > should not generate whitespace only default slot 1`] = `
+"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
+const t0 = _template(" Header ")
+const t1 = _template(" Footer ")
+
+export function render(_ctx) {
+  const _component_Comp = _resolveComponent("Comp")
+  const n5 = _createComponentWithFallback(_component_Comp, null, {
+    "header": () => {
+      const n0 = t0()
+      return n0
+    }, 
+    "footer": () => {
+      const n3 = t1()
+      return n3
+    }
+  }, true)
+  return n5
+}"
+`;
index 978f988d5802a1751ffd50bbd93b109485cf54fe..8db1386b3635befd7f0c943588e1652746bd0ccc 100644 (file)
@@ -509,4 +509,60 @@ describe('compiler: transform slot', () => {
       })
     })
   })
+
+  describe(`with whitespace: 'preserve'`, () => {
+    test('named default slot + implicit whitespace content', () => {
+      const source = `
+      <Comp>
+        <template #header> Header </template>
+        <template #default> Default </template>
+      </Comp>
+      `
+      const { code } = compileWithSlots(source, {
+        whitespace: 'preserve',
+      })
+
+      expect(
+        `Extraneous children found when component already has explicitly named default slot.`,
+      ).not.toHaveBeenWarned()
+      expect(code).toMatchSnapshot()
+    })
+
+    test('implicit default slot', () => {
+      const source = `
+      <Comp>
+        <template #header> Header </template>
+        <p/>
+      </Comp>
+      `
+      const { code } = compileWithSlots(source, {
+        whitespace: 'preserve',
+      })
+
+      expect(
+        `Extraneous children found when component already has explicitly named default slot.`,
+      ).not.toHaveBeenWarned()
+      expect(code).toMatchSnapshot()
+    })
+
+    test('should not generate whitespace only default slot', () => {
+      const source = `
+      <Comp>
+        <template #header> Header </template>
+        <template #footer> Footer </template>
+      </Comp>
+      `
+      const { code, ir } = compileWithSlots(source, {
+        whitespace: 'preserve',
+      })
+
+      const slots = (ir.block.dynamic.children[0].operation as any).slots[0]
+        .slots
+      // should be: header, footer (no default)
+      expect(Object.keys(slots).length).toBe(2)
+      expect(!!slots['default']).toBe(false)
+
+      expect(code).toMatchSnapshot()
+    })
+  })
 })
index 7cdb1b1a78c4abbf456e22bd5e56453edced82f3..5f858058f2799943c06bce549b9393d77ee05c30 100644 (file)
@@ -23,6 +23,13 @@ const seen = new WeakMap<
   WeakSet<TemplateChildNode | RootNode>
 >()
 
+export function markNonTemplate(
+  node: TemplateChildNode,
+  context: TransformContext,
+): void {
+  seen.get(context.root)!.add(node)
+}
+
 export const transformText: NodeTransform = (node, context) => {
   if (!seen.has(context.root)) seen.set(context.root, new WeakSet())
   if (seen.get(context.root)!.has(node)) {
@@ -68,7 +75,7 @@ export const transformText: NodeTransform = (node, context) => {
           prev.type === NodeTypes.TEXT
         ) {
           // mark leading text node for skipping
-          seen.get(context.root)!.add(prev)
+          markNonTemplate(prev, context)
         }
       }
     }
@@ -143,7 +150,7 @@ function processTextContainer(
 }
 
 function createTextLikeExpression(node: TextLike, context: TransformContext) {
-  seen.get(context.root)!.add(node)
+  markNonTemplate(node, context)
   if (node.type === NodeTypes.TEXT) {
     return createSimpleExpression(node.content, true, node.loc)
   } else {
index d1bf1c6b05f54fd3344e2c12024361971a04785c..d1f08d614e56aa8508d558bb37f8089975aaf12e 100644 (file)
@@ -24,6 +24,7 @@ import {
   type VaporDirectiveNode,
 } from '../ir'
 import { findDir, resolveExpression } from '../utils'
+import { markNonTemplate } from './transformText'
 
 export const transformVSlot: NodeTransform = (node, context) => {
   if (node.type !== NodeTypes.ELEMENT) return
@@ -66,11 +67,21 @@ function transformComponentSlot(
 ) {
   const { children } = node
   const arg = dir && dir.arg
-  const nonSlotTemplateChildren = children.filter(
-    n =>
-      isNonWhitespaceContent(node) &&
-      !(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot)),
-  )
+
+  // whitespace: 'preserve'
+  const emptyTextNodes: TemplateChildNode[] = []
+  const nonSlotTemplateChildren = children.filter(n => {
+    if (isNonWhitespaceContent(n)) {
+      return !(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot))
+    } else {
+      emptyTextNodes.push(n)
+    }
+  })
+  if (!nonSlotTemplateChildren.length) {
+    emptyTextNodes.forEach(n => {
+      markNonTemplate(n, context)
+    })
+  }
 
   const [block, onExit] = createSlotBlock(node, dir, context)