Skip to content

Commit 1169db8

Browse files
committed
Merge branch 'edison/feat/fowardedSlots' into edison/feat/setScopeId
2 parents 4aaa69a + dcf927f commit 1169db8

File tree

13 files changed

+290
-9
lines changed

13 files changed

+290
-9
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
@@ -409,6 +409,35 @@ describe('compiler: transform slot', () => {
409409
})
410410
})
411411

412+
describe('forwarded slots', () => {
413+
test('<slot> tag only', () => {
414+
const { code } = compileWithSlots(`<Comp><slot/></Comp>`)
415+
expect(code).toMatchSnapshot()
416+
})
417+
418+
test('<slot> tag w/ v-if', () => {
419+
const { code } = compileWithSlots(`<Comp><slot v-if="ok"/></Comp>`)
420+
expect(code).toMatchSnapshot()
421+
})
422+
423+
test('<slot> tag w/ v-for', () => {
424+
const { code } = compileWithSlots(`<Comp><slot v-for="a in b"/></Comp>`)
425+
expect(code).toMatchSnapshot()
426+
})
427+
428+
test('<slot> tag w/ template', () => {
429+
const { code } = compileWithSlots(
430+
`<Comp><template #default><slot/></template></Comp>`,
431+
)
432+
expect(code).toMatchSnapshot()
433+
})
434+
435+
test('<slot w/ nested component>', () => {
436+
const { code } = compileWithSlots(`<Comp><Comp><slot/></Comp></Comp>`)
437+
expect(code).toMatchSnapshot()
438+
})
439+
})
440+
412441
describe('errors', () => {
413442
test('error on extraneous children w/ named default slot', () => {
414443
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
@@ -66,6 +66,7 @@ export interface RootIRNode {
6666
directive: Set<string>
6767
block: BlockIRNode
6868
hasTemplateRef: boolean
69+
hasForwardedSlot: boolean
6970
}
7071

7172
export interface IfIRNode extends BaseIRNode {
@@ -209,6 +210,7 @@ export interface SlotOutletIRNode extends BaseIRNode {
209210
name: SimpleExpressionNode
210211
props: IRProps[]
211212
fallback?: BlockIRNode
213+
forwarded?: boolean
212214
parent?: number
213215
anchor?: number
214216
}

packages/compiler-vapor/src/transform.ts

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

7777
inVOnce: boolean = false
7878
inVFor: number = 0
79+
inSlot: boolean = false
7980

8081
comment: CommentNode[] = []
8182
component: Set<string> = this.ir.component
@@ -230,6 +231,7 @@ export function transform(
230231
directive: new Set(),
231232
block: newBlock(node),
232233
hasTemplateRef: false,
234+
hasForwardedSlot: false,
233235
}
234236

235237
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
@@ -237,7 +237,14 @@ function createSlotBlock(
237237
const block: SlotBlockIRNode = newBlock(slotNode)
238238
block.props = dir && dir.exp
239239
const exitBlock = context.enterBlock(block)
240-
return [block, exitBlock]
240+
context.inSlot = true
241+
return [
242+
block,
243+
() => {
244+
context.inSlot = false
245+
exitBlock()
246+
},
247+
]
241248
}
242249

243250
function isNonWhitespaceContent(node: TemplateChildNode): boolean {

packages/runtime-vapor/__tests__/componentSlots.spec.ts

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
createSlot,
88
createVaporApp,
99
defineVaporComponent,
10+
forwardedSlotCreator,
1011
insert,
1112
prepend,
1213
renderEffect,
@@ -15,7 +16,7 @@ import {
1516
import { currentInstance, nextTick, ref } from '@vue/runtime-dom'
1617
import { makeRender } from './_utils'
1718
import type { DynamicSlot } from '../src/componentSlots'
18-
import { setElementText } from '../src/dom/prop'
19+
import { setElementText, setText } from '../src/dom/prop'
1920

2021
const define = makeRender<any>()
2122

@@ -503,4 +504,106 @@ describe('component: slots', () => {
503504
expect(host.innerHTML).toBe('<div><h1></h1><!--slot--></div>')
504505
})
505506
})
507+
508+
describe('forwarded slot', () => {
509+
test('should work', async () => {
510+
const Child = defineVaporComponent({
511+
setup() {
512+
return createSlot('foo', null)
513+
},
514+
})
515+
const Parent = defineVaporComponent({
516+
setup() {
517+
const createForwardedSlot = forwardedSlotCreator()
518+
const n2 = createComponent(
519+
Child,
520+
null,
521+
{
522+
foo: () => {
523+
return createForwardedSlot('foo', null)
524+
},
525+
},
526+
true,
527+
)
528+
return n2
529+
},
530+
})
531+
532+
const foo = ref('foo')
533+
const { host } = define({
534+
setup() {
535+
const n2 = createComponent(
536+
Parent,
537+
null,
538+
{
539+
foo: () => {
540+
const n0 = template(' ')() as any
541+
renderEffect(() => setText(n0, foo.value))
542+
return n0
543+
},
544+
},
545+
true,
546+
)
547+
return n2
548+
},
549+
}).render()
550+
551+
expect(host.innerHTML).toBe('foo<!--slot--><!--slot-->')
552+
553+
foo.value = 'bar'
554+
await nextTick()
555+
expect(host.innerHTML).toBe('bar<!--slot--><!--slot-->')
556+
})
557+
558+
test('mixed with non-forwarded slot', async () => {
559+
const Child = defineVaporComponent({
560+
setup() {
561+
return [createSlot('foo', null)]
562+
},
563+
})
564+
const Parent = defineVaporComponent({
565+
setup() {
566+
const createForwardedSlot = forwardedSlotCreator()
567+
const n2 = createComponent(Child, null, {
568+
foo: () => {
569+
const n0 = createForwardedSlot('foo', null)
570+
return n0
571+
},
572+
})
573+
const n3 = createSlot('default', null)
574+
return [n2, n3]
575+
},
576+
})
577+
578+
const foo = ref('foo')
579+
const { host } = define({
580+
setup() {
581+
const n2 = createComponent(
582+
Parent,
583+
null,
584+
{
585+
foo: () => {
586+
const n0 = template(' ')() as any
587+
renderEffect(() => setText(n0, foo.value))
588+
return n0
589+
},
590+
default: () => {
591+
const n3 = template(' ')() as any
592+
renderEffect(() => setText(n3, foo.value))
593+
return n3
594+
},
595+
},
596+
true,
597+
)
598+
return n2
599+
},
600+
}).render()
601+
602+
expect(host.innerHTML).toBe('foo<!--slot--><!--slot-->foo<!--slot-->')
603+
604+
foo.value = 'bar'
605+
await nextTick()
606+
expect(host.innerHTML).toBe('bar<!--slot--><!--slot-->bar<!--slot-->')
607+
})
608+
})
506609
})

packages/runtime-vapor/src/componentProps.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ export function hasAttrFromRawProps(rawProps: RawProps, key: string): boolean {
210210
if (dynamicSources) {
211211
let i = dynamicSources.length
212212
while (i--) {
213-
if (hasOwn(resolveSource(dynamicSources[i]), key)) {
213+
const source = resolveSource(dynamicSources[i])
214+
if (source && hasOwn(source, key)) {
214215
return true
215216
}
216217
}

0 commit comments

Comments
 (0)