-
-
Notifications
You must be signed in to change notification settings - Fork 8.4k
/
vIf.ts
355 lines (338 loc) · 10.1 KB
/
vIf.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
import {
createStructuralDirectiveTransform,
TransformContext,
traverseNode
} from '../transform'
import {
NodeTypes,
ElementTypes,
ElementNode,
DirectiveNode,
IfBranchNode,
SimpleExpressionNode,
createCallExpression,
createConditionalExpression,
createSimpleExpression,
createObjectProperty,
createObjectExpression,
IfConditionalExpression,
BlockCodegenNode,
IfNode,
createVNodeCall,
AttributeNode,
locStub,
CacheExpression,
ConstantTypes,
MemoExpression
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
import { validateBrowserExpression } from '../validateExpression'
import { FRAGMENT, CREATE_COMMENT } from '../runtimeHelpers'
import {
injectProp,
findDir,
findProp,
isBuiltInType,
makeBlock
} from '../utils'
import { PatchFlags, PatchFlagNames } from '@vue/shared'
import { getMemoedVNodeCall } from '..'
export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/,
(node, dir, context) => {
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
// #1587: We need to dynamically increment the key based on the current
// node's sibling nodes, since chained v-if/else branches are
// rendered at the same depth
const siblings = context.parent!.children
let i = siblings.indexOf(ifNode)
let key = 0
while (i-- >= 0) {
const sibling = siblings[i]
if (sibling && sibling.type === NodeTypes.IF) {
key += sibling.branches.length
}
}
// Exit callback. Complete the codegenNode when all children have been
// transformed.
return () => {
if (isRoot) {
ifNode.codegenNode = createCodegenNodeForBranch(
branch,
key,
context
) as IfConditionalExpression
} else {
// attach this branch's codegen node to the v-if root.
const parentCondition = getParentCondition(ifNode.codegenNode!)
parentCondition.alternate = createCodegenNodeForBranch(
branch,
key + ifNode.branches.length - 1,
context
)
}
}
})
}
)
// target-agnostic transform used for both Client and SSR
export function processIf(
node: ElementNode,
dir: DirectiveNode,
context: TransformContext,
processCodegen?: (
node: IfNode,
branch: IfBranchNode,
isRoot: boolean
) => (() => void) | undefined
) {
if (
dir.name !== 'else' &&
(!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
) {
const loc = dir.exp ? dir.exp.loc : node.loc
context.onError(
createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc)
)
dir.exp = createSimpleExpression(`true`, false, loc)
}
if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) {
// dir.exp can only be simple expression because vIf transform is applied
// before expression transform.
dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
}
if (__DEV__ && __BROWSER__ && dir.exp) {
validateBrowserExpression(dir.exp as SimpleExpressionNode, context)
}
if (dir.name === 'if') {
const branch = createIfBranch(node, dir)
const ifNode: IfNode = {
type: NodeTypes.IF,
loc: node.loc,
branches: [branch]
}
context.replaceNode(ifNode)
if (processCodegen) {
return processCodegen(ifNode, branch, true)
}
} else {
// locate the adjacent v-if
const siblings = context.parent!.children
const comments = []
let i = siblings.indexOf(node)
while (i-- >= -1) {
const sibling = siblings[i]
if (__DEV__ && sibling && sibling.type === NodeTypes.COMMENT) {
context.removeNode(sibling)
comments.unshift(sibling)
continue
}
if (
sibling &&
sibling.type === NodeTypes.TEXT &&
!sibling.content.trim().length
) {
context.removeNode(sibling)
continue
}
if (sibling && sibling.type === NodeTypes.IF) {
// Check if v-else was followed by v-else-if
if (
dir.name === 'else-if' &&
sibling.branches[sibling.branches.length - 1].condition === undefined
) {
context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
)
}
// move the node to the if node's branches
context.removeNode()
const branch = createIfBranch(node, dir)
if (
__DEV__ &&
comments.length &&
// #3619 ignore comments if the v-if is direct child of <transition>
!(
context.parent &&
context.parent.type === NodeTypes.ELEMENT &&
isBuiltInType(context.parent.tag, 'transition')
)
) {
branch.children = [...comments, ...branch.children]
}
// check if user is forcing same key on different branches
if (__DEV__ || !__BROWSER__) {
const key = branch.userKey
if (key) {
sibling.branches.forEach(({ userKey }) => {
if (isSameKey(userKey, key)) {
context.onError(
createCompilerError(
ErrorCodes.X_V_IF_SAME_KEY,
branch.userKey!.loc
)
)
}
})
}
}
sibling.branches.push(branch)
const onExit = processCodegen && processCodegen(sibling, branch, false)
// since the branch was removed, it will not be traversed.
// make sure to traverse here.
traverseNode(branch, context)
// call on exit
if (onExit) onExit()
// make sure to reset currentNode after traversal to indicate this
// node has been removed.
context.currentNode = null
} else {
context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
)
}
break
}
}
}
function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
const isTemplateIf = node.tagType === ElementTypes.TEMPLATE
return {
type: NodeTypes.IF_BRANCH,
loc: node.loc,
condition: dir.name === 'else' ? undefined : dir.exp,
children: isTemplateIf && !findDir(node, 'for') ? node.children : [node],
userKey: findProp(node, `key`),
isTemplateIf
}
}
function createCodegenNodeForBranch(
branch: IfBranchNode,
keyIndex: number,
context: TransformContext
): IfConditionalExpression | BlockCodegenNode | MemoExpression {
if (branch.condition) {
return createConditionalExpression(
branch.condition,
createChildrenCodegenNode(branch, keyIndex, context),
// make sure to pass in asBlock: true so that the comment node call
// closes the current block.
createCallExpression(context.helper(CREATE_COMMENT), [
__DEV__ ? '"v-if"' : '""',
'true'
])
) as IfConditionalExpression
} else {
return createChildrenCodegenNode(branch, keyIndex, context)
}
}
function createChildrenCodegenNode(
branch: IfBranchNode,
keyIndex: number,
context: TransformContext
): BlockCodegenNode | MemoExpression {
const { helper } = context
const keyProperty = createObjectProperty(
`key`,
createSimpleExpression(
`${keyIndex}`,
false,
locStub,
ConstantTypes.CAN_HOIST
)
)
const { children } = branch
const firstChild = children[0]
const needFragmentWrapper =
children.length !== 1 || firstChild.type !== NodeTypes.ELEMENT
if (needFragmentWrapper) {
if (children.length === 1 && firstChild.type === NodeTypes.FOR) {
// optimize away nested fragments when child is a ForNode
const vnodeCall = firstChild.codegenNode!
injectProp(vnodeCall, keyProperty, context)
return vnodeCall
} else {
let patchFlag = PatchFlags.STABLE_FRAGMENT
let patchFlagText = PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
// check if the fragment actually contains a single valid child with
// the rest being comments
if (
__DEV__ &&
!branch.isTemplateIf &&
children.filter(c => c.type !== NodeTypes.COMMENT).length === 1
) {
patchFlag |= PatchFlags.DEV_ROOT_FRAGMENT
patchFlagText += `, ${PatchFlagNames[PatchFlags.DEV_ROOT_FRAGMENT]}`
}
return createVNodeCall(
context,
helper(FRAGMENT),
createObjectExpression([keyProperty]),
children,
patchFlag + (__DEV__ ? ` /* ${patchFlagText} */` : ``),
undefined,
undefined,
true,
false,
false /* isComponent */,
branch.loc
)
}
} else {
const ret = (firstChild as ElementNode).codegenNode as
| BlockCodegenNode
| MemoExpression
const vnodeCall = getMemoedVNodeCall(ret)
// Change createVNode to createBlock.
if (vnodeCall.type === NodeTypes.VNODE_CALL) {
makeBlock(vnodeCall, context)
}
// inject branch key
injectProp(vnodeCall, keyProperty, context)
return ret
}
}
function isSameKey(
a: AttributeNode | DirectiveNode | undefined,
b: AttributeNode | DirectiveNode
): boolean {
if (!a || a.type !== b.type) {
return false
}
if (a.type === NodeTypes.ATTRIBUTE) {
if (a.value!.content !== (b as AttributeNode).value!.content) {
return false
}
} else {
// directive
const exp = a.exp!
const branchExp = (b as DirectiveNode).exp!
if (exp.type !== branchExp.type) {
return false
}
if (
exp.type !== NodeTypes.SIMPLE_EXPRESSION ||
exp.isStatic !== (branchExp as SimpleExpressionNode).isStatic ||
exp.content !== (branchExp as SimpleExpressionNode).content
) {
return false
}
}
return true
}
function getParentCondition(
node: IfConditionalExpression | CacheExpression
): IfConditionalExpression {
while (true) {
if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {
if (node.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {
node = node.alternate
} else {
return node
}
} else if (node.type === NodeTypes.JS_CACHE_EXPRESSION) {
node = node.value as IfConditionalExpression
}
}
}