Skip to content

Commit 04e2b53

Browse files
committed
properly perserve whitespace in <pre> (fix #3341)
1 parent 40e37f1 commit 04e2b53

File tree

6 files changed

+47
-21
lines changed

6 files changed

+47
-21
lines changed

flow/compiler.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ declare type CompilerOptions = {
88
isUnaryTag?: (tag: string) => ?boolean; // check if a tag is unary for the platform
99
isReservedTag?: (tag: string) => ?boolean; // check if a tag is a native for the platform
1010
mustUseProp?: (attr: string) => ?boolean; // check if an attribute should be bound as a property
11+
isPreTag?: (attr: string) => ?boolean; // check if a tag needs to preserve whitespace
1112
getTagNamespace?: (tag: string) => ?string; // check the namespace for a tag
1213
transforms?: Array<Function>; // a list of transforms on parsed AST before codegen
1314
preserveWhitespace?: boolean;

src/compiler/parser/index.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const decodeHTMLCached = cached(decodeHTML)
3030
let warn
3131
let platformGetTagNamespace
3232
let platformMustUseProp
33+
let platformIsPreTag
3334
let preTransforms
3435
let transforms
3536
let postTransforms
@@ -45,6 +46,7 @@ export function parse (
4546
warn = options.warn || baseWarn
4647
platformGetTagNamespace = options.getTagNamespace || no
4748
platformMustUseProp = options.mustUseProp || no
49+
platformIsPreTag = options.isPreTag || no
4850
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
4951
transforms = pluckModuleFunction(options.modules, 'transformNode')
5052
postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
@@ -53,6 +55,7 @@ export function parse (
5355
const preserveWhitespace = options.preserveWhitespace !== false
5456
let root
5557
let currentParent
58+
let inVPre = false
5659
let inPre = false
5760
let warned = false
5861
parseHTML(template, {
@@ -97,13 +100,16 @@ export function parse (
97100
preTransforms[i](element, options)
98101
}
99102

100-
if (!inPre) {
103+
if (!inVPre) {
101104
processPre(element)
102105
if (element.pre) {
103-
inPre = true
106+
inVPre = true
104107
}
105108
}
106-
if (inPre) {
109+
if (platformIsPreTag(element.tag)) {
110+
inPre = true
111+
}
112+
if (inVPre) {
107113
processRawAttrs(element)
108114
} else {
109115
processFor(element)
@@ -178,6 +184,9 @@ export function parse (
178184
currentParent = stack[stack.length - 1]
179185
// check pre state
180186
if (element.pre) {
187+
inVPre = false
188+
}
189+
if (platformIsPreTag(element.tag)) {
181190
inPre = false
182191
}
183192
},
@@ -192,13 +201,13 @@ export function parse (
192201
}
193202
return
194203
}
195-
text = currentParent.tag === 'pre' || text.trim()
204+
text = inPre || text.trim()
196205
? decodeHTMLCached(text)
197206
// only preserve whitespace if its not right after a starting tag
198207
: preserveWhitespace && currentParent.children.length ? ' ' : ''
199208
if (text) {
200209
let expression
201-
if (!inPre && text !== ' ' && (expression = parseText(text, delimiters))) {
210+
if (!inVPre && text !== ' ' && (expression = parseText(text, delimiters))) {
202211
currentParent.children.push({
203212
type: 2,
204213
expression,

src/core/config.js

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,22 @@ import { no, noop } from 'shared/util'
44

55
export type Config = {
66
// user
7-
optionMergeStrategies: { [key: string]: Function },
8-
silent: boolean,
9-
devtools: boolean,
10-
errorHandler: ?Function,
11-
ignoredElements: ?Array<string>,
12-
keyCodes: { [key: string]: number },
7+
optionMergeStrategies: { [key: string]: Function };
8+
silent: boolean;
9+
devtools: boolean;
10+
errorHandler: ?Function;
11+
ignoredElements: ?Array<string>;
12+
keyCodes: { [key: string]: number };
1313
// platform
14-
isReservedTag: (x?: string) => boolean,
15-
isUnknownElement: (x?: string) => boolean,
16-
getTagNamespace: (x?: string) => string | void,
17-
mustUseProp: (x?: string) => boolean,
14+
isReservedTag: (x?: string) => boolean;
15+
isUnknownElement: (x?: string) => boolean;
16+
getTagNamespace: (x?: string) => string | void;
17+
mustUseProp: (x?: string) => boolean;
1818
// internal
19-
_assetTypes: Array<string>,
20-
_lifecycleHooks: Array<string>,
21-
_maxUpdateCount: number,
22-
_isServer: boolean
19+
_assetTypes: Array<string>;
20+
_lifecycleHooks: Array<string>;
21+
_maxUpdateCount: number;
22+
_isServer: boolean;
2323
}
2424

2525
const config: Config = {

src/platforms/web/compiler/index.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import { compile as baseCompile } from 'compiler/index'
66
import { detectErrors } from 'compiler/error-detector'
77
import modules from './modules/index'
88
import directives from './directives/index'
9-
import { isIE, isReservedTag, isUnaryTag, mustUseProp, getTagNamespace } from '../util/index'
9+
import {
10+
isIE, isReservedTag, isUnaryTag,
11+
mustUseProp, getTagNamespace, isPreTag
12+
} from '../util/index'
1013

1114
const cache: { [key: string]: CompiledFunctionResult } = Object.create(null)
1215

@@ -19,7 +22,8 @@ export const baseOptions: CompilerOptions = {
1922
isReservedTag,
2023
isUnaryTag,
2124
mustUseProp,
22-
getTagNamespace
25+
getTagNamespace,
26+
isPreTag
2327
}
2428

2529
export function compile (

src/platforms/web/util/element.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ export const isSVG = makeMap(
5555
true
5656
)
5757

58+
export const isPreTag = (tag: ?string): boolean => tag === 'pre'
59+
5860
export const isReservedTag = (tag: string): ?boolean => {
5961
return isHTMLTag(tag) || isSVG(tag)
6062
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,4 +310,14 @@ describe('parser', () => {
310310
expect(spy1).toHaveBeenCalledWith('img')
311311
expect(spy2).toHaveBeenCalledWith('img')
312312
})
313+
314+
it('preserve whitespace in <pre> tag', function () {
315+
const options = extend({}, baseOptions)
316+
const ast = parse('<pre><code> \n<span>hi</span>\n </code></pre>', options)
317+
const code = ast.children[0]
318+
expect(code.children[0].type).toBe(3)
319+
expect(code.children[0].text).toBe(' \n')
320+
expect(code.children[2].type).toBe(3)
321+
expect(code.children[2].text).toBe('\n ')
322+
})
313323
})

0 commit comments

Comments
 (0)