Skip to content

Commit 1c42f3a

Browse files
committed
feat(compiler-core): whitespace handling strategy
1 parent 4fe4de0 commit 1c42f3a

File tree

3 files changed

+99
-11
lines changed

3 files changed

+99
-11
lines changed

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

+92-8
Original file line numberDiff line numberDiff line change
@@ -1736,36 +1736,42 @@ foo
17361736
})
17371737
})
17381738

1739-
describe('whitespace management', () => {
1739+
describe('whitespace management when adopting strategy condense', () => {
1740+
const parse = (content: string, options?: ParserOptions) =>
1741+
baseParse(content, {
1742+
whitespace: 'condense',
1743+
...options
1744+
})
1745+
17401746
it('should remove whitespaces at start/end inside an element', () => {
1741-
const ast = baseParse(`<div> <span/> </div>`)
1747+
const ast = parse(`<div> <span/> </div>`)
17421748
expect((ast.children[0] as ElementNode).children.length).toBe(1)
17431749
})
17441750

17451751
it('should remove whitespaces w/ newline between elements', () => {
1746-
const ast = baseParse(`<div/> \n <div/> \n <div/>`)
1752+
const ast = parse(`<div/> \n <div/> \n <div/>`)
17471753
expect(ast.children.length).toBe(3)
17481754
expect(ast.children.every(c => c.type === NodeTypes.ELEMENT)).toBe(true)
17491755
})
17501756

17511757
it('should remove whitespaces adjacent to comments', () => {
1752-
const ast = baseParse(`<div/> \n <!--foo--> <div/>`)
1758+
const ast = parse(`<div/> \n <!--foo--> <div/>`)
17531759
expect(ast.children.length).toBe(3)
17541760
expect(ast.children[0].type).toBe(NodeTypes.ELEMENT)
17551761
expect(ast.children[1].type).toBe(NodeTypes.COMMENT)
17561762
expect(ast.children[2].type).toBe(NodeTypes.ELEMENT)
17571763
})
17581764

17591765
it('should remove whitespaces w/ newline between comments and elements', () => {
1760-
const ast = baseParse(`<div/> \n <!--foo--> \n <div/>`)
1766+
const ast = parse(`<div/> \n <!--foo--> \n <div/>`)
17611767
expect(ast.children.length).toBe(3)
17621768
expect(ast.children[0].type).toBe(NodeTypes.ELEMENT)
17631769
expect(ast.children[1].type).toBe(NodeTypes.COMMENT)
17641770
expect(ast.children[2].type).toBe(NodeTypes.ELEMENT)
17651771
})
17661772

17671773
it('should NOT remove whitespaces w/ newline between interpolations', () => {
1768-
const ast = baseParse(`{{ foo }} \n {{ bar }}`)
1774+
const ast = parse(`{{ foo }} \n {{ bar }}`)
17691775
expect(ast.children.length).toBe(3)
17701776
expect(ast.children[0].type).toBe(NodeTypes.INTERPOLATION)
17711777
expect(ast.children[1]).toMatchObject({
@@ -1776,7 +1782,7 @@ foo
17761782
})
17771783

17781784
it('should NOT remove whitespaces w/o newline between elements', () => {
1779-
const ast = baseParse(`<div/> <div/> <div/>`)
1785+
const ast = parse(`<div/> <div/> <div/>`)
17801786
expect(ast.children.length).toBe(5)
17811787
expect(ast.children.map(c => c.type)).toMatchObject([
17821788
NodeTypes.ELEMENT,
@@ -1788,7 +1794,7 @@ foo
17881794
})
17891795

17901796
it('should condense consecutive whitespaces in text', () => {
1791-
const ast = baseParse(` foo \n bar baz `)
1797+
const ast = parse(` foo \n bar baz `)
17921798
expect((ast.children[0] as TextNode).content).toBe(` foo bar baz `)
17931799
})
17941800

@@ -1824,6 +1830,84 @@ foo
18241830
})
18251831
})
18261832

1833+
describe('whitespace management when adopting strategy preserve', () => {
1834+
const parse = (content: string, options?: ParserOptions) =>
1835+
baseParse(content, {
1836+
whitespace: 'preserve',
1837+
...options
1838+
})
1839+
1840+
it('should preserve whitespaces at start/end inside an element', () => {
1841+
const ast = parse(`<div> <span/> </div>`)
1842+
expect((ast.children[0] as ElementNode).children.length).toBe(3)
1843+
})
1844+
1845+
it('should preserve whitespaces w/ newline between elements', () => {
1846+
const ast = parse(`<div/> \n <div/> \n <div/>`)
1847+
expect(ast.children.length).toBe(5)
1848+
expect(ast.children.map(c => c.type)).toMatchObject([
1849+
NodeTypes.ELEMENT,
1850+
NodeTypes.TEXT,
1851+
NodeTypes.ELEMENT,
1852+
NodeTypes.TEXT,
1853+
NodeTypes.ELEMENT
1854+
])
1855+
})
1856+
1857+
it('should preserve whitespaces adjacent to comments', () => {
1858+
const ast = parse(`<div/> \n <!--foo--> <div/>`)
1859+
expect(ast.children.length).toBe(5)
1860+
expect(ast.children.map(c => c.type)).toMatchObject([
1861+
NodeTypes.ELEMENT,
1862+
NodeTypes.TEXT,
1863+
NodeTypes.COMMENT,
1864+
NodeTypes.TEXT,
1865+
NodeTypes.ELEMENT
1866+
])
1867+
})
1868+
1869+
it('should preserve whitespaces w/ newline between comments and elements', () => {
1870+
const ast = parse(`<div/> \n <!--foo--> \n <div/>`)
1871+
expect(ast.children.length).toBe(5)
1872+
expect(ast.children.map(c => c.type)).toMatchObject([
1873+
NodeTypes.ELEMENT,
1874+
NodeTypes.TEXT,
1875+
NodeTypes.COMMENT,
1876+
NodeTypes.TEXT,
1877+
NodeTypes.ELEMENT
1878+
])
1879+
})
1880+
1881+
it('should preserve whitespaces w/ newline between interpolations', () => {
1882+
const ast = parse(`{{ foo }} \n {{ bar }}`)
1883+
expect(ast.children.length).toBe(3)
1884+
expect(ast.children[0].type).toBe(NodeTypes.INTERPOLATION)
1885+
expect(ast.children[1]).toMatchObject({
1886+
type: NodeTypes.TEXT,
1887+
content: ' \n '
1888+
})
1889+
expect(ast.children[2].type).toBe(NodeTypes.INTERPOLATION)
1890+
})
1891+
1892+
it('should preserve whitespaces w/o newline between elements', () => {
1893+
const ast = parse(`<div/> <div/> <div/>`)
1894+
expect(ast.children.length).toBe(5)
1895+
expect(ast.children.map(c => c.type)).toMatchObject([
1896+
NodeTypes.ELEMENT,
1897+
NodeTypes.TEXT,
1898+
NodeTypes.ELEMENT,
1899+
NodeTypes.TEXT,
1900+
NodeTypes.ELEMENT
1901+
])
1902+
})
1903+
1904+
it('should preserve consecutive whitespaces in text', () => {
1905+
const content = ` foo \n bar baz `
1906+
const ast = parse(content)
1907+
expect((ast.children[0] as TextNode).content).toBe(content)
1908+
})
1909+
})
1910+
18271911
describe('Errors', () => {
18281912
const patterns: {
18291913
[key: string]: Array<{

packages/compiler-core/src/options.ts

+4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ export interface ParserOptions {
4444
* @default ['{{', '}}']
4545
*/
4646
delimiters?: [string, string]
47+
/**
48+
* Whitespace handling strategy
49+
*/
50+
whitespace?: 'preserve' | 'condense'
4751
/**
4852
* Only needed for DOM compilers
4953
*/

packages/compiler-core/src/parse.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const decodeMap: Record<string, string> = {
5151

5252
export const defaultParserOptions: MergedParserOptions = {
5353
delimiters: [`{{`, `}}`],
54+
whitespace: 'condense',
5455
getNamespace: () => Namespaces.HTML,
5556
getTextMode: () => TextModes.DATA,
5657
isVoidTag: NO,
@@ -202,10 +203,9 @@ function parseChildren(
202203
}
203204
}
204205

205-
// Whitespace management for more efficient output
206-
// (same as v2 whitespace: 'condense')
206+
// Whitespace handling strategy like v2
207207
let removedWhitespace = false
208-
if (mode !== TextModes.RAWTEXT && mode !== TextModes.RCDATA) {
208+
if (context.options.whitespace === 'condense' && mode !== TextModes.RAWTEXT && mode !== TextModes.RCDATA) {
209209
for (let i = 0; i < nodes.length; i++) {
210210
const node = nodes[i]
211211
if (!context.inPre && node.type === NodeTypes.TEXT) {

0 commit comments

Comments
 (0)