From dee3d6ab8b4da6653d15eb148c51d9878007f6b6 Mon Sep 17 00:00:00 2001 From: CodeDaraW Date: Thu, 16 Jul 2020 21:28:09 +0800 Subject: [PATCH] feat(compiler-core): whitespace handling strategy --- .../compiler-core/__tests__/parse.spec.ts | 100 ++++++++++++++++-- packages/compiler-core/src/options.ts | 4 + packages/compiler-core/src/parse.ts | 6 +- 3 files changed, 99 insertions(+), 11 deletions(-) diff --git a/packages/compiler-core/__tests__/parse.spec.ts b/packages/compiler-core/__tests__/parse.spec.ts index 790123ac6..e0608fde4 100644 --- a/packages/compiler-core/__tests__/parse.spec.ts +++ b/packages/compiler-core/__tests__/parse.spec.ts @@ -1736,20 +1736,26 @@ foo }) }) - describe('whitespace management', () => { + describe('whitespace management when adopting strategy condense', () => { + const parse = (content: string, options?: ParserOptions) => + baseParse(content, { + whitespace: 'condense', + ...options + }) + it('should remove whitespaces at start/end inside an element', () => { - const ast = baseParse(`
`) + const ast = parse(`
`) expect((ast.children[0] as ElementNode).children.length).toBe(1) }) it('should remove whitespaces w/ newline between elements', () => { - const ast = baseParse(`
\n
\n
`) + const ast = parse(`
\n
\n
`) expect(ast.children.length).toBe(3) expect(ast.children.every(c => c.type === NodeTypes.ELEMENT)).toBe(true) }) it('should remove whitespaces adjacent to comments', () => { - const ast = baseParse(`
\n
`) + const ast = parse(`
\n
`) expect(ast.children.length).toBe(3) expect(ast.children[0].type).toBe(NodeTypes.ELEMENT) expect(ast.children[1].type).toBe(NodeTypes.COMMENT) @@ -1757,7 +1763,7 @@ foo }) it('should remove whitespaces w/ newline between comments and elements', () => { - const ast = baseParse(`
\n \n
`) + const ast = parse(`
\n \n
`) expect(ast.children.length).toBe(3) expect(ast.children[0].type).toBe(NodeTypes.ELEMENT) expect(ast.children[1].type).toBe(NodeTypes.COMMENT) @@ -1765,7 +1771,7 @@ foo }) it('should NOT remove whitespaces w/ newline between interpolations', () => { - const ast = baseParse(`{{ foo }} \n {{ bar }}`) + const ast = parse(`{{ foo }} \n {{ bar }}`) expect(ast.children.length).toBe(3) expect(ast.children[0].type).toBe(NodeTypes.INTERPOLATION) expect(ast.children[1]).toMatchObject({ @@ -1776,7 +1782,7 @@ foo }) it('should NOT remove whitespaces w/o newline between elements', () => { - const ast = baseParse(`
`) + const ast = parse(`
`) expect(ast.children.length).toBe(5) expect(ast.children.map(c => c.type)).toMatchObject([ NodeTypes.ELEMENT, @@ -1788,7 +1794,7 @@ foo }) it('should condense consecutive whitespaces in text', () => { - const ast = baseParse(` foo \n bar baz `) + const ast = parse(` foo \n bar baz `) expect((ast.children[0] as TextNode).content).toBe(` foo bar baz `) }) @@ -1824,6 +1830,84 @@ foo }) }) + describe('whitespace management when adopting strategy preserve', () => { + const parse = (content: string, options?: ParserOptions) => + baseParse(content, { + whitespace: 'preserve', + ...options + }) + + it('should preserve whitespaces at start/end inside an element', () => { + const ast = parse(`
`) + expect((ast.children[0] as ElementNode).children.length).toBe(3) + }) + + it('should preserve whitespaces w/ newline between elements', () => { + const ast = parse(`
\n
\n
`) + expect(ast.children.length).toBe(5) + expect(ast.children.map(c => c.type)).toMatchObject([ + NodeTypes.ELEMENT, + NodeTypes.TEXT, + NodeTypes.ELEMENT, + NodeTypes.TEXT, + NodeTypes.ELEMENT + ]) + }) + + it('should preserve whitespaces adjacent to comments', () => { + const ast = parse(`
\n
`) + expect(ast.children.length).toBe(5) + expect(ast.children.map(c => c.type)).toMatchObject([ + NodeTypes.ELEMENT, + NodeTypes.TEXT, + NodeTypes.COMMENT, + NodeTypes.TEXT, + NodeTypes.ELEMENT + ]) + }) + + it('should preserve whitespaces w/ newline between comments and elements', () => { + const ast = parse(`
\n \n
`) + expect(ast.children.length).toBe(5) + expect(ast.children.map(c => c.type)).toMatchObject([ + NodeTypes.ELEMENT, + NodeTypes.TEXT, + NodeTypes.COMMENT, + NodeTypes.TEXT, + NodeTypes.ELEMENT + ]) + }) + + it('should preserve whitespaces w/ newline between interpolations', () => { + const ast = parse(`{{ foo }} \n {{ bar }}`) + expect(ast.children.length).toBe(3) + expect(ast.children[0].type).toBe(NodeTypes.INTERPOLATION) + expect(ast.children[1]).toMatchObject({ + type: NodeTypes.TEXT, + content: ' \n ' + }) + expect(ast.children[2].type).toBe(NodeTypes.INTERPOLATION) + }) + + it('should preserve whitespaces w/o newline between elements', () => { + const ast = parse(`
`) + expect(ast.children.length).toBe(5) + expect(ast.children.map(c => c.type)).toMatchObject([ + NodeTypes.ELEMENT, + NodeTypes.TEXT, + NodeTypes.ELEMENT, + NodeTypes.TEXT, + NodeTypes.ELEMENT + ]) + }) + + it('should preserve consecutive whitespaces in text', () => { + const content = ` foo \n bar baz ` + const ast = parse(content) + expect((ast.children[0] as TextNode).content).toBe(content) + }) + }) + describe('Errors', () => { const patterns: { [key: string]: Array<{ diff --git a/packages/compiler-core/src/options.ts b/packages/compiler-core/src/options.ts index 08cb12d89..a387c1678 100644 --- a/packages/compiler-core/src/options.ts +++ b/packages/compiler-core/src/options.ts @@ -52,6 +52,10 @@ export interface ParserOptions * @default ['{{', '}}'] */ delimiters?: [string, string] + /** + * Whitespace handling strategy + */ + whitespace?: 'preserve' | 'condense' /** * Only needed for DOM compilers */ diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index 6335df7a3..cb5f61831 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -65,6 +65,7 @@ const decodeMap: Record = { export const defaultParserOptions: MergedParserOptions = { delimiters: [`{{`, `}}`], + whitespace: 'condense', getNamespace: () => Namespaces.HTML, getTextMode: () => TextModes.DATA, isVoidTag: NO, @@ -219,10 +220,9 @@ function parseChildren( } } - // Whitespace management for more efficient output - // (same as v2 whitespace: 'condense') + // Whitespace handling strategy like v2 let removedWhitespace = false - if (mode !== TextModes.RAWTEXT && mode !== TextModes.RCDATA) { + if (context.options.whitespace === 'condense' && mode !== TextModes.RAWTEXT && mode !== TextModes.RCDATA) { for (let i = 0; i < nodes.length; i++) { const node = nodes[i] if (!context.inPre && node.type === NodeTypes.TEXT) {