Skip to content

Commit 4683a64

Browse files
committed
refactor(compiler-vapor): impl dynamic slot name + add error detection
1 parent 8ed97a3 commit 4683a64

File tree

7 files changed

+226
-61
lines changed

7 files changed

+226
-61
lines changed

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

+7-4
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ const t0 = _template("foo")
66
77
export function render(_ctx) {
88
const _component_Comp = _resolveComponent("Comp")
9-
const n2 = _createComponent(_component_Comp, null, { [_ctx.dynamicName]: () => {
10-
const n0 = t0()
11-
return n0
12-
} }, null, true)
9+
const n2 = _createComponent(_component_Comp, null, null, () => [{
10+
name: _ctx.name,
11+
fn: () => {
12+
const n0 = t0()
13+
return n0
14+
}
15+
}], true)
1316
return n2
1417
}"
1518
`;

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

+108-18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { createSimpleExpression } from '@vue/compiler-dom'
1+
import { ErrorCodes, NodeTypes } from '@vue/compiler-core'
22
import {
3-
type CreateComponentIRNode,
43
IRNodeTypes,
54
transformChildren,
65
transformElement,
@@ -36,10 +35,22 @@ describe('compiler: transform slot', () => {
3635
expect(code).toMatchSnapshot()
3736

3837
expect(ir.template).toEqual(['<div></div>'])
39-
expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
40-
const slots = (ir.block.operation[0] as CreateComponentIRNode).slots!
41-
expect(slots.length).toBe(1)
42-
expect(slots[0].name.content).toBe('default')
38+
expect(ir.block.operation).toMatchObject([
39+
{
40+
type: IRNodeTypes.CREATE_COMPONENT_NODE,
41+
id: 1,
42+
tag: 'Comp',
43+
props: [[]],
44+
slots: {
45+
default: {
46+
type: IRNodeTypes.BLOCK,
47+
dynamic: {
48+
children: [{ template: 0 }],
49+
},
50+
},
51+
},
52+
},
53+
])
4354
expect(ir.block.returns).toEqual([1])
4455
expect(ir.block.dynamic).toMatchObject({
4556
children: [{ id: 1 }],
@@ -55,11 +66,28 @@ describe('compiler: transform slot', () => {
5566
expect(code).toMatchSnapshot()
5667

5768
expect(ir.template).toEqual(['foo', 'bar', '<span></span>'])
58-
expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
59-
const slots = (ir.block.operation[0] as CreateComponentIRNode).slots!
60-
expect(slots.length).toBe(2)
61-
expect(slots[0].name.content).toBe('one')
62-
expect(slots[1].name.content).toBe('default')
69+
expect(ir.block.operation).toMatchObject([
70+
{
71+
type: IRNodeTypes.CREATE_COMPONENT_NODE,
72+
id: 4,
73+
tag: 'Comp',
74+
props: [[]],
75+
slots: {
76+
one: {
77+
type: IRNodeTypes.BLOCK,
78+
dynamic: {
79+
children: [{ template: 0 }],
80+
},
81+
},
82+
default: {
83+
type: IRNodeTypes.BLOCK,
84+
dynamic: {
85+
children: [{}, { template: 1 }, { template: 2 }],
86+
},
87+
},
88+
},
89+
},
90+
])
6391
})
6492

6593
test('nested slots', () => {
@@ -72,13 +100,75 @@ describe('compiler: transform slot', () => {
72100
})
73101

74102
test('dynamic slots name', () => {
75-
const { ir, code } = compileWithSlots(`<Comp>
76-
<template #[dynamicName]>foo</template>
77-
</Comp>`)
78-
expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
79-
const slots = (ir.block.operation[0] as CreateComponentIRNode).slots!
80-
expect(slots.length).toBe(1)
81-
expect(slots[0].name.isStatic).toBe(false)
103+
const { ir, code } = compileWithSlots(
104+
`<Comp>
105+
<template #[name]>foo</template>
106+
</Comp>`,
107+
)
82108
expect(code).toMatchSnapshot()
109+
expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
110+
expect(ir.block.operation).toMatchObject([
111+
{
112+
type: IRNodeTypes.CREATE_COMPONENT_NODE,
113+
tag: 'Comp',
114+
slots: undefined,
115+
dynamicSlots: [
116+
{
117+
name: {
118+
type: NodeTypes.SIMPLE_EXPRESSION,
119+
content: 'name',
120+
isStatic: false,
121+
},
122+
fn: { type: IRNodeTypes.BLOCK },
123+
},
124+
],
125+
},
126+
])
127+
})
128+
129+
describe('errors', () => {
130+
test('error on extraneous children w/ named default slot', () => {
131+
const onError = vi.fn()
132+
const source = `<Comp><template #default>foo</template>bar</Comp>`
133+
compileWithSlots(source, { onError })
134+
const index = source.indexOf('bar')
135+
expect(onError.mock.calls[0][0]).toMatchObject({
136+
code: ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
137+
loc: {
138+
start: {
139+
offset: index,
140+
line: 1,
141+
column: index + 1,
142+
},
143+
end: {
144+
offset: index + 3,
145+
line: 1,
146+
column: index + 4,
147+
},
148+
},
149+
})
150+
})
151+
152+
test('error on duplicated slot names', () => {
153+
const onError = vi.fn()
154+
const source = `<Comp><template #foo></template><template #foo></template></Comp>`
155+
compileWithSlots(source, { onError })
156+
const index = source.lastIndexOf('#foo')
157+
expect(onError.mock.calls[0][0]).toMatchObject({
158+
code: ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES,
159+
loc: {
160+
start: {
161+
offset: index,
162+
line: 1,
163+
column: index + 1,
164+
},
165+
end: {
166+
offset: index + 4,
167+
line: 1,
168+
column: index + 5,
169+
},
170+
},
171+
})
172+
})
83173
})
84174
})

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

