@@ -64,7 +64,8 @@ interface ParserContext {
64
64
offset : number
65
65
line : number
66
66
column : number
67
- inPre : boolean
67
+ inPre : boolean // HTML <pre> tag, preserve whitespaces
68
+ inVPre : boolean // v-pre, do not process directives and interpolations
68
69
}
69
70
70
71
export function baseParse (
@@ -93,7 +94,8 @@ function createParserContext(
93
94
offset : 0 ,
94
95
originalSource : content ,
95
96
source : content ,
96
- inPre : false
97
+ inPre : false ,
98
+ inVPre : false
97
99
}
98
100
}
99
101
@@ -112,7 +114,7 @@ function parseChildren(
112
114
let node : TemplateChildNode | TemplateChildNode [ ] | undefined = undefined
113
115
114
116
if ( mode === TextModes . DATA || mode === TextModes . RCDATA ) {
115
- if ( ! context . inPre && startsWith ( s , context . options . delimiters [ 0 ] ) ) {
117
+ if ( ! context . inVPre && startsWith ( s , context . options . delimiters [ 0 ] ) ) {
116
118
// '{{'
117
119
node = parseInterpolation ( context , mode )
118
120
} else if ( mode === TextModes . DATA && s [ 0 ] === '<' ) {
@@ -187,41 +189,47 @@ function parseChildren(
187
189
// Whitespace management for more efficient output
188
190
// (same as v2 whitespace: 'condense')
189
191
let removedWhitespace = false
190
- if (
191
- mode !== TextModes . RAWTEXT &&
192
- ( ! parent || ! context . options . isPreTag ( parent . tag ) )
193
- ) {
194
- for ( let i = 0 ; i < nodes . length ; i ++ ) {
195
- const node = nodes [ i ]
196
- if ( node . type === NodeTypes . TEXT ) {
197
- if ( ! node . content . trim ( ) ) {
198
- const prev = nodes [ i - 1 ]
199
- const next = nodes [ i + 1 ]
200
- // If:
201
- // - the whitespace is the first or last node, or:
202
- // - the whitespace is adjacent to a comment, or:
203
- // - the whitespace is between two elements AND contains newline
204
- // Then the whitespace is ignored.
205
- if (
206
- ! prev ||
207
- ! next ||
208
- prev . type === NodeTypes . COMMENT ||
209
- next . type === NodeTypes . COMMENT ||
210
- ( prev . type === NodeTypes . ELEMENT &&
211
- next . type === NodeTypes . ELEMENT &&
212
- / [ \r \n ] / . test ( node . content ) )
213
- ) {
214
- removedWhitespace = true
215
- nodes [ i ] = null as any
192
+ if ( mode !== TextModes . RAWTEXT ) {
193
+ if ( ! context . inPre ) {
194
+ for ( let i = 0 ; i < nodes . length ; i ++ ) {
195
+ const node = nodes [ i ]
196
+ if ( node . type === NodeTypes . TEXT ) {
197
+ if ( ! node . content . trim ( ) ) {
198
+ const prev = nodes [ i - 1 ]
199
+ const next = nodes [ i + 1 ]
200
+ // If:
201
+ // - the whitespace is the first or last node, or:
202
+ // - the whitespace is adjacent to a comment, or:
203
+ // - the whitespace is between two elements AND contains newline
204
+ // Then the whitespace is ignored.
205
+ if (
206
+ ! prev ||
207
+ ! next ||
208
+ prev . type === NodeTypes . COMMENT ||
209
+ next . type === NodeTypes . COMMENT ||
210
+ ( prev . type === NodeTypes . ELEMENT &&
211
+ next . type === NodeTypes . ELEMENT &&
212
+ / [ \r \n ] / . test ( node . content ) )
213
+ ) {
214
+ removedWhitespace = true
215
+ nodes [ i ] = null as any
216
+ } else {
217
+ // Otherwise, condensed consecutive whitespace inside the text down to
218
+ // a single space
219
+ node . content = ' '
220
+ }
216
221
} else {
217
- // Otherwise, condensed consecutive whitespace inside the text down to
218
- // a single space
219
- node . content = ' '
222
+ node . content = node . content . replace ( / \s + / g, ' ' )
220
223
}
221
- } else {
222
- node . content = node . content . replace ( / \s + / g, ' ' )
223
224
}
224
225
}
226
+ } else {
227
+ // remove leading newline per html spec
228
+ // https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
229
+ const first = nodes [ 0 ]
230
+ if ( first && first . type === NodeTypes . TEXT ) {
231
+ first . content = first . content . replace ( / ^ \r ? \n / , '' )
232
+ }
225
233
}
226
234
}
227
235
@@ -347,9 +355,11 @@ function parseElement(
347
355
348
356
// Start tag.
349
357
const wasInPre = context . inPre
358
+ const wasInVPre = context . inVPre
350
359
const parent = last ( ancestors )
351
360
const element = parseTag ( context , TagType . Start , parent )
352
361
const isPreBoundary = context . inPre && ! wasInPre
362
+ const isVPreBoundary = context . inVPre && ! wasInVPre
353
363
354
364
if ( element . isSelfClosing || context . options . isVoidTag ( element . tag ) ) {
355
365
return element
@@ -381,6 +391,9 @@ function parseElement(
381
391
if ( isPreBoundary ) {
382
392
context . inPre = false
383
393
}
394
+ if ( isVPreBoundary ) {
395
+ context . inVPre = false
396
+ }
384
397
return element
385
398
}
386
399
@@ -423,12 +436,17 @@ function parseTag(
423
436
// Attributes.
424
437
let props = parseAttributes ( context , type )
425
438
439
+ // check <pre> tag
440
+ if ( context . options . isPreTag ( tag ) ) {
441
+ context . inPre = true
442
+ }
443
+
426
444
// check v-pre
427
445
if (
428
- ! context . inPre &&
446
+ ! context . inVPre &&
429
447
props . some ( p => p . type === NodeTypes . DIRECTIVE && p . name === 'pre' )
430
448
) {
431
- context . inPre = true
449
+ context . inVPre = true
432
450
// reset context
433
451
extend ( context , cursor )
434
452
context . source = currentSource
@@ -450,7 +468,7 @@ function parseTag(
450
468
451
469
let tagType = ElementTypes . ELEMENT
452
470
const options = context . options
453
- if ( ! context . inPre && ! options . isCustomElement ( tag ) ) {
471
+ if ( ! context . inVPre && ! options . isCustomElement ( tag ) ) {
454
472
const hasVIs = props . some (
455
473
p => p . type === NodeTypes . DIRECTIVE && p . name === 'is'
456
474
)
@@ -580,7 +598,7 @@ function parseAttribute(
580
598
}
581
599
const loc = getSelection ( context , start )
582
600
583
- if ( ! context . inPre && / ^ ( v - | : | @ | # ) / . test ( name ) ) {
601
+ if ( ! context . inVPre && / ^ ( v - | : | @ | # ) / . test ( name ) ) {
584
602
const match = / (?: ^ v - ( [ a - z 0 - 9 - ] + ) ) ? (?: (?: : | ^ @ | ^ # ) ( [ ^ \. ] + ) ) ? ( .+ ) ? $ / i. exec (
585
603
name
586
604
) !
0 commit comments