]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-core): fix whitespace management for slots with whitespace: 'preserve...
authorHcySunYang <HcySunYang@outlook.com>
Thu, 13 May 2021 22:24:43 +0000 (06:24 +0800)
committerGitHub <noreply@github.com>
Thu, 13 May 2021 22:24:43 +0000 (18:24 -0400)
fix #3766

packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
packages/compiler-core/__tests__/transforms/vSlot.spec.ts
packages/compiler-core/src/transforms/vSlot.ts

index 5bb9ea6e1d9781fa0fe649794183ec5722ec4e7b..008fc7ba5bee74b1f774a22f47ef6a841abd9005 100644 (file)
@@ -209,3 +209,48 @@ return function render(_ctx, _cache) {
   }))
 }"
 `;
+
+exports[`compiler: transform component slots with whitespace: 'preserve' implicit default slot 1`] = `
+"const { createVNode: _createVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+  const _component_Comp = _resolveComponent(\\"Comp\\")
+
+  return (_openBlock(), _createBlock(_component_Comp, null, {
+    header: _withCtx(() => [\\" Header \\"]),
+    default: _withCtx(() => [
+      \\" \\",
+      _createVNode(\\"p\\")
+    ]),
+    _: 1 /* STABLE */
+  }))
+}"
+`;
+
+exports[`compiler: transform component slots with whitespace: 'preserve' named default slot + implicit whitespace content 1`] = `
+"const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+  const _component_Comp = _resolveComponent(\\"Comp\\")
+
+  return (_openBlock(), _createBlock(_component_Comp, null, {
+    header: _withCtx(() => [\\" Header \\"]),
+    default: _withCtx(() => [\\" Default \\"]),
+    _: 1 /* STABLE */
+  }))
+}"
+`;
+
+exports[`compiler: transform component slots with whitespace: 'preserve' should not generate whitespace only default slot 1`] = `
+"const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+  const _component_Comp = _resolveComponent(\\"Comp\\")
+
+  return (_openBlock(), _createBlock(_component_Comp, null, {
+    header: _withCtx(() => [\\" Header \\"]),
+    footer: _withCtx(() => [\\" Footer \\"]),
+    _: 1 /* STABLE */
+  }))
+}"
+`;
index 99fc009b504598c955dc0536fcfd26e7e575a04d..beddd212ab942add4059489f06890bfa9d8b2ad2 100644 (file)
@@ -9,7 +9,9 @@ import {
   ForNode,
   ComponentNode,
   VNodeCall,
-  SlotsExpression
+  SlotsExpression,
+  ObjectExpression,
+  SimpleExpressionNode
 } from '../../src'
 import { transformElement } from '../../src/transforms/transformElement'
 import { transformOn } from '../../src/transforms/vOn'
@@ -27,7 +29,9 @@ import { transformFor } from '../../src/transforms/vFor'
 import { transformIf } from '../../src/transforms/vIf'
 
 function parseWithSlots(template: string, options: CompilerOptions = {}) {
-  const ast = parse(template)
+  const ast = parse(template, {
+    whitespace: options.whitespace
+  })
   transform(ast, {
     nodeTransforms: [
       transformIf,
@@ -862,4 +866,64 @@ describe('compiler: transform component slots', () => {
       })
     })
   })
+
+  describe(`with whitespace: 'preserve'`, () => {
+    test('named default slot + implicit whitespace content', () => {
+      const source = `
+      <Comp>
+        <template #header> Header </template>
+        <template #default> Default </template>
+      </Comp>
+      `
+      const { root } = parseWithSlots(source, {
+        whitespace: 'preserve'
+      })
+
+      expect(
+        `Extraneous children found when component already has explicitly named default slot.`
+      ).not.toHaveBeenWarned()
+      expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
+    })
+
+    test('implicit default slot', () => {
+      const source = `
+      <Comp>
+        <template #header> Header </template>
+        <p/>
+      </Comp>
+      `
+      const { root } = parseWithSlots(source, {
+        whitespace: 'preserve'
+      })
+
+      expect(
+        `Extraneous children found when component already has explicitly named default slot.`
+      ).not.toHaveBeenWarned()
+      expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
+    })
+
+    test('should not generate whitespace only default slot', () => {
+      const source = `
+      <Comp>
+        <template #header> Header </template>
+        <template #footer> Footer </template>
+      </Comp>
+      `
+      const { root } = parseWithSlots(source, {
+        whitespace: 'preserve'
+      })
+
+      // slots is vnodeCall's children as an ObjectExpression
+      const slots = (root as any).children[0].codegenNode.children
+        .properties as ObjectExpression['properties']
+
+      // should be: header, footer, _ (no default)
+      expect(slots.length).toBe(3)
+      expect(
+        slots.some(p => (p.key as SimpleExpressionNode).content === 'default')
+      ).toBe(false)
+
+      expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
+    })
+  })
 })
index 4c249d23e7b94b0570622beb17b81ab21c339cc6..327bac448b85166cdbafd18d75fa660fc64427a7 100644 (file)
@@ -311,7 +311,13 @@ export function buildSlots(
     if (!hasTemplateSlots) {
       // implicit default slot (on component)
       slotsProperties.push(buildDefaultSlotProperty(undefined, children))
-    } else if (implicitDefaultChildren.length) {
+    } else if (
+      implicitDefaultChildren.length &&
+      // #3766
+      // with whitespace: 'preserve', whitespaces between slots will end up in
+      // implicitDefaultChildren. Ignore if all implicit children are whitespaces.
+      implicitDefaultChildren.some(node => isNonWhitespaceContent(node))
+    ) {
       // implicit default slot (mixed with named slots)
       if (hasNamedDefaultSlot) {
         context.onError(
@@ -397,3 +403,11 @@ function hasForwardedSlots(children: TemplateChildNode[]): boolean {
   }
   return false
 }
+
+function isNonWhitespaceContent(node: TemplateChildNode): boolean {
+  if (node.type !== NodeTypes.TEXT && node.type !== NodeTypes.TEXT_CALL)
+    return true
+  return node.type === NodeTypes.TEXT
+    ? !!node.content.trim()
+    : isNonWhitespaceContent(node.content)
+}