Skip to content

Commit 8a59311

Browse files
authored
perf(compiler-vapor/runtime-vapor): finer update granularity (#222)
1 parent 208dbc6 commit 8a59311

File tree

5 files changed

+141
-71
lines changed

5 files changed

+141
-71
lines changed

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

+10-10
Original file line numberDiff line numberDiff line change
@@ -6,13 +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, null, () => [{
9+
const n2 = _createComponent(_component_Comp, null, null, [() => ({
1010
name: _ctx.name,
1111
fn: () => {
1212
const n0 = t0()
1313
return n0
1414
}
15-
}], true)
15+
})], true)
1616
return n2
1717
}"
1818
`;
@@ -23,13 +23,13 @@ const t0 = _template("foo")
2323
2424
export function render(_ctx) {
2525
const _component_Comp = _resolveComponent("Comp")
26-
const n2 = _createComponent(_component_Comp, null, null, () => [_createForSlots(_ctx.list, (item) => ({
26+
const n2 = _createComponent(_component_Comp, null, null, [() => (_createForSlots(_ctx.list, (item) => ({
2727
name: item,
2828
fn: _withDestructure(({ bar }) => [bar], (_ctx0) => {
2929
const n0 = t0()
3030
return n0
3131
})
32-
}))], true)
32+
})))], true)
3333
return n2
3434
}"
3535
`;
@@ -40,13 +40,13 @@ const t0 = _template("foo")
4040
4141
export function render(_ctx) {
4242
const _component_Comp = _resolveComponent("Comp")
43-
const n2 = _createComponent(_component_Comp, null, null, () => [_createForSlots(_ctx.list, (_, __, index) => ({
43+
const n2 = _createComponent(_component_Comp, null, null, [() => (_createForSlots(_ctx.list, (_, __, index) => ({
4444
name: index,
4545
fn: () => {
4646
const n0 = t0()
4747
return n0
4848
}
49-
}))], true)
49+
})))], true)
5050
return n2
5151
}"
5252
`;
@@ -59,7 +59,7 @@ const t2 = _template("else condition")
5959
6060
export function render(_ctx) {
6161
const _component_Comp = _resolveComponent("Comp")
62-
const n6 = _createComponent(_component_Comp, null, null, () => [_ctx.condition
62+
const n6 = _createComponent(_component_Comp, null, null, [() => (_ctx.condition
6363
? {
6464
name: "condition",
6565
fn: () => {
@@ -84,7 +84,7 @@ export function render(_ctx) {
8484
return n4
8585
},
8686
key: "2"
87-
}], true)
87+
})], true)
8888
return n6
8989
}"
9090
`;
@@ -151,13 +151,13 @@ exports[`compiler: transform slot > on component dynamically named slot 1`] = `
151151
152152
export function render(_ctx) {
153153
const _component_Comp = _resolveComponent("Comp")
154-
const n1 = _createComponent(_component_Comp, null, { }, () => [{
154+
const n1 = _createComponent(_component_Comp, null, { }, [() => ({
155155
name: _ctx.named,
156156
fn: _withDestructure(({ foo }) => [foo], (_ctx0) => {
157157
const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar])
158158
return n0
159159
})
160-
}], true)
160+
})], true)
161161
return n1
162162
}"
163163
`;

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

