Skip to content

Commit 18b77fc

Browse files
committed
feat(compiler): support native v-if directive
1 parent 1289392 commit 18b77fc

File tree

17 files changed

+839
-296
lines changed

17 files changed

+839
-296
lines changed

packages/compiler/src/compile.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
} from './ir'
3030
import { transformVFor } from './transforms/vFor'
3131
import { transformVOnce } from './transforms/vOnce'
32+
import { transformVIf } from './transforms/vIf'
3233
import type { CompilerOptions as BaseCompilerOptions } from '@vue/compiler-dom'
3334
import type { ExpressionStatement, JSXElement, JSXFragment } from '@babel/types'
3435

@@ -112,6 +113,7 @@ export function getBaseTransformPreset(): TransformPreset {
112113
return [
113114
[
114115
transformVOnce,
116+
transformVIf,
115117
transformVFor,
116118
transformTemplateRef,
117119
transformText,

packages/compiler/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ export { transformVModel } from './transforms/vModel'
2222
export { transformVShow } from './transforms/vShow'
2323
export { transformVHtml } from './transforms/vHtml'
2424
export { transformVFor } from './transforms/vFor'
25+
export { transformVIf } from './transforms/vIf'
26+
export { transformVOnce } from './transforms/vOnce'
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { isStaticNode } from '@vue/compiler-dom'
2+
import { DynamicFlag, IRNodeTypes, type OperationNode } from '../ir'
3+
import { resolveExpression } from '../utils'
4+
import { type TransformContext, transformNode } from '../transform'
5+
import { createBranch } from './utils'
6+
import type { ConditionalExpression, LogicalExpression } from '@babel/types'
7+
8+
export function processConditionalExpression(
9+
node: ConditionalExpression,
10+
context: TransformContext,
11+
) {
12+
const { test, consequent, alternate } = node
13+
14+
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT
15+
const id = context.reference()
16+
const condition = resolveExpression(test, context)
17+
const [branch, onExit] = createBranch(consequent, context)
18+
const operation: OperationNode = {
19+
type: IRNodeTypes.IF,
20+
id,
21+
condition,
22+
positive: branch,
23+
once: context.inVOnce || isStaticNode(test),
24+
}
25+
26+
return [
27+
() => {
28+
onExit()
29+
context.registerOperation(operation)
30+
},
31+
() => {
32+
const [branch, onExit] = createBranch(alternate, context)
33+
operation.negative = branch
34+
transformNode(context)
35+
onExit()
36+
},
37+
]
38+
}
39+
40+
export function processLogicalExpression(
41+
node: LogicalExpression,
42+
context: TransformContext,
43+
) {
44+
const { left, right, operator } = node
45+
46+
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE
47+
context.dynamic.flags |= DynamicFlag.INSERT
48+
49+
const id = context.reference()
50+
const condition = resolveExpression(left, context)
51+
const [branch, onExit] = createBranch(
52+
operator === '&&' ? right : left,
53+
context,
54+
)
55+
const operation: OperationNode = {
56+
type: IRNodeTypes.IF,
57+
id,
58+
condition,
59+
positive: branch,
60+
once: context.inVOnce,
61+
}
62+
63+
return [
64+
() => {
65+
onExit()
66+
context.registerOperation(operation)
67+
},
68+
() => {
69+
const [branch, onExit] = createBranch(
70+
operator === '&&' ? left : right,
71+
context,
72+
)
73+
operation.negative = branch
74+
transformNode(context)
75+
onExit()
76+
},
77+
]
78+
}

packages/compiler/src/transforms/transformText.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import {
1212
resolveExpression,
1313
resolveJSXText,
1414
} from '../utils'
15-
import { processConditionalExpression, processLogicalExpression } from './vIf'
15+
import {
16+
processConditionalExpression,
17+
processLogicalExpression,
18+
} from './expression'
1619
import type { NodeTransform, TransformContext } from '../transform'
1720
import type {
1821
JSXElement,
@@ -67,9 +70,13 @@ function processTextLike(context: TransformContext<JSXExpressionContainer>) {
6770
const idx = nexts.findIndex((n) => !isTextLike(n))
6871
const nodes = (idx > -1 ? nexts.slice(0, idx) : nexts) as Array<TextLike>
6972

70-
const id = context.reference()
7173
const values = createTextLikeExpressions(nodes, context)
74+
if (!values.length) {
75+
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE
76+
return
77+
}
7278

79+
const id = context.reference()
7380
context.dynamic.flags |= DynamicFlag.INSERT | DynamicFlag.NON_TEMPLATE
7481

7582
context.registerOperation({
Lines changed: 130 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,142 @@
1-
import { isStaticNode } from '@vue/compiler-dom'
2-
import { DynamicFlag, IRNodeTypes, type OperationNode } from '../ir'
3-
import { resolveExpression } from '../utils'
4-
import { type TransformContext, transformNode } from '../transform'
1+
import {
2+
ErrorCodes,
3+
createCompilerError,
4+
createSimpleExpression,
5+
isConstantNode,
6+
} from '@vue/compiler-dom'
7+
import {
8+
type NodeTransform,
9+
type TransformContext,
10+
createStructuralDirectiveTransform,
11+
} from '../transform'
12+
import { DynamicFlag, IRNodeTypes } from '../ir'
13+
import { isEmptyText, resolveDirectiveNode, resolveLocation } from '../utils'
514
import { createBranch } from './utils'
6-
import type { ConditionalExpression, LogicalExpression } from '@babel/types'
15+
import type { JSXAttribute, JSXElement } from '@babel/types'
716

8-
export function processConditionalExpression(
9-
node: ConditionalExpression,
17+
export const transformVIf: NodeTransform = createStructuralDirectiveTransform(
18+
['if', 'else', 'else-if'],
19+
processIf,
20+
)
21+
22+
export const transformedIfNode = new WeakMap()
23+
24+
export function processIf(
25+
node: JSXElement,
26+
attribute: JSXAttribute,
1027
context: TransformContext,
11-
) {
12-
const { test, consequent, alternate } = node
13-
14-
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT
15-
const id = context.reference()
16-
const condition = resolveExpression(test, context)
17-
const [branch, onExit] = createBranch(consequent, context)
18-
const operation: OperationNode = {
19-
type: IRNodeTypes.IF,
20-
id,
21-
condition,
22-
positive: branch,
23-
once: context.inVOnce || isStaticNode(test),
28+
): (() => void) | undefined {
29+
const dir = resolveDirectiveNode(attribute, context)
30+
if (dir.name !== 'else' && (!dir.exp || !dir.exp.content.trim())) {
31+
const loc = dir.exp ? dir.exp.loc : resolveLocation(node.loc, context)
32+
context.options.onError(
33+
createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc),
34+
)
35+
dir.exp = createSimpleExpression(`true`, false, loc)
2436
}
2537

26-
return [
27-
() => {
28-
onExit()
29-
context.registerOperation(operation)
30-
},
31-
() => {
32-
const [branch, onExit] = createBranch(alternate, context)
33-
operation.negative = branch
34-
transformNode(context)
38+
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE
39+
transformedIfNode.set(node, dir)
40+
if (dir.name === 'if') {
41+
const id = context.reference()
42+
context.dynamic.flags |= DynamicFlag.INSERT
43+
const [branch, onExit] = createBranch(node, context)
44+
45+
return () => {
3546
onExit()
36-
},
37-
]
47+
context.registerOperation({
48+
type: IRNodeTypes.IF,
49+
id,
50+
condition: dir.exp!,
51+
positive: branch,
52+
once:
53+
context.inVOnce ||
54+
isConstantNode(attribute.value!, context.options.bindingMetadata),
55+
})
56+
}
57+
} else {
58+
// check the adjacent v-if
59+
const siblingIf = getSiblingIf(context as TransformContext<JSXElement>)
60+
61+
const { operation } = context.block
62+
let lastIfNode = operation.at(-1)
63+
64+
if (
65+
// check if v-if is the sibling node
66+
!siblingIf ||
67+
// check if IfNode is the last operation and get the root IfNode
68+
!lastIfNode ||
69+
lastIfNode.type !== IRNodeTypes.IF
70+
) {
71+
context.options.onError(
72+
createCompilerError(
73+
ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
74+
resolveLocation(node.loc, context),
75+
),
76+
)
77+
return
78+
}
79+
80+
while (lastIfNode.negative && lastIfNode.negative.type === IRNodeTypes.IF) {
81+
lastIfNode = lastIfNode.negative
82+
}
83+
84+
// Check if v-else was followed by v-else-if
85+
if (dir.name === 'else-if' && lastIfNode.negative) {
86+
context.options.onError(
87+
createCompilerError(
88+
ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
89+
resolveLocation(node.loc, context),
90+
),
91+
)
92+
}
93+
94+
// TODO ignore comments if the v-if is direct child of <transition> (PR #3622)
95+
// if (__DEV__ && context.root.comment.length) {
96+
// node = wrapTemplate(node, ['else-if', 'else'])
97+
// context.node = node = extend({}, node, {
98+
// children: [...context.comment, ...node.children],
99+
// })
100+
// }
101+
context.root.comment = []
102+
103+
const [branch, onExit] = createBranch(node, context)
104+
105+
if (dir.name === 'else') {
106+
lastIfNode.negative = branch
107+
} else {
108+
lastIfNode.negative = {
109+
type: IRNodeTypes.IF,
110+
id: -1,
111+
condition: dir.exp!,
112+
positive: branch,
113+
once: context.inVOnce,
114+
}
115+
}
116+
117+
return () => onExit()
118+
}
38119
}
39120

40-
export function processLogicalExpression(
41-
node: LogicalExpression,
42-
context: TransformContext,
43-
) {
44-
const { left, right, operator } = node
121+
export function getSiblingIf(context: TransformContext<JSXElement>) {
122+
const parent = context.parent
123+
if (!parent) return
45124

46-
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE
47-
context.dynamic.flags |= DynamicFlag.INSERT
48-
49-
const id = context.reference()
50-
const condition = resolveExpression(left, context)
51-
const [branch, onExit] = createBranch(
52-
operator === '&&' ? right : left,
53-
context,
54-
)
55-
const operation: OperationNode = {
56-
type: IRNodeTypes.IF,
57-
id,
58-
condition,
59-
positive: branch,
60-
once: context.inVOnce,
125+
const siblings = parent.node.children
126+
let sibling
127+
let i = siblings.indexOf(context.node)
128+
while (--i >= 0) {
129+
if (!isEmptyText(siblings[i])) {
130+
sibling = siblings[i]
131+
break
132+
}
61133
}
62134

63-
return [
64-
() => {
65-
onExit()
66-
context.registerOperation(operation)
67-
},
68-
() => {
69-
const [branch, onExit] = createBranch(
70-
operator === '&&' ? left : right,
71-
context,
72-
)
73-
operation.negative = branch
74-
transformNode(context)
75-
onExit()
76-
},
77-
]
135+
if (
136+
sibling &&
137+
sibling.type === 'JSXElement' &&
138+
transformedIfNode.has(sibling)
139+
) {
140+
return sibling
141+
}
78142
}

packages/compiler/src/transforms/vSlot.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ function transformComponentSlot(
108108
}
109109
}
110110

111-
const elseIfRE = /^else(-if)?$/
111+
const elseIfRE = /^v-else(-if)?$/
112112
// <template #foo>
113113
function transformTemplateSlot(
114114
node: JSXElement,

packages/compiler/src/utils.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -288,15 +288,16 @@ export function resolveDirectiveNode(
288288
withFn = false,
289289
): VaporDirectiveNode {
290290
const { value, name } = node
291-
const nameString =
291+
let nameString =
292292
name.type === 'JSXNamespacedName'
293293
? name.namespace.name
294294
: name.type === 'JSXIdentifier'
295295
? name.name
296296
: ''
297297
let argString = name.type === 'JSXNamespacedName' ? name.name.name : ''
298298
if (name.type !== 'JSXNamespacedName' && !argString) {
299-
const [, modifiers] = nameString.split('_')
299+
const [newName, modifiers] = nameString.split('_')
300+
nameString = newName
300301
argString = `_${modifiers}`
301302
}
302303

@@ -328,7 +329,7 @@ export function resolveDirectiveNode(
328329

329330
return {
330331
type: NodeTypes.DIRECTIVE,
331-
name: nameString,
332+
name: nameString.slice(2),
332333
rawName: `${nameString}:${argString}`,
333334
exp,
334335
arg,

0 commit comments

Comments
 (0)