Skip to content

Commit 2182e88

Browse files
authored
feat(vapor): forwarded slots (#13408)
1 parent 35475c0 commit 2182e88

File tree

16 files changed

+1732
-54
lines changed

16 files changed

+1732
-54
lines changed

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

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,97 @@ export function render(_ctx) {
103103
}"
104104
`;
105105

106+
exports[`compiler: transform slot > forwarded slots > <slot w/ nested component> 1`] = `
107+
"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
108+
109+
export function render(_ctx) {
110+
const _createForwardedSlot = _forwardedSlotCreator()
111+
const _component_Comp = _resolveComponent("Comp")
112+
const n2 = _createComponentWithFallback(_component_Comp, null, {
113+
"default": () => {
114+
const n1 = _createComponentWithFallback(_component_Comp, null, {
115+
"default": () => {
116+
const n0 = _createForwardedSlot("default", null)
117+
return n0
118+
}
119+
})
120+
return n1
121+
}
122+
}, true)
123+
return n2
124+
}"
125+
`;
126+
127+
exports[`compiler: transform slot > forwarded slots > <slot> tag only 1`] = `
128+
"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
129+
130+
export function render(_ctx) {
131+
const _createForwardedSlot = _forwardedSlotCreator()
132+
const _component_Comp = _resolveComponent("Comp")
133+
const n1 = _createComponentWithFallback(_component_Comp, null, {
134+
"default": () => {
135+
const n0 = _createForwardedSlot("default", null)
136+
return n0
137+
}
138+
}, true)
139+
return n1
140+
}"
141+
`;
142+
143+
exports[`compiler: transform slot > forwarded slots > <slot> tag w/ template 1`] = `
144+
"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
145+
146+
export function render(_ctx) {
147+
const _createForwardedSlot = _forwardedSlotCreator()
148+
const _component_Comp = _resolveComponent("Comp")
149+
const n2 = _createComponentWithFallback(_component_Comp, null, {
150+
"default": () => {
151+
const n0 = _createForwardedSlot("default", null)
152+
return n0
153+
}
154+
}, true)
155+
return n2
156+
}"
157+
`;
158+
159+
exports[`compiler: transform slot > forwarded slots > <slot> tag w/ v-for 1`] = `
160+
"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createFor as _createFor, createComponentWithFallback as _createComponentWithFallback } from 'vue';
161+
162+
export function render(_ctx) {
163+
const _createForwardedSlot = _forwardedSlotCreator()
164+
const _component_Comp = _resolveComponent("Comp")
165+
const n3 = _createComponentWithFallback(_component_Comp, null, {
166+
"default": () => {
167+
const n0 = _createFor(() => (_ctx.b), (_for_item0) => {
168+
const n2 = _createForwardedSlot("default", null)
169+
return n2
170+
})
171+
return n0
172+
}
173+
}, true)
174+
return n3
175+
}"
176+
`;
177+
178+
exports[`compiler: transform slot > forwarded slots > <slot> tag w/ v-if 1`] = `
179+
"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createIf as _createIf, createComponentWithFallback as _createComponentWithFallback } from 'vue';
180+
181+
export function render(_ctx) {
182+
const _createForwardedSlot = _forwardedSlotCreator()
183+
const _component_Comp = _resolveComponent("Comp")
184+
const n3 = _createComponentWithFallback(_component_Comp, null, {
185+
"default": () => {
186+
const n0 = _createIf(() => (_ctx.ok), () => {
187+
const n2 = _createForwardedSlot("default", null)
188+
return n2
189+
})
190+
return n0
191+
}
192+
}, true)
193+
return n3
194+
}"
195+
`;
196+
106197
exports[`compiler: transform slot > implicit default slot 1`] = `
107198
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
108199
const t0 = _template("<div></div>")

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,35 @@ describe('compiler: transform slot', () => {
420420
})
421421
})
422422

