Browse Source

feat(compiler): add whitespace option, deprecate preserveWhitespace option

close #9208
dev
Evan You 6 years ago
parent
commit
e1abedb9e6
  1. 3
      flow/compiler.js
  2. 46
      packages/vue-template-compiler/README.md
  3. 1
      packages/vue-template-compiler/types/index.d.ts
  4. 1
      packages/vue-template-compiler/types/test.ts
  5. 34
      src/compiler/parser/index.js
  6. 41
      test/unit/modules/compiler/parser.spec.js

3
flow/compiler.js

@ -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

46
packages/vue-template-compiler/README.md

@ -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 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.**
The optional `options` object can contain the following:
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`
@ -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.
---

1
packages/vue-template-compiler/types/index.d.ts

@ -7,6 +7,7 @@ interface CompilerOptions {
modules?: ModuleOptions[];
directives?: Record<string, DirectiveFunction>;
preserveWhitespace?: boolean;
whitespace?: 'preserve' | 'condense';
}
interface CompiledResult {

1
packages/vue-template-compiler/types/test.ts

@ -10,6 +10,7 @@ import {
// check compile options
const compiled = compile("<div>hi</div>", {
preserveWhitespace: false,
whitespace: 'condense',
modules: [
{
preTransformNode: el => el,

34
src/compiler/parser/index.js

@ -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
@ -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
@ -235,12 +239,14 @@ export function parse (
},
end (tag, start, end) {
// remove trailing whitespace
const element = stack[stack.length - 1]
if (!inPre) {
// remove trailing whitespace node
const lastNode = element.children[element.children.length - 1]
if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {
if (lastNode && lastNode.type === 3 && lastNode.text === ' ') {
element.children.pop()
}
}
// pop stack
stack.length -= 1
currentParent = stack[stack.length - 1]
@ -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))) {

41
test/unit/modules/compiler/parser.spec.js

@ -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! ')
})
})

Loading…
Cancel
Save