diff --git a/flow/compiler.js b/flow/compiler.js index a5f3304de52..316ff65e44b 100644 --- a/flow/compiler.js +++ b/flow/compiler.js @@ -7,6 +7,7 @@ declare type CompilerOptions = { canBeLeftOpenTag?: (tag: string) => ?boolean; // check if a tag can be left opened isReservedTag?: (tag: string) => ?boolean; // check if a tag is a native for the platform preserveWhitespace?: boolean; // preserve whitespace between elements? + removeWhitespace?: string; // how to deal with whitespace between elements? optimize?: boolean; // optimize static content? // web specific diff --git a/src/compiler/parser/index.js b/src/compiler/parser/index.js index 9428cf75e3b..c9433181b7c 100644 --- a/src/compiler/parser/index.js +++ b/src/compiler/parser/index.js @@ -29,6 +29,8 @@ const argRE = /:(.*)$/ export const bindRE = /^:|^v-bind:/ const modifierRE = /\.[^.]+/g +const lineBreakRE = /^[\s\r\n]*\n+[\s\r\n]*|[\s\r\n]*\n+[\s\r\n]*$/g + const decodeHTMLCached = cached(he.decode) // configurable state @@ -79,6 +81,9 @@ export function parse ( const stack = [] const preserveWhitespace = options.preserveWhitespace !== false + // with-line-break + const removeWhitespace = options.removeWhitespace || (preserveWhitespace ? 'none' : 'all') + let root let currentParent let inVPre = false @@ -258,11 +263,16 @@ export function parse ( return } const children = currentParent.children - text = inPre || text.trim() - ? isTextTag(currentParent) ? text : decodeHTMLCached(text) + if (inPre || text.trim()) { + text = isTextTag(currentParent) ? text : decodeHTMLCached(text) + } else { // only preserve whitespace if its not right after a starting tag - : preserveWhitespace && children.length ? ' ' : '' + text = (removeWhitespace !== 'all') && children.length ? ' ' : '' + } if (text) { + if (removeWhitespace === 'with-line-break') { + text = text.replace(lineBreakRE, '') + } let res if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) { children.push({ diff --git a/test/unit/modules/compiler/parser.spec.js b/test/unit/modules/compiler/parser.spec.js index efa5daf4eb3..da5e0f9001c 100644 --- a/test/unit/modules/compiler/parser.spec.js +++ b/test/unit/modules/compiler/parser.spec.js @@ -757,4 +757,81 @@ describe('parser', () => { const ast = parse(`

{{\r\nmsg\r\n}}

`, baseOptions) expect(ast.children[0].expression).toBe('_s(msg)') }) + + it('should remove whitespaces correctly with `preserveWhitespace: false`', () => { + const options = extend({ + preserveWhitespace: false + }, baseOptions) + + const ast = parse('

\n Welcome to Vue.js world.\n Have fun!\n

', options) + expect(ast.tag).toBe('p') + expect(ast.children.length).toBe(4) + expect(ast.children[0].type).toBe(3) + expect(ast.children[0].text).toBe('\n Welcome to ') + expect(ast.children[1].tag).toBe('b') + expect(ast.children[1].children[0].text).toBe('Vue.js') + expect(ast.children[2].tag).toBe('i') + expect(ast.children[2].children[0].text).toBe('world') + expect(ast.children[3].type).toBe(3) + expect(ast.children[3].text).toBe('.\n Have fun!\n') + }) + + it('should remove whitespaces correctly with `removeWhitespace: \'with-line-break\'`', () => { + const options = extend({ + removeWhitespace: 'with-line-break' + }, baseOptions) + + const ast = parse('

\n Welcome to Vue.js world.\n Have fun!\n

', options) + expect(ast.tag).toBe('p') + expect(ast.children.length).toBe(5) + expect(ast.children[0].type).toBe(3) + expect(ast.children[0].text).toBe('Welcome to ') + expect(ast.children[1].tag).toBe('b') + expect(ast.children[1].children[0].text).toBe('Vue.js') + expect(ast.children[2].type).toBe(3) + expect(ast.children[2].text).toBe(' ') + expect(ast.children[3].tag).toBe('i') + expect(ast.children[3].children[0].text).toBe('world') + expect(ast.children[4].type).toBe(3) + expect(ast.children[4].text).toBe('.\n Have fun!') + }) + + it('should remove whitespaces correctly with `removeWhitespace: \'all\'`', () => { + const options = extend({ + removeWhitespace: 'all' + }, baseOptions) + + const ast = parse('

\n Welcome to Vue.js world.\n Have fun!\n

', options) + expect(ast.tag).toBe('p') + expect(ast.children.length).toBe(4) + expect(ast.children[0].type).toBe(3) + expect(ast.children[0].text).toBe('\n Welcome to ') + expect(ast.children[1].tag).toBe('b') + expect(ast.children[1].children[0].text).toBe('Vue.js') + expect(ast.children[2].tag).toBe('i') + expect(ast.children[2].children[0].text).toBe('world') + expect(ast.children[3].type).toBe(3) + expect(ast.children[3].text).toBe('.\n Have fun!\n') + }) + + it('should ignore `preserveWhitespace: false` if `removeWhitespace` is specified', () => { + const options = extend({ + removeWhitespace: 'with-line-break', + preserveWhitespace: false + }, baseOptions) + + const ast = parse('

\n Welcome to Vue.js world.\n Have fun!\n

', options) + expect(ast.tag).toBe('p') + expect(ast.children.length).toBe(5) + expect(ast.children[0].type).toBe(3) + expect(ast.children[0].text).toBe('Welcome to ') + expect(ast.children[1].tag).toBe('b') + expect(ast.children[1].children[0].text).toBe('Vue.js') + expect(ast.children[2].type).toBe(3) + expect(ast.children[2].text).toBe(' ') + expect(ast.children[3].tag).toBe('i') + expect(ast.children[3].children[0].text).toBe('world') + expect(ast.children[4].type).toBe(3) + expect(ast.children[4].text).toBe('.\n Have fun!') + }) })