@@ -64,7 +64,8 @@ interface ParserContext {
6464 offset : number
6565 line : number
6666 column : number
67- inPre : boolean
67+ inPre : boolean // HTML <pre> tag, preserve whitespaces
68+ inVPre : boolean // v-pre, do not process directives and interpolations
6869}
6970
7071export function baseParse (
@@ -93,7 +94,8 @@ function createParserContext(
9394 offset : 0 ,
9495 originalSource : content ,
9596 source : content ,
96- inPre : false
97+ inPre : false ,
98+ inVPre : false
9799 }
98100}
99101
@@ -112,7 +114,7 @@ function parseChildren(
112114 let node : TemplateChildNode | TemplateChildNode [ ] | undefined = undefined
113115
114116 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 ] ) ) {
116118 // '{{'
117119 node = parseInterpolation ( context , mode )
118120 } else if ( mode === TextModes . DATA && s [ 0 ] === '<' ) {
@@ -187,41 +189,47 @@ function parseChildren(
187189 // Whitespace management for more efficient output
188190 // (same as v2 whitespace: 'condense')
189191 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+ }
216221 } 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, ' ' )
220223 }
221- } else {
222- node . content = node . content . replace ( / \s + / g, ' ' )
223224 }
224225 }
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+ }
225233 }
226234 }
227235
@@ -347,9 +355,11 @@ function parseElement(
347355
348356 // Start tag.
349357 const wasInPre = context . inPre
358+ const wasInVPre = context . inVPre
350359 const parent = last ( ancestors )
351360 const element = parseTag ( context , TagType . Start , parent )
352361 const isPreBoundary = context . inPre && ! wasInPre
362+ const isVPreBoundary = context . inVPre && ! wasInVPre
353363
354364 if ( element . isSelfClosing || context . options . isVoidTag ( element . tag ) ) {
355365 return element
@@ -381,6 +391,9 @@ function parseElement(
381391 if ( isPreBoundary ) {
382392 context . inPre = false
383393 }
394+ if ( isVPreBoundary ) {
395+ context . inVPre = false
396+ }
384397 return element
385398}
386399
@@ -423,12 +436,17 @@ function parseTag(
423436 // Attributes.
424437 let props = parseAttributes ( context , type )
425438
439+ // check <pre> tag
440+ if ( context . options . isPreTag ( tag ) ) {
441+ context . inPre = true
442+ }
443+
426444 // check v-pre
427445 if (
428- ! context . inPre &&
446+ ! context . inVPre &&
429447 props . some ( p => p . type === NodeTypes . DIRECTIVE && p . name === 'pre' )
430448 ) {
431- context . inPre = true
449+ context . inVPre = true
432450 // reset context
433451 extend ( context , cursor )
434452 context . source = currentSource
@@ -450,7 +468,7 @@ function parseTag(
450468
451469 let tagType = ElementTypes . ELEMENT
452470 const options = context . options
453- if ( ! context . inPre && ! options . isCustomElement ( tag ) ) {
471+ if ( ! context . inVPre && ! options . isCustomElement ( tag ) ) {
454472 const hasVIs = props . some (
455473 p => p . type === NodeTypes . DIRECTIVE && p . name === 'is'
456474 )
@@ -580,7 +598,7 @@ function parseAttribute(
580598 }
581599 const loc = getSelection ( context , start )
582600
583- if ( ! context . inPre && / ^ ( v - | : | @ | # ) / . test ( name ) ) {
601+ if ( ! context . inVPre && / ^ ( v - | : | @ | # ) / . test ( name ) ) {
584602 const match = / (?: ^ v - ( [ a - z 0 - 9 - ] + ) ) ? (?: (?: : | ^ @ | ^ # ) ( [ ^ \. ] + ) ) ? ( .+ ) ? $ / i. exec (
585603 name
586604 ) !
0 commit comments