Skip to content

Commit d69db0b

Browse files
committed
feat(compiler-core/v-slot): only force dynamic slots when referencing scope vars
This feature is only applied with prefixIdentifiers: true.
1 parent 5e97643 commit d69db0b

File tree

4 files changed

+123
-12
lines changed

4 files changed

+123
-12
lines changed

packages/compiler-core/__tests__/transforms/vModel.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ function parseWithVModel(template: string, options: CompilerOptions = {}) {
2525
nodeTransforms: [
2626
transformFor,
2727
transformExpression,
28-
trackSlotScopes,
29-
transformElement
28+
transformElement,
29+
trackSlotScopes
3030
],
3131
directiveTransforms: {
3232
...options.directiveTransforms,

packages/compiler-core/__tests__/transforms/vSlot.spec.ts

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
NodeTypes,
88
ErrorCodes,
99
ForNode,
10-
CallExpression
10+
CallExpression,
11+
ComponentNode
1112
} from '../../src'
1213
import { transformElement } from '../../src/transforms/transformElement'
1314
import { transformOn } from '../../src/transforms/vOn'
@@ -32,8 +33,8 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
3233
...(options.prefixIdentifiers
3334
? [trackVForSlotScopes, transformExpression]
3435
: []),
35-
trackSlotScopes,
36-
transformElement
36+
transformElement,
37+
trackSlotScopes
3738
],
3839
directiveTransforms: {
3940
on: transformOn,
@@ -347,7 +348,60 @@ describe('compiler: transform component slots', () => {
347348
const div = ((root.children[0] as ForNode).children[0] as ElementNode)
348349
.codegenNode as any
349350
const comp = div.arguments[2][0]
350-
expect(comp.codegenNode.arguments[3]).toMatch(PatchFlags.DYNAMIC_SLOTS + '')
351+
expect(comp.codegenNode.arguments[3]).toBe(
352+
genFlagText(PatchFlags.DYNAMIC_SLOTS)
353+
)
354+
})
355+
356+
test('should only force dynamic slots when actually using scope vars w/ prefixIdentifiers: true', () => {
357+
function assertDynamicSlots(template: string, shouldForce: boolean) {
358+
const { root } = parseWithSlots(template, { prefixIdentifiers: true })
359+
let flag: any
360+
if (root.children[0].type === NodeTypes.FOR) {
361+
const div = (root.children[0].children[0] as ElementNode)
362+
.codegenNode as any
363+
const comp = div.arguments[2][0]
364+
flag = comp.codegenNode.arguments[3]
365+
} else {
366+
const innerComp = (root.children[0] as ComponentNode)
367+
.children[0] as ComponentNode
368+
flag = innerComp.codegenNode!.arguments[3]
369+
}
370+
if (shouldForce) {
371+
expect(flag).toBe(genFlagText(PatchFlags.DYNAMIC_SLOTS))
372+
} else {
373+
expect(flag).toBeUndefined()
374+
}
375+
}
376+
377+
assertDynamicSlots(
378+
`<div v-for="i in list">
379+
<Comp v-slot="bar">foo</Comp>
380+
</div>`,
381+
false
382+
)
383+
384+
assertDynamicSlots(
385+
`<div v-for="i in list">
386+
<Comp v-slot="bar">{{ i }}</Comp>
387+
</div>`,
388+
true
389+
)
390+
391+
// reference the component's own slot variable should not force dynamic slots
392+
assertDynamicSlots(
393+
`<Comp v-slot="foo">
394+
<Comp v-slot="bar">{{ bar }}</Comp>
395+
</Comp>`,
396+
false
397+
)
398+
399+
assertDynamicSlots(
400+
`<Comp v-slot="foo">
401+
<Comp v-slot="bar">{{ foo }}</Comp>
402+
</Comp>`,
403+
true
404+
)
351405
})
352406

353407
test('named slot with v-if', () => {

packages/compiler-core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ export function baseCompile(
5353
transformExpression
5454
]
5555
: []),
56-
trackSlotScopes,
5756
transformSlotOutlet,
5857
transformElement,
58+
trackSlotScopes,
5959
optimizeText,
6060
...(options.nodeTransforms || []) // user transforms
6161
],

packages/compiler-core/src/transforms/vSlot.ts

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,21 @@ import {
1919
FunctionExpression,
2020
CallExpression,
2121
createCallExpression,
22-
createArrayExpression
22+
createArrayExpression,
23+
IfBranchNode
2324
} from '../ast'
2425
import { TransformContext, NodeTransform } from '../transform'
2526
import { createCompilerError, ErrorCodes } from '../errors'
26-
import { findDir, isTemplateNode, assert, isVSlot } from '../utils'
27+
import {
28+
findDir,
29+
isTemplateNode,
30+
assert,
31+
isVSlot,
32+
isSimpleIdentifier
33+
} from '../utils'
2734
import { CREATE_SLOTS, RENDER_LIST } from '../runtimeHelpers'
2835
import { parseForExpression, createForLoopParams } from './vFor'
36+
import { isObject } from '@vue/shared'
2937

3038
const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
3139
p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic
@@ -108,9 +116,12 @@ export function buildSlots(
108116

109117
// If the slot is inside a v-for or another v-slot, force it to be dynamic
110118
// since it likely uses a scope variable.
111-
// TODO: This can be further optimized to only make it dynamic when the slot
112-
// actually uses the scope variables.
113-
let hasDynamicSlots = context.scopes.vSlot > 1 || context.scopes.vFor > 0
119+
let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0
120+
// with `prefixIdentifiers: true`, this can be further optimized to make
121+
// it dynamic only when the slot actually uses the scope variables.
122+
if (!__BROWSER__ && context.prefixIdentifiers) {
123+
hasDynamicSlots = hasScopeRef(node, context.identifiers)
124+
}
114125

115126
// 1. Check for default slot with slotProps on component itself.
116127
// <Comp v-slot="{ prop }"/>
@@ -326,3 +337,49 @@ function buildDynamicSlot(
326337
createObjectProperty(`fn`, fn)
327338
])
328339
}
340+
341+
function hasScopeRef(
342+
node: TemplateChildNode | IfBranchNode | SimpleExpressionNode | undefined,
343+
ids: TransformContext['identifiers']
344+
): boolean {
345+
if (!node) {
346+
return false
347+
}
348+
switch (node.type) {
349+
case NodeTypes.ELEMENT:
350+
for (let i = 0; i < node.props.length; i++) {
351+
const p = node.props[i]
352+
if (
353+
p.type === NodeTypes.DIRECTIVE &&
354+
(hasScopeRef(p.arg, ids) || hasScopeRef(p.exp, ids))
355+
) {
356+
return true
357+
}
358+
}
359+
return node.children.some(c => hasScopeRef(c, ids))
360+
case NodeTypes.FOR:
361+
if (hasScopeRef(node.source, ids)) {
362+
return true
363+
}
364+
return node.children.some(c => hasScopeRef(c, ids))
365+
case NodeTypes.IF:
366+
return node.branches.some(b => hasScopeRef(b, ids))
367+
case NodeTypes.IF_BRANCH:
368+
if (hasScopeRef(node.condition, ids)) {
369+
return true
370+
}
371+
return node.children.some(c => hasScopeRef(c, ids))
372+
case NodeTypes.SIMPLE_EXPRESSION:
373+
return (
374+
!node.isStatic &&
375+
isSimpleIdentifier(node.content) &&
376+
!!ids[node.content]
377+
)
378+
case NodeTypes.COMPOUND_EXPRESSION:
379+
return node.children.some(c => isObject(c) && hasScopeRef(c, ids))
380+
case NodeTypes.INTERPOLATION:
381+
return hasScopeRef(node.content, ids)
382+
default:
383+
return false
384+
}
385+
}

0 commit comments

Comments
 (0)