Browse Source

refactor(compiler): improve whitespace: 'preserve' behavior from #1600

- discard leading/ending whitespace inside an element
- condense preserved whitesapce into single space
Evan You 5 years ago
parent
commit
b047a0864c
2 changed files with 19 additions and 17 deletions
  1. 3 3
      packages/compiler-core/__tests__/parse.spec.ts
  2. 16 14
      packages/compiler-core/src/parse.ts

+ 3 - 3
packages/compiler-core/__tests__/parse.spec.ts

@@ -1837,9 +1837,9 @@ foo
         ...options
       })
 
-    it('should preserve whitespaces at start/end inside an element', () => {
+    it('should still remove whitespaces at start/end inside an element', () => {
       const ast = parse(`<div>   <span/>    </div>`)
-      expect((ast.children[0] as ElementNode).children.length).toBe(3)
+      expect((ast.children[0] as ElementNode).children.length).toBe(1)
     })
 
     it('should preserve whitespaces w/ newline between elements', () => {
@@ -1884,7 +1884,7 @@ foo
       expect(ast.children[0].type).toBe(NodeTypes.INTERPOLATION)
       expect(ast.children[1]).toMatchObject({
         type: NodeTypes.TEXT,
-        content: ' \n '
+        content: ' '
       })
       expect(ast.children[2].type).toBe(NodeTypes.INTERPOLATION)
     })

+ 16 - 14
packages/compiler-core/src/parse.ts

@@ -38,6 +38,7 @@ import {
 } from './compat/compatConfig'
 
 type OptionalOptions =
+  | 'whitespace'
   | 'isNativeTag'
   | 'isBuiltInComponent'
   | keyof CompilerCompatOptions
@@ -65,7 +66,6 @@ const decodeMap: Record<string, string> = {
 
 export const defaultParserOptions: MergedParserOptions = {
   delimiters: [`{{`, `}}`],
-  whitespace: 'condense',
   getNamespace: () => Namespaces.HTML,
   getTextMode: () => TextModes.DATA,
   isVoidTag: NO,
@@ -222,35 +222,37 @@ function parseChildren(
 
   // Whitespace handling strategy like v2
   let removedWhitespace = false
-  if (context.options.whitespace === 'condense' && mode !== TextModes.RAWTEXT && mode !== TextModes.RCDATA) {
+  if (mode !== TextModes.RAWTEXT && mode !== TextModes.RCDATA) {
+    const preserve = context.options.whitespace === 'preserve'
     for (let i = 0; i < nodes.length; i++) {
       const node = nodes[i]
       if (!context.inPre && node.type === NodeTypes.TEXT) {
         if (!/[^\t\r\n\f ]/.test(node.content)) {
           const prev = nodes[i - 1]
           const next = nodes[i + 1]
-          // If:
+          // Remove if:
           // - the whitespace is the first or last node, or:
-          // - the whitespace is adjacent to a comment, or:
-          // - the whitespace is between two elements AND contains newline
-          // Then the whitespace is ignored.
+          // - (condense mode) the whitespace is adjacent to a comment, or:
+          // - (condense mode) the whitespace is between two elements AND contains newline
           if (
             !prev ||
             !next ||
-            prev.type === NodeTypes.COMMENT ||
-            next.type === NodeTypes.COMMENT ||
-            (prev.type === NodeTypes.ELEMENT &&
-              next.type === NodeTypes.ELEMENT &&
-              /[\r\n]/.test(node.content))
+            (!preserve &&
+              (prev.type === NodeTypes.COMMENT ||
+                next.type === NodeTypes.COMMENT ||
+                (prev.type === NodeTypes.ELEMENT &&
+                  next.type === NodeTypes.ELEMENT &&
+                  /[\r\n]/.test(node.content))))
           ) {
             removedWhitespace = true
             nodes[i] = null as any
           } else {
-            // Otherwise, condensed consecutive whitespace inside the text
-            // down to a single space
+            // Otherwise, the whitespace is condensed into a single space
             node.content = ' '
           }
-        } else {
+        } else if (!preserve) {
+          // in condense mode, consecutive whitespaces in text are condensed
+          // down to a single space.
           node.content = node.content.replace(/[\t\r\n\f ]+/g, ' ')
         }
       }