diff --git a/packages/compiler-core/__tests__/parse.spec.ts b/packages/compiler-core/__tests__/parse.spec.ts index 1266d534..84f4b94a 100644 --- a/packages/compiler-core/__tests__/parse.spec.ts +++ b/packages/compiler-core/__tests__/parse.spec.ts @@ -1600,6 +1600,55 @@ foo }) }) + describe('whitespace management', () => { + it('should remove whitespaces at start/end inside an element', () => { + const ast = parse(`
where whitespace is intact isCustomElement?: (tag: string) => boolean getNamespace?: (tag: string, parent: ElementNode | undefined) => Namespace getTextMode?: (tag: string, ns: Namespace) => TextModes @@ -53,6 +54,7 @@ export const defaultParserOptions: MergedParserOptions = { getNamespace: () => Namespaces.HTML, getTextMode: () => TextModes.DATA, isVoidTag: NO, + isPreTag: NO, isCustomElement: NO, namedCharacterReferences: { 'gt;': '>', @@ -207,34 +209,36 @@ function parseChildren( // Whitespace management for more efficient output // (same as v2 whitespance: 'condense') let removedWhitespace = false - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i] - if (node.type === NodeTypes.TEXT) { - if (!node.content.trim()) { - const prev = nodes[i - 1] - const next = nodes[i + 1] - // If: - // - the whitespace is the first or last node, or: - // - the whitespace contains newline AND is between two element or comments - // Then the whitespace is ignored. - if ( - !prev || - !next || - ((prev.type === NodeTypes.ELEMENT || - prev.type === NodeTypes.COMMENT) && - (next.type === NodeTypes.ELEMENT || - next.type === NodeTypes.COMMENT) && - /[\r\n]/.test(node.content)) - ) { - removedWhitespace = true - nodes[i] = null as any + if (!parent || !context.options.isPreTag(parent.tag)) { + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i] + if (node.type === NodeTypes.TEXT) { + if (!node.content.trim()) { + const prev = nodes[i - 1] + const next = nodes[i + 1] + // If: + // - the whitespace is the first or last node, or: + // - the whitespace contains newline AND is between two element or comments + // Then the whitespace is ignored. + if ( + !prev || + !next || + ((prev.type === NodeTypes.ELEMENT || + prev.type === NodeTypes.COMMENT) && + (next.type === NodeTypes.ELEMENT || + next.type === NodeTypes.COMMENT) && + /[\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 + node.content = ' ' + } } else { - // Otherwise, condensed consecutive whitespace inside the text down to - // a single space - node.content = ' ' + node.content = node.content.replace(/\s+/g, ' ') } - } else { - node.content = node.content.replace(/\s+/g, ' ') } } } diff --git a/packages/compiler-dom/__tests__/parse.spec.ts b/packages/compiler-dom/__tests__/parse.spec.ts index aa05c557..4043f366 100644 --- a/packages/compiler-dom/__tests__/parse.spec.ts +++ b/packages/compiler-dom/__tests__/parse.spec.ts @@ -98,6 +98,15 @@ describe('DOM parser', () => { } }) }) + + test('tag should preserve raw whitespace', () => { + const rawText = ` \na b \n c` + const ast = parse(`${rawText}`, parserOptions) + expect((ast.children[0] as ElementNode).children[0]).toMatchObject({ + type: NodeTypes.TEXT, + content: rawText + }) + }) }) describe('Interpolation', () => { diff --git a/packages/compiler-dom/src/parserOptionsMinimal.ts b/packages/compiler-dom/src/parserOptionsMinimal.ts index 41e069de..38b3e6a8 100644 --- a/packages/compiler-dom/src/parserOptionsMinimal.ts +++ b/packages/compiler-dom/src/parserOptionsMinimal.ts @@ -15,8 +15,8 @@ export const enum DOMNamespaces { export const parserOptionsMinimal: ParserOptions = { isVoidTag, - isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag), + isPreTag: tag => tag === 'pre', // https://html.spec.whatwg.org/multipage/parsing.html#tree-construction-dispatcher getNamespace(tag: string, parent: ElementNode | undefined): DOMNamespaces {