Skip to content

Commit

Permalink
feat(compiler): add whitespace option, deprecate preserveWhitespace o…
Browse files Browse the repository at this point in the history
…ption

close #9208
  • Loading branch information
yyx990803 committed Dec 26, 2018
1 parent 9c71852 commit e1abedb
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 13 deletions.
3 changes: 2 additions & 1 deletion flow/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ declare type CompilerOptions = {
isUnaryTag?: (tag: string) => ?boolean; // check if a tag is unary for the platform
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?
preserveWhitespace?: boolean; // preserve whitespace between elements? (Deprecated)
whitespace?: 'preserve' | 'condense'; // whitespace handling strategy
optimize?: boolean; // optimize static content?

// web specific
Expand Down
46 changes: 42 additions & 4 deletions packages/vue-template-compiler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,48 @@ Note the returned function code uses `with` and thus cannot be used in strict mo

#### Options

It's possible to hook into the compilation process to support custom template features. **However, beware that by injecting custom compile-time modules, your templates will not work with other build tools built on standard built-in modules, e.g `vue-loader` and `vueify`.**
- `whitespace`
- Type: `string`
- Valid values: `'preserve' | 'condense'`
- Default: `'preserve'`

The optional `options` object can contain the following:
The default value `'preserve'` handles whitespaces as follows:

- A whitespace-only text node between element tags is condensed into a single space.
- All other whitespaces are preserved as-is.

If set to `'condense'`:

- A whitespace-only text node between element tags is removed if it contains new lines. Otherwise, it is condensed into a single space.
- Consecutive whitespaces inside a non-whitespace-only text node is condensed into a single space.

Using condense mode will result in smaller compiled code size and slightly improved performance. However, it will produce minor visual layout differences compared to plain HTML in certain cases.

**This option does not affect the `<pre>` tag.**

Example:

``` html
<!-- source -->
<div>
<span>
foo
</span> <span>bar</span>
</div>

<!-- whitespace: 'preserve' -->
<div> <span>
foo
</span> <span>bar</span> </div>

<!-- whitespace: 'condense' -->
<div><span> foo </span> <span>bar</span></div>
```

- `modules`

It's possible to hook into the compilation process to support custom template features. **However, beware that by injecting custom compile-time modules, your templates will not work with other build tools built on standard built-in modules, e.g `vue-loader` and `vueify`.**

An array of compiler modules. For details on compiler modules, refer to the `ModuleOptions` type in [flow declarations](https://github.com/vuejs/vue/blob/dev/flow/compiler.js#L38-L45) and the [built-in modules](https://github.com/vuejs/vue/tree/dev/src/platforms/web/compiler/modules).

- `directives`
Expand All @@ -59,9 +95,11 @@ The optional `options` object can contain the following:

Refer to the implementation of some [built-in compile-time directives](https://github.com/vuejs/vue/tree/dev/src/platforms/web/compiler/directives).

- `preserveWhitespace`
- `preserveWhitespace` **Deprecated since 2.6**
- Type: `boolean`
- Default: `true`

Defaults to `true`. This means the compiled render function preserves all whitespace characters between HTML tags. If set to `false`, whitespace between tags will be ignored. This can result in slightly better performance but may affect layout for inline elements.
By default, the compiled render function preserves all whitespace characters between HTML tags. If set to `false`, whitespace between tags will be ignored. This can result in slightly better performance but may affect layout for inline elements.

---

Expand Down
1 change: 1 addition & 0 deletions packages/vue-template-compiler/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface CompilerOptions {
modules?: ModuleOptions[];
directives?: Record<string, DirectiveFunction>;
preserveWhitespace?: boolean;
whitespace?: 'preserve' | 'condense';
}

interface CompiledResult {
Expand Down
1 change: 1 addition & 0 deletions packages/vue-template-compiler/types/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
// check compile options
const compiled = compile("<div>hi</div>", {
preserveWhitespace: false,
whitespace: 'condense',
modules: [
{
preTransformNode: el => el,
Expand Down
38 changes: 30 additions & 8 deletions src/compiler/parser/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ const argRE = /:(.*)$/
export const bindRE = /^:|^v-bind:/
const modifierRE = /\.[^.]+/g

const lineBreakRE = /[\r\n]/
const whitespaceRE = /\s+/g

const decodeHTMLCached = cached(he.decode)

// configurable state
Expand Down Expand Up @@ -79,6 +82,7 @@ export function parse (

const stack = []
const preserveWhitespace = options.preserveWhitespace !== false
const whitespaceOption = options.whitespace
let root
let currentParent
let inVPre = false
Expand Down Expand Up @@ -235,11 +239,13 @@ export function parse (
},

end (tag, start, end) {
// remove trailing whitespace
const element = stack[stack.length - 1]
const lastNode = element.children[element.children.length - 1]
if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {
element.children.pop()
if (!inPre) {
// remove trailing whitespace node
const lastNode = element.children[element.children.length - 1]
if (lastNode && lastNode.type === 3 && lastNode.text === ' ') {
element.children.pop()
}
}
// pop stack
stack.length -= 1
Expand Down Expand Up @@ -276,11 +282,27 @@ export function parse (
return
}
const children = currentParent.children
text = inPre || text.trim()
? isTextTag(currentParent) ? text : decodeHTMLCached(text)
// only preserve whitespace if its not right after a starting tag
: preserveWhitespace && children.length ? ' ' : ''
if (inPre || text.trim()) {
text = isTextTag(currentParent) ? text : decodeHTMLCached(text)
} else if (!children.length) {
// remove the whitespace-only node right after an opening tag
text = ''
} else if (whitespaceOption) {
if (whitespaceOption === 'condense') {
// in condense mode, remove the whitespace node if it contains
// line break, otherwise condense to a single space
text = lineBreakRE.test(text) ? '' : ' '
} else {
text = ' '
}
} else {
text = preserveWhitespace ? ' ' : ''
}
if (text) {
if (whitespaceOption === 'condense') {
// condense consecutive whitespaces into single space
text = text.replace(whitespaceRE, ' ')
}
let res
let child: ?ASTNode
if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {
Expand Down
41 changes: 41 additions & 0 deletions test/unit/modules/compiler/parser.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -757,4 +757,45 @@ describe('parser', () => {
const ast = parse(`<p>{{\r\nmsg\r\n}}</p>`, baseOptions)
expect(ast.children[0].expression).toBe('_s(msg)')
})

it('preserveWhitespace: false', () => {
const options = extend({
preserveWhitespace: false
}, baseOptions)

const ast = parse('<p>\n Welcome to <b>Vue.js</b> <i>world</i> \n <span>.\n Have fun!\n</span></p>', 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].tag).toBe('span')
expect(ast.children[3].children[0].text).toBe('.\n Have fun!\n')
})

it(`whitespace: 'condense'`, () => {
const options = extend({
whitespace: 'condense',
// should be ignored when whitespace is specified
preserveWhitespace: false
}, baseOptions)
const ast = parse('<p>\n Welcome to <b>Vue.js</b> <i>world</i> \n <span>.\n Have fun!\n</span></p>', 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)
// should condense inline whitespace into single space
expect(ast.children[2].text).toBe(' ')
expect(ast.children[3].tag).toBe('i')
expect(ast.children[3].children[0].text).toBe('world')
// should have removed the whitespace node between tags that contains newlines
expect(ast.children[4].tag).toBe('span')
expect(ast.children[4].children[0].text).toBe('. Have fun! ')
})
})

0 comments on commit e1abedb

Please sign in to comment.