+12-6
Original file line numberDiff line numberDiff line change
@@ -168,24 +168,30 @@ function genDynamicSlots(
168168
dynamicSlots: ComponentDynamicSlot[],
169169
context: CodegenContext,
170170
) {
171-
const slotsExpr = genMulti(
171+
return genMulti(
172172
dynamicSlots.length > 1 ? DELIMITERS_ARRAY_NEWLINE : DELIMITERS_ARRAY,
173-
...dynamicSlots.map(slot => genDynamicSlot(slot, context)),
173+
...dynamicSlots.map(slot => genDynamicSlot(slot, context, true)),
174174
)
175-
return ['() => ', ...slotsExpr]
176175
}
177176

178177
function genDynamicSlot(
179178
slot: ComponentDynamicSlot,
180179
context: CodegenContext,
180+
top = false,
181181
): CodeFragment[] {
182182
switch (slot.slotType) {
183183
case DynamicSlotType.BASIC:
184-
return genBasicDynamicSlot(slot, context)
184+
return top
185+
? ['() => (', ...genBasicDynamicSlot(slot, context), ')']
186+
: genBasicDynamicSlot(slot, context)
185187
case DynamicSlotType.LOOP:
186-
return genLoopSlot(slot, context)
188+
return top
189+
? ['() => (', ...genLoopSlot(slot, context), ')']
190+
: genLoopSlot(slot, context)
187191
case DynamicSlotType.CONDITIONAL:
188-
return genConditionalSlot(slot, context)
192+
return top
193+
? ['() => (', ...genConditionalSlot(slot, context), ')']
194+
: genConditionalSlot(slot, context)
189195
}
190196
}
191197

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

+79-18
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,11 @@ describe('component: slots', () => {
9797

9898
const { render } = define({
9999
render() {
100-
return createComponent(Child, {}, { _: 2 as any }, () => [
101-
flag1.value
102-
? { name: 'one', fn: () => template('<span></span>')() }
103-
: { name: 'two', fn: () => template('<div></div>')() },
100+
return createComponent(Child, {}, { _: 2 as any }, [
101+
() =>
102+
flag1.value
103+
? { name: 'one', fn: () => template('<span></span>')() }
104+
: { name: 'two', fn: () => template('<div></div>')() },
104105
])
105106
},
106107
})
@@ -132,10 +133,11 @@ describe('component: slots', () => {
132133

133134
const { render } = define({
134135
setup() {
135-
return createComponent(Child, {}, {}, () => [
136-
flag1.value
137-
? [{ name: 'header', fn: () => template('header')() }]
138-
: [{ name: 'footer', fn: () => template('footer')() }],
136+
return createComponent(Child, {}, {}, [
137+
() =>
138+
flag1.value
139+
? [{ name: 'header', fn: () => template('header')() }]
140+
: [{ name: 'footer', fn: () => template('footer')() }],
139141
])
140142
},
141143
})
@@ -178,8 +180,8 @@ describe('component: slots', () => {
178180
return template('content')()
179181
},
180182
},
181-
() => [
182-
[
183+
[
184+
() => [
183185
{
184186
name: 'inVFor',
185187
fn: () => {
@@ -188,14 +190,14 @@ describe('component: slots', () => {
188190
},
189191
},
190192
],
191-
{
193+
() => ({
192194
name: 'inVIf',
193195
key: '1',
194196
fn: () => {
195197
instanceInVIfSlot = getCurrentInstance()
196198
return template('content')()
197199
},
198-
},
200+
}),
199201
],
200202
)
201203
},
@@ -206,6 +208,61 @@ describe('component: slots', () => {
206208
expect(instanceInVIfSlot).toBe(instance)
207209
})
208210

211+
test('dynamicSlots should update separately', async () => {
212+
const flag1 = ref(true)
213+
const flag2 = ref(true)
214+
const slotFn1 = vitest.fn()
215+
const slotFn2 = vitest.fn()
216+
217+
let instance: any
218+
const Child = () => {
219+
instance = getCurrentInstance()
220+
return template('child')()
221+
}
222+
223+
const { render } = define({
224+
render() {
225+
return createComponent(Child, {}, {}, [
226+
() => {
227+
slotFn1()
228+
return flag1.value
229+
? { name: 'one', fn: () => template('one')() }
230+
: { name: 'two', fn: () => template('two')() }
231+
},
232+
() => {
233+
slotFn2()
234+
return flag2.value
235+
? { name: 'three', fn: () => template('three')() }
236+
: { name: 'four', fn: () => template('four')() }
237+
},
238+
])
239+
},
240+
})
241+
242+
render()
243+
244+
expect(instance.slots).toHaveProperty('one')
245+
expect(instance.slots).toHaveProperty('three')
246+
expect(slotFn1).toHaveBeenCalledTimes(1)
247+
expect(slotFn2).toHaveBeenCalledTimes(1)
248+
249+
flag1.value = false
250+
await nextTick()
251+
252+
expect(instance.slots).toHaveProperty('two')
253+
expect(instance.slots).toHaveProperty('three')
254+
expect(slotFn1).toHaveBeenCalledTimes(2)
255+
expect(slotFn2).toHaveBeenCalledTimes(1)
256+
257+
flag2.value = false
258+
await nextTick()
259+
260+
expect(instance.slots).toHaveProperty('two')
261+
expect(instance.slots).toHaveProperty('four')
262+
expect(slotFn1).toHaveBeenCalledTimes(2)
263+
expect(slotFn2).toHaveBeenCalledTimes(2)
264+
})
265+
209266
test.todo('should respect $stable flag', async () => {
210267
// TODO: $stable flag?
211268
})
@@ -299,8 +356,11 @@ describe('component: slots', () => {
299356

300357
const { host } = define(() => {
301358
// dynamic slot
302-
return createComponent(Comp, {}, {}, () => [
303-
{ name: 'header', fn: ({ title }) => template(`${title()}`)() },
359+
return createComponent(Comp, {}, {}, [
360+
() => ({
361+
name: 'header',
362+
fn: ({ title }) => template(`${title()}`)(),
363+
}),
304364
])
305365
}).render()
306366

@@ -363,10 +423,11 @@ describe('component: slots', () => {
363423
})
364424

365425
const { host } = define(() => {
366-
return createComponent(Child, {}, {}, () => [
367-
flag1.value
368-
? { name: 'one', fn: () => template('one content')() }
369-
: { name: 'two', fn: () => template('two content')() },
426+
return createComponent(Child, {}, {}, [
427+
() =>
428+
flag1.value
429+
? { name: 'one', fn: () => template('one content')() }
430+
: { name: 'two', fn: () => template('two content')() },
370431
])
371432
}).render()
372433

packages/runtime-vapor/src/componentSlots.ts

+37-34
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ export interface DynamicSlot {
3333
key?: string
3434
}
3535

36-
export type DynamicSlots = () => (DynamicSlot | DynamicSlot[])[]
36+
type DynamicSlotFn = () => DynamicSlot | DynamicSlot[]
37+
38+
export type DynamicSlots = DynamicSlotFn[]
3739

3840
export function initSlots(
3941
instance: ComponentInternalInstance,
@@ -51,50 +53,51 @@ export function initSlots(
5153

5254
if (dynamicSlots) {
5355
slots = shallowReactive(slots)
54-
const dynamicSlotKeys: Record<string, true> = {}
55-
firstEffect(instance, () => {
56-
const _dynamicSlots: (DynamicSlot | DynamicSlot[])[] =
57-
callWithAsyncErrorHandling(
58-
dynamicSlots,
59-
instance,
60-
VaporErrorCodes.RENDER_FUNCTION,
61-
)
62-
for (let i = 0; i < _dynamicSlots.length; i++) {
63-
const slot = _dynamicSlots[i]
56+
const dynamicSlotRecords: Record<string, boolean>[] = []
57+
dynamicSlots.forEach((fn, index) => {
58+
firstEffect(instance, () => {
59+
const slotRecord = (dynamicSlotRecords[index] =
60+
dynamicSlotRecords[index] || {})
61+
const dynamicSlot: DynamicSlot | DynamicSlot[] =
62+
callWithAsyncErrorHandling(
63+
fn,
64+
instance,
65+
VaporErrorCodes.RENDER_FUNCTION,
66+
)
6467
// array of dynamic slot generated by <template v-for="..." #[...]>
65-
if (isArray(slot)) {
66-
for (let j = 0; j < slot.length; j++) {
67-
slots[slot[j].name] = withCtx(slot[j].fn)
68-
dynamicSlotKeys[slot[j].name] = true
68+
if (isArray(dynamicSlot)) {
69+
for (let j = 0; j < dynamicSlot.length; j++) {
70+
slots[dynamicSlot[j].name] = withCtx(dynamicSlot[j].fn)
71+
slotRecord[dynamicSlot[j].name] = true
6972
}
70-
} else if (slot) {
73+
} else if (dynamicSlot) {
7174
// conditional single slot generated by <template v-if="..." #foo>
72-
slots[slot.name] = withCtx(
73-
slot.key
75+
slots[dynamicSlot.name] = withCtx(
76+
dynamicSlot.key
7477
? (...args: any[]) => {
75-
const res = slot.fn(...args)
78+
const res = dynamicSlot.fn(...args)
7679
// attach branch key so each conditional branch is considered a
7780
// different fragment
78-
if (res) (res as any).key = slot.key
81+
if (res) (res as any).key = dynamicSlot.key
7982
return res
8083
}
81-
: slot.fn,
84+
: dynamicSlot.fn,
8285
)
83-
dynamicSlotKeys[slot.name] = true
86+
slotRecord[dynamicSlot.name] = true
8487
}
85-
}
86-
// delete stale slots
87-
for (const key in dynamicSlotKeys) {
88-
if (
89-
!_dynamicSlots.some(slot =>
90-
slot && isArray(slot)
91-
? slot.some(s => s.name === key)
92-
: slot.name === key,
93-
)
94-
) {
95-
delete slots[key]
88+
// delete stale slots
89+
for (const key in slotRecord) {
90+
if (
91+
slotRecord[key] &&
92+
!(dynamicSlot && isArray(dynamicSlot)
93+
? dynamicSlot.some(s => s.name === key)
94+
: dynamicSlot.name === key)
95+
) {
96+
slotRecord[key] = false
97+
delete slots[key]
98+
}
9699
}
97-
}
100+
})
98101
})
99102
}
100103

packages/runtime-vapor/src/errorHandling.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,12 @@ export function callWithErrorHandling(
8888
return res
8989
}
9090

91-
export function callWithAsyncErrorHandling(
92-
fn: Function | Function[],
91+
export function callWithAsyncErrorHandling<F extends Function | Function[]>(
92+
fn: F,
9393
instance: ComponentInternalInstance | null,
9494
type: ErrorTypes,
9595
args?: unknown[],
96-
): any[] {
96+
): F extends Function ? any : any[] {
9797
if (isFunction(fn)) {
9898
const res = callWithErrorHandling(fn, instance, type, args)
9999
if (res && isPromise(res)) {

0 commit comments

Comments
 (0)