423+
describe('forwarded slots', () => {
424+
test('<slot> tag only', () => {
425+
const { code } = compileWithSlots(`<Comp><slot/></Comp>`)
426+
expect(code).toMatchSnapshot()
427+
})
428+
429+
test('<slot> tag w/ v-if', () => {
430+
const { code } = compileWithSlots(`<Comp><slot v-if="ok"/></Comp>`)
431+
expect(code).toMatchSnapshot()
432+
})
433+
434+
test('<slot> tag w/ v-for', () => {
435+
const { code } = compileWithSlots(`<Comp><slot v-for="a in b"/></Comp>`)
436+
expect(code).toMatchSnapshot()
437+
})
438+
439+
test('<slot> tag w/ template', () => {
440+
const { code } = compileWithSlots(
441+
`<Comp><template #default><slot/></template></Comp>`,
442+
)
443+
expect(code).toMatchSnapshot()
444+
})
445+
446+
test('<slot w/ nested component>', () => {
447+
const { code } = compileWithSlots(`<Comp><Comp><slot/></Comp></Comp>`)
448+
expect(code).toMatchSnapshot()
449+
})
450+
})
451+
423452
describe('errors', () => {
424453
test('error on extraneous children w/ named default slot', () => {
425454
const onError = vi.fn()

packages/compiler-vapor/src/generate.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
genCall,
1919
} from './generators/utils'
2020
import { setTemplateRefIdent } from './generators/templateRef'
21+
import { createForwardedSlotIdent } from './generators/slotOutlet'
2122

2223
export type CodegenOptions = Omit<BaseCodegenOptions, 'optimizeImports'>
2324

@@ -129,6 +130,12 @@ export function generate(
129130
`const ${setTemplateRefIdent} = ${context.helper('createTemplateRefSetter')}()`,
130131
)
131132
}
133+
if (ir.hasForwardedSlot) {
134+
push(
135+
NEWLINE,
136+
`const ${createForwardedSlotIdent} = ${context.helper('forwardedSlotCreator')}()`,
137+
)
138+
}
132139
push(...genBlockContent(ir.block, context, true))
133140
push(INDENT_END, NEWLINE)
134141

packages/compiler-vapor/src/generators/slotOutlet.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import { genExpression } from './expression'
55
import { type CodeFragment, NEWLINE, buildCodeFragment, genCall } from './utils'
66
import { genRawProps } from './component'
77

8+
export const createForwardedSlotIdent = `_createForwardedSlot`
9+
810
export function genSlotOutlet(
911
oper: SlotOutletIRNode,
1012
context: CodegenContext,
1113
): CodeFragment[] {
1214
const { helper } = context
13-
const { id, name, fallback } = oper
15+
const { id, name, fallback, forwarded } = oper
1416
const [frag, push] = buildCodeFragment()
1517

1618
const nameExpr = name.isStatic
@@ -26,7 +28,7 @@ export function genSlotOutlet(
2628
NEWLINE,
2729
`const n${id} = `,
2830
...genCall(
29-
helper('createSlot'),
31+
forwarded ? createForwardedSlotIdent : helper('createSlot'),
3032
nameExpr,
3133
genRawProps(oper.props, context) || 'null',
3234
fallbackArg,

packages/compiler-vapor/src/ir/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export interface RootIRNode {
6767
directive: Set<string>
6868
block: BlockIRNode
6969
hasTemplateRef: boolean
70+
hasForwardedSlot: boolean
7071
}
7172

7273
export interface IfIRNode extends BaseIRNode {
@@ -213,6 +214,7 @@ export interface SlotOutletIRNode extends BaseIRNode {
213214
name: SimpleExpressionNode
214215
props: IRProps[]
215216
fallback?: BlockIRNode
217+
forwarded?: boolean
216218
parent?: number
217219
anchor?: number
218220
}

packages/compiler-vapor/src/transform.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export class TransformContext<T extends AllNode = AllNode> {
7878

7979
inVOnce: boolean = false
8080
inVFor: number = 0
81+
inSlot: boolean = false
8182

8283
comment: CommentNode[] = []
8384
component: Set<string> = this.ir.component
@@ -219,6 +220,7 @@ export function transform(
219220
directive: new Set(),
220221
block: newBlock(node),
221222
hasTemplateRef: false,
223+
hasForwardedSlot: false,
222224
}
223225

224226
const context = new TransformContext(ir, node, options)

packages/compiler-vapor/src/transforms/transformSlotOutlet.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,15 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
9999
}
100100

101101
return () => {
102+
if (context.inSlot) context.ir.hasForwardedSlot = true
102103
exitBlock && exitBlock()
103104
context.dynamic.operation = {
104105
type: IRNodeTypes.SLOT_OUTLET_NODE,
105106
id,
106107
name: slotName,
107108
props: irProps,
108109
fallback,
110+
forwarded: context.inSlot,
109111
}
110112
}
111113
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,14 @@ function createSlotBlock(
269269
block.dynamic.needsKey = true
270270
}
271271
const exitBlock = context.enterBlock(block)
272-
return [block, exitBlock]
272+
context.inSlot = true
273+
return [
274+
block,
275+
() => {
276+
context.inSlot = false
277+
exitBlock()
278+
},
279+
]
273280
}
274281

275282
function isNonWhitespaceContent(node: TemplateChildNode): boolean {

packages/runtime-core/src/helpers/renderSlot.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ export function renderSlot(
8181
}
8282
openBlock()
8383
const validSlotContent = slot && ensureValidVNode(slot(props))
84+
85+
// handle forwarded vapor slot fallback
86+
ensureVaporSlotFallback(validSlotContent, fallback)
87+
8488
const slotKey =
8589
props.key ||
8690
// slot content array of a dynamic conditional slot may have a branch
@@ -124,3 +128,20 @@ export function ensureValidVNode(
124128
? vnodes
125129
: null
126130
}
131+
132+
export function ensureVaporSlotFallback(
133+
vnodes: VNodeArrayChildren | null | undefined,
134+
fallback?: () => VNodeArrayChildren,
135+
): void {
136+
let vaporSlot: any
137+
if (
138+
vnodes &&
139+
vnodes.length === 1 &&
140+
isVNode(vnodes[0]) &&
141+
(vaporSlot = vnodes[0].vs)
142+
) {
143+
if (!vaporSlot.fallback && fallback) {
144+
vaporSlot.fallback = fallback
145+
}
146+
}
147+
}

packages/runtime-core/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,10 @@ export { startMeasure, endMeasure } from './profiling'
563563
* @internal
564564
*/
565565
export { initFeatureFlags } from './featureFlags'
566+
/**
567+
* @internal
568+
*/
569+
export { ensureVaporSlotFallback } from './helpers/renderSlot'
566570
/**
567571
* @internal
568572
*/

0 commit comments

Comments
 (0)