Skip to content

Commit c2fc7e3

Browse files
committed
feat(compiler): force dynamicSlots flag when inside v-for or v-slot
1 parent 4dea23f commit c2fc7e3

File tree

6 files changed

+73
-16
lines changed

6 files changed

+73
-16
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ return function render() {
159159
createVNode(_component_Inner, null, {
160160
default: ({ bar }) => [toString(foo), toString(bar), toString(_ctx.baz)],
161161
_compiled: true
162-
}),
162+
}, 256 /* DYNAMIC_SLOTS */),
163163
toString(foo),
164164
toString(_ctx.bar),
165165
toString(_ctx.baz)

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

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
generate,
66
ElementNode,
77
NodeTypes,
8-
ErrorCodes
8+
ErrorCodes,
9+
ForNode
910
} from '../../src'
1011
import { transformElement } from '../../src/transforms/transformElement'
1112
import { transformOn } from '../../src/transforms/vOn'
@@ -17,15 +18,20 @@ import {
1718
} from '../../src/transforms/vSlot'
1819
import { CREATE_SLOTS, RENDER_LIST } from '../../src/runtimeConstants'
1920
import { createObjectMatcher } from '../testUtils'
20-
import { PatchFlags } from '@vue/shared'
21+
import { PatchFlags, PatchFlagNames } from '@vue/shared'
22+
import { transformFor } from '../../src/transforms/vFor'
23+
import { transformIf } from '../../src/transforms/vIf'
2124

2225
function parseWithSlots(template: string, options: CompilerOptions = {}) {
2326
const ast = parse(template)
2427
transform(ast, {
2528
nodeTransforms: [
29+
transformIf,
30+
transformFor,
2631
...(options.prefixIdentifiers
27-
? [trackVForSlotScopes, transformExpression, trackSlotScopes]
32+
? [trackVForSlotScopes, transformExpression]
2833
: []),
34+
trackSlotScopes,
2935
transformElement
3036
],
3137
directiveTransforms: {
@@ -36,7 +42,10 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
3642
})
3743
return {
3844
root: ast,
39-
slots: (ast.children[0] as ElementNode).codegenNode!.arguments[2]
45+
slots:
46+
ast.children[0].type === NodeTypes.ELEMENT
47+
? ast.children[0].codegenNode!.arguments[2]
48+
: null
4049
}
4150
}
4251

@@ -295,7 +304,12 @@ describe('compiler: transform component slots', () => {
295304
}
296305
]
297306
}
298-
})
307+
}),
308+
// nested slot should be forced dynamic, since scope variables
309+
// are not tracked as dependencies of the slot.
310+
`${PatchFlags.DYNAMIC_SLOTS} /* ${
311+
PatchFlagNames[PatchFlags.DYNAMIC_SLOTS]
312+
} */`
299313
]
300314
}
301315
},
@@ -325,6 +339,18 @@ describe('compiler: transform component slots', () => {
325339
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
326340
})
327341

