Skip to content
This repository was archived by the owner on Jul 19, 2025. It is now read-only.

Commit c391500

Browse files
committed
feat: props for slot outlet
1 parent 1075e8e commit c391500

File tree

6 files changed

+201
-8
lines changed

6 files changed

+201
-8
lines changed

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ export function render(_ctx) {
2222
}"
2323
`;
2424
25+
exports[`compiler: transform <slot> outlets > default slot outlet with props 1`] = `
26+
"import { createSlot as _createSlot } from 'vue/vapor';
27+
28+
export function render(_ctx) {
29+
const n0 = _createSlot("default", [{
30+
foo: () => ("bar"),
31+
baz: () => (_ctx.qux),
32+
fooBar: () => (_ctx.foo-_ctx.bar)
33+
}])
34+
return n0
35+
}"
36+
`;
37+
2538
exports[`compiler: transform <slot> outlets > dynamically named slot outlet 1`] = `
2639
"import { createSlot as _createSlot } from 'vue/vapor';
2740
@@ -61,3 +74,42 @@ export function render(_ctx) {
6174
return n0
6275
}"
6376
`;
77+
78+
exports[`compiler: transform <slot> outlets > statically named slot outlet with props 1`] = `
79+
"import { createSlot as _createSlot } from 'vue/vapor';
80+
81+
export function render(_ctx) {
82+
const n0 = _createSlot("foo", [{
83+
foo: () => ("bar"),
84+
baz: () => (_ctx.qux)
85+
}])
86+
return n0
87+
}"
88+
`;
89+
90+
exports[`compiler: transform <slot> outlets > statically named slot outlet with v-bind="obj" 1`] = `
91+
"import { createSlot as _createSlot } from 'vue/vapor';
92+
93+
export function render(_ctx) {
94+
const n0 = _createSlot("foo", [{
95+
foo: () => ("bar")
96+
}, () => (_ctx.obj), {
97+
baz: () => (_ctx.qux)
98+
}])
99+
return n0
100+
}"
101+
`;
102+
103+
exports[`compiler: transform <slot> outlets > statically named slot outlet with v-on 1`] = `
104+
"import { toHandlers as _toHandlers } from 'vue';
105+
import { createSlot as _createSlot } from 'vue/vapor';
106+
107+
export function render(_ctx) {
108+
const n0 = _createSlot("default", [{
109+
onClick: () => _ctx.foo
110+
}, () => (_toHandlers(_ctx.bar)), {
111+
baz: () => (_ctx.qux)
112+
}])
113+
return n0
114+
}"
115+
`;

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

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,80 @@ describe('compiler: transform <slot> outlets', () => {
9292
])
9393
})
9494

95+
test('default slot outlet with props', () => {
96+
const { ir, code } = compileWithSlotsOutlet(
97+
`<slot foo="bar" :baz="qux" :foo-bar="foo-bar" />`,
98+
)
99+
expect(code).toMatchSnapshot()
100+
expect(ir.block.operation).toMatchObject([
101+
{
102+
type: IRNodeTypes.SLOT_OUTLET_NODE,
103+
name: { content: 'default' },
104+
props: [
105+
[
106+
{ key: { content: 'foo' }, values: [{ content: 'bar' }] },
107+
{ key: { content: 'baz' }, values: [{ content: 'qux' }] },
108+
{ key: { content: 'fooBar' }, values: [{ content: 'foo-bar' }] },
109+
],
110+
],
111+
},
112+
])
113+
})
114+
115+
test('statically named slot outlet with props', () => {
116+
const { ir, code } = compileWithSlotsOutlet(
117+
`<slot name="foo" foo="bar" :baz="qux" />`,
118+
)
119+
expect(code).toMatchSnapshot()
120+
expect(ir.block.operation).toMatchObject([
121+
{
122+
type: IRNodeTypes.SLOT_OUTLET_NODE,
123+
name: { content: 'foo' },
124+
props: [
125+
[
126+
{ key: { content: 'foo' }, values: [{ content: 'bar' }] },
127+
{ key: { content: 'baz' }, values: [{ content: 'qux' }] },
128+
],
129+
],
130+
},
131+
])
132+
})
133+
134+
test('statically named slot outlet with v-bind="obj"', () => {
135+
const { ir, code } = compileWithSlotsOutlet(
136+
`<slot name="foo" foo="bar" v-bind="obj" :baz="qux" />`,
137+
)
138+
expect(code).toMatchSnapshot()
139+
expect(ir.block.operation).toMatchObject([
140+
{
141+
type: IRNodeTypes.SLOT_OUTLET_NODE,
142+
name: { content: 'foo' },
143+
props: [
144+
[{ key: { content: 'foo' }, values: [{ content: 'bar' }] }],
145+
{ value: { content: 'obj', isStatic: false } },
146+
[{ key: { content: 'baz' }, values: [{ content: 'qux' }] }],
147+
],
148+
},
149+
])
150+
})
151+
152+
test('statically named slot outlet with v-on', () => {
153+
const { ir, code } = compileWithSlotsOutlet(
154+
`<slot @click="foo" v-on="bar" :baz="qux" />`,
155+
)
156+
expect(code).toMatchSnapshot()
157+
expect(ir.block.operation).toMatchObject([
158+
{
159+
type: IRNodeTypes.SLOT_OUTLET_NODE,
160+
props: [
161+
[{ key: { content: 'click' }, values: [{ content: 'foo' }] }],
162+
{ value: { content: 'bar' }, handler: true },
163+
[{ key: { content: 'baz' }, values: [{ content: 'qux' }] }],
164+
],
165+
},
166+
])
167+
})
168+
95169
test('default slot outlet with fallback', () => {
96170
const { ir, code } = compileWithSlotsOutlet(`<slot><div/></slot>`)
97171
expect(code).toMatchSnapshot()

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

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
1+
import { isArray } from '@vue/shared'
12
import type { CodegenContext } from '../generate'
2-
import type { SlotOutletIRNode } from '../ir'
3+
import type { IRProp, SlotOutletIRNode } from '../ir'
34
import { genBlock } from './block'
45
import { genExpression } from './expression'
5-
import { type CodeFragment, NEWLINE, buildCodeFragment, genCall } from './utils'
6+
import {
7+
type CodeFragment,
8+
INDENT_END,
9+
INDENT_START,
10+
NEWLINE,
11+
buildCodeFragment,
12+
genCall,
13+
genMulti,
14+
} from './utils'
15+
import { genPropKey } from './prop'
16+
import { genEventHandler } from './event'
617

718
export function genSlotOutlet(oper: SlotOutletIRNode, context: CodegenContext) {
8-
const { vaporHelper } = context
19+
const { helper, vaporHelper } = context
920
const { id, name, fallback } = oper
1021
const [frag, push] = buildCodeFragment()
1122

@@ -19,7 +30,52 @@ export function genSlotOutlet(oper: SlotOutletIRNode, context: CodegenContext) {
1930
}
2031

2132
push(NEWLINE, `const n${id} = `)
22-
push(...genCall(vaporHelper('createSlot'), nameExpr, false, fallbackArg))
33+
push(
34+
...genCall(
35+
vaporHelper('createSlot'),
36+
nameExpr,
37+
genRawProps() || false,
38+
fallbackArg,
39+
),
40+
)
2341

2442
return frag
43+
44+
// TODO share this with genCreateComponent
45+
function genRawProps() {
46+
const props = oper.props
47+
.map(props => {
48+
if (isArray(props)) {
49+
if (!props.length) return
50+
return genStaticProps(props)
51+
} else {
52+
let expr = genExpression(props.value, context)
53+
if (props.handler) expr = genCall(helper('toHandlers'), expr)
54+
return ['() => (', ...expr, ')']
55+
}
56+
})
57+
.filter(Boolean)
58+
if (props.length) {
59+
return genMulti(['[', ']', ', '], ...props)
60+
}
61+
}
62+
63+
function genStaticProps(props: IRProp[]) {
64+
return genMulti(
65+
[
66+
['{', INDENT_START, NEWLINE],
67+
[INDENT_END, NEWLINE, '}'],
68+
[', ', NEWLINE],
69+
],
70+
...props.map(prop => {
71+
return [
72+
...genPropKey(prop, context),
73+
': ',
74+
...(prop.handler
75+
? genEventHandler(context, prop.values[0])
76+
: ['() => (', ...genExpression(prop.values[0], context), ')']),
77+
]
78+
}),
79+
)
80+
}
2581
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export type PropsResult =
183183
| [dynamic: true, props: IRProps[], expressions: SimpleExpressionNode[]]
184184
| [dynamic: false, props: IRProp[]]
185185

186-
function buildProps(
186+
export function buildProps(
187187
node: ElementNode,
188188
context: TransformContext<ElementNode>,
189189
isComponent: boolean,

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ import {
1313
DynamicFlag,
1414
type IRDynamicInfo,
1515
IRNodeTypes,
16+
type IRProps,
1617
type VaporDirectiveNode,
1718
} from '../ir'
1819
import { camelize, extend } from '@vue/shared'
1920
import { genDefaultDynamic } from './utils'
21+
import { buildProps } from './transformElement'
2022

2123
export const transformSlotOutlet: NodeTransform = (node, context) => {
2224
if (node.type !== NodeTypes.ELEMENT || node.tag !== 'slot') {
@@ -37,7 +39,6 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
3739
if (p.value) {
3840
if (p.name === 'name') {
3941
name = createSimpleExpression(p.value.content, true, p.loc)
40-
break
4142
} else {
4243
p.name = camelize(p.name)
4344
nonNameProps.push(p)
@@ -65,14 +66,23 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
6566
}
6667
}
6768
name ||= createSimpleExpression('default', true)
69+
let irProps: IRProps[] = []
70+
if (nonNameProps.length > 0) {
71+
const [isDynamic, props] = buildProps(
72+
extend({}, node, { props: nonNameProps }),
73+
context as TransformContext<ElementNode>,
74+
true,
75+
)
76+
irProps = isDynamic ? props : [props]
77+
}
6878

6979
return () => {
7080
exitBlock && exitBlock()
7181
context.registerOperation({
7282
type: IRNodeTypes.SLOT_OUTLET_NODE,
7383
id,
7484
name,
75-
props: [],
85+
props: irProps,
7686
fallback,
7787
})
7888
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const delegatedEvents = /*#__PURE__*/ makeMap(
2020
export const transformVOn: DirectiveTransform = (dir, node, context) => {
2121
let { arg, exp, loc, modifiers } = dir
2222
const isComponent = node.tagType === ElementTypes.COMPONENT
23+
const isSlotOutlet = node.tag === 'slot'
2324

2425
if (!exp && !modifiers.length) {
2526
context.options.onError(
@@ -60,7 +61,7 @@ export const transformVOn: DirectiveTransform = (dir, node, context) => {
6061
}
6162
}
6263

63-
if (isComponent) {
64+
if (isComponent || isSlotOutlet) {
6465
const handler = exp || EMPTY_EXPRESSION
6566
return {
6667
key: arg,

0 commit comments

Comments
 (0)