Skip to content

Commit 5dfb271

Browse files
committed
feat(compiler): implement support for v-pre
1 parent 08df965 commit 5dfb271

File tree

2 files changed

+151
-33
lines changed

2 files changed

+151
-33
lines changed

packages/compiler-core/__tests__/parse.spec.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,6 +1278,88 @@ describe('compiler: parse', () => {
12781278
})
12791279
})
12801280

1281+
test('v-pre', () => {
1282+
const ast = parse(
1283+
`<div v-pre :id="foo"><Comp/>{{ bar }}</div>\n` +
1284+
`<div :id="foo"><Comp/>{{ bar }}</div>`
1285+
)
1286+
1287+
const divWithPre = ast.children[0] as ElementNode
1288+
expect(divWithPre.props).toMatchObject([
1289+
{
1290+
type: NodeTypes.ATTRIBUTE,
1291+
name: `:id`,
1292+
value: {
1293+
type: NodeTypes.TEXT,
1294+
content: `foo`
1295+
},
1296+
loc: {
1297+
source: `:id="foo"`,
1298+
start: {
1299+
line: 1,
1300+
column: 12
1301+
},
1302+
end: {
1303+
line: 1,
1304+
column: 21
1305+
}
1306+
}
1307+
}
1308+
])
1309+
expect(divWithPre.children[0]).toMatchObject({
1310+
type: NodeTypes.ELEMENT,
1311+
tagType: ElementTypes.ELEMENT,
1312+
tag: `Comp`
1313+
})
1314+
expect(divWithPre.children[1]).toMatchObject({
1315+
type: NodeTypes.TEXT,
1316+
content: `{{ bar }}`
1317+
})
1318+
1319+
// should not affect siblings after it
1320+
const divWithoutPre = ast.children[1] as ElementNode
1321+
expect(divWithoutPre.props).toMatchObject([
1322+
{
1323+
type: NodeTypes.DIRECTIVE,
1324+
name: `bind`,
1325+
arg: {
1326+
type: NodeTypes.SIMPLE_EXPRESSION,
1327+
isStatic: true,
1328+
content: `id`
1329+
},
1330+
exp: {
1331+
type: NodeTypes.SIMPLE_EXPRESSION,
1332+
isStatic: false,
1333+
content: `foo`
1334+
},
1335+
loc: {
1336+
source: `:id="foo"`,
1337+
start: {
1338+
line: 2,
1339+
column: 6
1340+
},
1341+
end: {
1342+
line: 2,
1343+
column: 15
1344+
}
1345+
}
1346+
}
1347+
])
1348+
expect(divWithoutPre.children[0]).toMatchObject({
1349+
type: NodeTypes.ELEMENT,
1350+
tagType: ElementTypes.COMPONENT,
1351+
tag: `Comp`
1352+
})
1353+
expect(divWithoutPre.children[1]).toMatchObject({
1354+
type: NodeTypes.INTERPOLATION,
1355+
content: {
1356+
type: NodeTypes.SIMPLE_EXPRESSION,
1357+
content: `bar`,
1358+
isStatic: false
1359+
}
1360+
})
1361+
})
1362+
12811363
test('end tags are case-insensitive.', () => {
12821364
const ast = parse('<div>hello</DIV>after')
12831365
const element = ast.children[0] as ElementNode

packages/compiler-core/src/parse.ts

Lines changed: 69 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
TemplateChildNode,
2727
InterpolationNode
2828
} from './ast'
29+
import { extend } from '@vue/shared'
2930

3031
export interface ParserOptions {
3132
isVoidTag?: (tag: string) => boolean // e.g. img, br, hr
@@ -74,6 +75,7 @@ interface ParserContext {
7475
line: number
7576
column: number
7677
maxCRNameLength: number
78+
inPre: boolean
7779
}
7880

7981
export function parse(content: string, options: ParserOptions = {}): RootNode {
@@ -109,7 +111,8 @@ function createParserContext(
109111
maxCRNameLength: Object.keys(
110112
options.namedCharacterReferences ||
111113
defaultParserOptions.namedCharacterReferences
112-
).reduce((max, name) => Math.max(max, name.length), 0)
114+
).reduce((max, name) => Math.max(max, name.length), 0),
115+
inPre: false
113116
}
114117
}
115118

@@ -127,7 +130,7 @@ function parseChildren(
127130
const s = context.source
128131
let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined
129132

130-
if (startsWith(s, context.options.delimiters[0])) {
133+
if (!context.inPre && startsWith(s, context.options.delimiters[0])) {
131134
// '{{'
132135
node = parseInterpolation(context, mode)
133136
} else if (mode === TextModes.DATA && s[0] === '<') {
@@ -325,8 +328,10 @@ function parseElement(
325328
__DEV__ && assert(/^<[a-z]/i.test(context.source))
326329

327330
// Start tag.
331+
const wasInPre = context.inPre
328332
const parent = last(ancestors)
329333
const element = parseTag(context, TagType.Start, parent)
334+
const isPreBoundary = context.inPre && !wasInPre
330335

331336
if (element.isSelfClosing || context.options.isVoidTag(element.tag)) {
332337
return element
@@ -354,6 +359,10 @@ function parseElement(
354359
}
355360

356361
element.loc = getSelection(context, element.loc.start)
362+
363+
if (isPreBoundary) {
364+
context.inPre = false
365+
}
357366
return element
358367
}
359368

@@ -380,43 +389,29 @@ function parseTag(
380389
const start = getCursor(context)
381390
const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!
382391
const tag = match[1]
383-
const props = []
384392
const ns = context.options.getNamespace(tag, parent)
385393

386-
let tagType = ElementTypes.ELEMENT
387-
if (tag === 'slot') tagType = ElementTypes.SLOT
388-
else if (tag === 'template') tagType = ElementTypes.TEMPLATE
389-
else if (/[A-Z-]/.test(tag)) tagType = ElementTypes.COMPONENT
390-
391394
advanceBy(context, match[0].length)
392395
advanceSpaces(context)
393396

394-
// Attributes.
395-
const attributeNames = new Set<string>()
396-
while (
397-
context.source.length > 0 &&
398-
!startsWith(context.source, '>') &&
399-
!startsWith(context.source, '/>')
400-
) {
401-
if (startsWith(context.source, '/')) {
402-
emitError(context, ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG)
403-
advanceBy(context, 1)
404-
advanceSpaces(context)
405-
continue
406-
}
407-
if (type === TagType.End) {
408-
emitError(context, ErrorCodes.END_TAG_WITH_ATTRIBUTES)
409-
}
397+
// save current state in case we need to re-parse attributes with v-pre
398+
const cursor = getCursor(context)
399+
const currentSource = context.source
410400

411-
const attr = parseAttribute(context, attributeNames)
412-
if (type === TagType.Start) {
413-
props.push(attr)
414-
}
401+
// Attributes.
402+
let props = parseAttributes(context, type)
415403

416-
if (/^[^\t\r\n\f />]/.test(context.source)) {
417-
emitError(context, ErrorCodes.MISSING_WHITESPACE_BETWEEN_ATTRIBUTES)
418-
}
419-
advanceSpaces(context)
404+
// check v-pre
405+
if (
406+
!context.inPre &&
407+
props.some(p => p.type === NodeTypes.DIRECTIVE && p.name === 'pre')
408+
) {
409+
context.inPre = true
410+
// reset context
411+
extend(context, cursor)
412+
context.source = currentSource
413+
// re-parse attrs and filter out v-pre itself
414+
props = parseAttributes(context, type).filter(p => p.name !== 'v-pre')
420415
}
421416

422417
// Tag close.
@@ -431,6 +426,13 @@ function parseTag(
431426
advanceBy(context, isSelfClosing ? 2 : 1)
432427
}
433428

429+
let tagType = ElementTypes.ELEMENT
430+
if (!context.inPre) {
431+
if (tag === 'slot') tagType = ElementTypes.SLOT
432+
else if (tag === 'template') tagType = ElementTypes.TEMPLATE
433+
else if (/[A-Z-]/.test(tag)) tagType = ElementTypes.COMPONENT
434+
}
435+
434436
return {
435437
type: NodeTypes.ELEMENT,
436438
ns,
@@ -444,6 +446,40 @@ function parseTag(
444446
}
445447
}
446448

449+
function parseAttributes(
450+
context: ParserContext,
451+
type: TagType
452+
): (AttributeNode | DirectiveNode)[] {
453+
const props = []
454+
const attributeNames = new Set<string>()
455+
while (
456+
context.source.length > 0 &&
457+
!startsWith(context.source, '>') &&
458+
!startsWith(context.source, '/>')
459+
) {
460+
if (startsWith(context.source, '/')) {
461+
emitError(context, ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG)
462+
advanceBy(context, 1)
463+
advanceSpaces(context)
464+
continue
465+
}
466+
if (type === TagType.End) {
467+
emitError(context, ErrorCodes.END_TAG_WITH_ATTRIBUTES)
468+
}
469+
470+
const attr = parseAttribute(context, attributeNames)
471+
if (type === TagType.Start) {
472+
props.push(attr)
473+
}
474+
475+
if (/^[^\t\r\n\f />]/.test(context.source)) {
476+
emitError(context, ErrorCodes.MISSING_WHITESPACE_BETWEEN_ATTRIBUTES)
477+
}
478+
advanceSpaces(context)
479+
}
480+
return props
481+
}
482+
447483
function parseAttribute(
448484
context: ParserContext,
449485
nameSet: Set<string>
@@ -497,7 +533,7 @@ function parseAttribute(
497533
}
498534
const loc = getSelection(context, start)
499535

500-
if (/^(v-|:|@|#)/.test(name)) {
536+
if (!context.inPre && /^(v-|:|@|#)/.test(name)) {
501537
const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^@|^#)([^\.]+))?(.+)?$/i.exec(
502538
name
503539
)!

0 commit comments

Comments
 (0)