342+
test('should force dynamic when inside v-for', () => {
343+
const { root } = parseWithSlots(
344+
`<div v-for="i in list">
345+
<Comp v-slot="bar">foo</Comp>
346+
</div>`
347+
)
348+
const div = ((root.children[0] as ForNode).children[0] as ElementNode)
349+
.codegenNode as any
350+
const comp = div.arguments[2][0]
351+
expect(comp.codegenNode.arguments[3]).toMatch(PatchFlags.DYNAMIC_SLOTS + '')
352+
})
353+
328354
test('named slot with v-if', () => {
329355
const { root, slots } = parseWithSlots(
330356
`<Comp>

packages/compiler-core/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ export function baseCompile(
4949
? [
5050
// order is important
5151
trackVForSlotScopes,
52-
transformExpression,
53-
trackSlotScopes
52+
transformExpression
5453
]
5554
: []),
55+
trackSlotScopes,
5656
optimizeText,
5757
transformStyle,
5858
transformSlotOutlet,

packages/compiler-core/src/transform.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ export interface TransformContext extends Required<TransformOptions> {
5959
statements: Set<string>
6060
hoists: JSChildNode[]
6161
identifiers: { [name: string]: number | undefined }
62+
scopes: {
63+
vFor: number
64+
vSlot: number
65+
vPre: number
66+
vOnce: number
67+
}
6268
parent: ParentNode | null
6369
childIndex: number
6470
currentNode: RootNode | TemplateChildNode | null
@@ -86,6 +92,12 @@ function createTransformContext(
8692
statements: new Set(),
8793
hoists: [],
8894
identifiers: {},
95+
scopes: {
96+
vFor: 0,
97+
vSlot: 0,
98+
vPre: 0,
99+
vOnce: 0
100+
},
89101
prefixIdentifiers,
90102
nodeTransforms,
91103
directiveTransforms,

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const transformFor = createStructuralDirectiveTransform(
4646
)
4747

4848
if (parseResult) {
49-
const { helper, addIdentifiers, removeIdentifiers } = context
49+
const { helper, addIdentifiers, removeIdentifiers, scopes } = context
5050
const { source, value, key, index } = parseResult
5151

5252
// create the loop render function expression now, and add the
@@ -79,6 +79,8 @@ export const transformFor = createStructuralDirectiveTransform(
7979
codegenNode
8080
})
8181

82+
// bookkeeping
83+
scopes.vFor++
8284
if (!__BROWSER__ && context.prefixIdentifiers) {
8385
// scope management
8486
// inject identifiers to context
@@ -88,6 +90,7 @@ export const transformFor = createStructuralDirectiveTransform(
8890
}
8991

9092
return () => {
93+
scopes.vFor--
9194
if (!__BROWSER__ && context.prefixIdentifiers) {
9295
value && removeIdentifiers(value)
9396
key && removeIdentifiers(key)

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

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,33 @@ const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
3232

3333
const defaultFallback = createSimpleExpression(`undefined`, false)
3434

35-
// A NodeTransform that tracks scope identifiers for scoped slots so that they
36-
// don't get prefixed by transformExpression. This transform is only applied
37-
// in non-browser builds with { prefixIdentifiers: true }
35+
// A NodeTransform that:
36+
// 1. Tracks scope identifiers for scoped slots so that they don't get prefixed
37+
// by transformExpression. This is only applied in non-browser builds with
38+
// { prefixIdentifiers: true }.
39+
// 2. Track v-slot depths so that we know a slot is inside another slot.
40+
// Note the exit callback is executed before buildSlots() on the same node,
41+
// so only nested slots see positive numbers.
3842
export const trackSlotScopes: NodeTransform = (node, context) => {
3943
if (
4044
node.type === NodeTypes.ELEMENT &&
4145
(node.tagType === ElementTypes.COMPONENT ||
4246
node.tagType === ElementTypes.TEMPLATE)
4347
) {
48+
// We are only checking non-empty v-slot here
49+
// since we only care about slots that introduce scope variables.
4450
const vSlot = findDir(node, 'slot')
4551
if (vSlot) {
46-
const { addIdentifiers, removeIdentifiers } = context
4752
const slotProps = vSlot.exp
48-
slotProps && addIdentifiers(slotProps)
53+
if (!__BROWSER__ && context.prefixIdentifiers) {
54+
slotProps && context.addIdentifiers(slotProps)
55+
}
56+
context.scopes.vSlot++
4957
return () => {
50-
slotProps && removeIdentifiers(slotProps)
58+
if (!__BROWSER__ && context.prefixIdentifiers) {
59+
slotProps && context.removeIdentifiers(slotProps)
60+
}
61+
context.scopes.vSlot--
5162
}
5263
}
5364
}
@@ -94,7 +105,12 @@ export function buildSlots(
94105
const { children, loc } = node
95106
const slotsProperties: Property[] = []
96107
const dynamicSlots: (ConditionalExpression | CallExpression)[] = []
97-
let hasDynamicSlots = false
108+
109+
// If the slot is inside a v-for or another v-slot, force it to be dynamic
110+
// 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 > 0 || context.scopes.vFor > 0
98114

99115
// 1. Check for default slot with slotProps on component itself.
100116
// <Comp v-slot="{ prop }"/>

0 commit comments

Comments
 (0)