+36-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { camelize, extend, isArray } from '@vue/shared'
22
import type { CodegenContext } from '../generate'
33
import {
4+
type ComponentDynamicSlot,
5+
type ComponentSlots,
46
type CreateComponentIRNode,
57
IRDynamicPropsKind,
68
type IRProp,
@@ -10,6 +12,7 @@ import {
1012
import {
1113
type CodeFragment,
1214
NEWLINE,
15+
SEGMENTS_ARRAY,
1316
SEGMENTS_ARRAY_NEWLINE,
1417
SEGMENTS_OBJECT,
1518
SEGMENTS_OBJECT_NEWLINE,
@@ -31,7 +34,7 @@ export function genCreateComponent(
3134
const { vaporHelper } = context
3235

3336
const tag = genTag()
34-
const isRoot = oper.root
37+
const { root: isRoot, slots, dynamicSlots } = oper
3538
const rawProps = genRawProps(oper.props, context)
3639

3740
return [
@@ -40,9 +43,17 @@ export function genCreateComponent(
4043
...genCall(
4144
vaporHelper('createComponent'),
4245
tag,
43-
rawProps || (oper.slots || isRoot ? 'null' : false),
44-
oper.slots ? genSlots(oper, context) : isRoot ? 'null' : false,
45-
isRoot && 'null',
46+
rawProps || (slots || dynamicSlots || isRoot ? 'null' : false),
47+
slots
48+
? genSlots(slots, context)
49+
: dynamicSlots || isRoot
50+
? 'null'
51+
: false,
52+
dynamicSlots
53+
? genDynamicSlots(dynamicSlots, context)
54+
: isRoot
55+
? 'null'
56+
: false,
4657
isRoot && 'true',
4758
),
4859
...genDirectivesForElement(oper.id, context),
@@ -137,18 +148,29 @@ function genModelModifiers(
137148
return [',', NEWLINE, ...modifiersKey, `: () => ({ ${modifiersVal} })`]
138149
}
139150

140-
function genSlots(oper: CreateComponentIRNode, context: CodegenContext) {
141-
const slotList = oper.slots!
151+
function genSlots(slots: ComponentSlots, context: CodegenContext) {
152+
const slotList = Object.entries(slots!)
142153
return genMulti(
143154
slotList.length > 1 ? SEGMENTS_OBJECT_NEWLINE : SEGMENTS_OBJECT,
144-
...slotList.map(({ name, block }) => {
145-
return [
146-
...(name.isStatic
147-
? [name.content]
148-
: ['[', ...genExpression(name, context), ']']),
149-
': ',
150-
...genBlock(block, context),
151-
]
155+
...slotList.map(([name, slot]) => {
156+
return [name, ': ', ...genBlock(slot, context)]
152157
}),
153158
)
154159
}
160+
161+
function genDynamicSlots(
162+
dynamicSlots: ComponentDynamicSlot[],
163+
context: CodegenContext,
164+
) {
165+
const slotsExpr = genMulti(
166+
dynamicSlots.length > 1 ? SEGMENTS_ARRAY_NEWLINE : SEGMENTS_ARRAY,
167+
...dynamicSlots.map(({ name, fn }) => {
168+
return genMulti(
169+
SEGMENTS_OBJECT_NEWLINE,
170+
['name: ', ...genExpression(name, context)],
171+
['fn: ', ...genBlock(fn, context)],
172+
)
173+
}),
174+
)
175+
return ['() => ', ...slotsExpr]
176+
}

packages/compiler-vapor/src/ir.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -199,12 +199,10 @@ export interface WithDirectiveIRNode extends BaseIRNode {
199199
builtin?: VaporHelper
200200
}
201201

202-
export interface ComponentStaticSlot {
203-
name: SimpleExpressionNode
204-
block: ComponentSlotBlockIRNode
202+
export interface ComponentSlotBlockIRNode extends BlockIRNode {
203+
// TODO slot props
205204
}
206-
207-
export interface ComponentSlotBlockIRNode extends BlockIRNode {}
205+
export type ComponentSlots = Record<string, ComponentSlotBlockIRNode>
208206
export interface ComponentDynamicSlot {
209207
name: SimpleExpressionNode
210208
fn: ComponentSlotBlockIRNode
@@ -217,7 +215,7 @@ export interface CreateComponentIRNode extends BaseIRNode {
217215
tag: string
218216
props: IRProps[]
219217

220-
slots?: ComponentStaticSlot[]
218+
slots?: ComponentSlots
221219
dynamicSlots?: ComponentDynamicSlot[]
222220

223221
resolve: boolean

packages/compiler-vapor/src/transform.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import {
1616
import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared'
1717
import {
1818
type BlockIRNode,
19-
type ComponentStaticSlot,
19+
type ComponentDynamicSlot,
20+
type ComponentSlots,
2021
DynamicFlag,
2122
type HackOptions,
2223
type IRDynamicInfo,
@@ -78,7 +79,8 @@ export class TransformContext<T extends AllNode = AllNode> {
7879

7980
comment: CommentNode[] = []
8081
component: Set<string> = this.ir.component
81-
slots: ComponentStaticSlot[] | null = null
82+
slots: ComponentSlots | null = null
83+
dynamicSlots: ComponentDynamicSlot[] | null = null
8284

8385
private globalId = 0
8486

@@ -92,12 +94,14 @@ export class TransformContext<T extends AllNode = AllNode> {
9294
}
9395

9496
enterBlock(ir: BlockIRNode, isVFor: boolean = false): () => void {
95-
const { block, template, dynamic, childrenTemplate, slots } = this
97+
const { block, template, dynamic, childrenTemplate, slots, dynamicSlots } =
98+
this
9699
this.block = ir
97100
this.dynamic = ir.dynamic
98101
this.template = ''
99102
this.childrenTemplate = []
100103
this.slots = null
104+
this.dynamicSlots = null
101105
isVFor && this.inVFor++
102106
return () => {
103107
// exit
@@ -107,6 +111,7 @@ export class TransformContext<T extends AllNode = AllNode> {
107111
this.dynamic = dynamic
108112
this.childrenTemplate = childrenTemplate
109113
this.slots = slots
114+
this.dynamicSlots = dynamicSlots
110115
isVFor && this.inVFor--
111116
}
112117
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,10 @@ function transformComponentElement(
105105
resolve,
106106
root,
107107
slots: context.slots || undefined,
108+
dynamicSlots: context.dynamicSlots || undefined,
108109
})
109110
context.slots = null
111+
context.dynamicSlots = null
110112
}
111113

112114
function resolveSetupReference(name: string, context: TransformContext) {

0 commit comments

Comments
 (0)