perf(compiler-vapor/runtime-vapor): finer update granularity (#222)

This commit is contained in:
Doctor Wu 2024-06-03 06:48:13 +08:00 committed by GitHub
parent 208dbc6d65
commit 8a59311a22
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 141 additions and 71 deletions

View File

@ -6,13 +6,13 @@ const t0 = _template("foo")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n2 = _createComponent(_component_Comp, null, null, () => [{
const n2 = _createComponent(_component_Comp, null, null, [() => ({
name: _ctx.name,
fn: () => {
const n0 = t0()
return n0
}
}], true)
})], true)
return n2
}"
`;
@ -23,13 +23,13 @@ const t0 = _template("foo")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n2 = _createComponent(_component_Comp, null, null, () => [_createForSlots(_ctx.list, (item) => ({
const n2 = _createComponent(_component_Comp, null, null, [() => (_createForSlots(_ctx.list, (item) => ({
name: item,
fn: _withDestructure(({ bar }) => [bar], (_ctx0) => {
const n0 = t0()
return n0
})
}))], true)
})))], true)
return n2
}"
`;
@ -40,13 +40,13 @@ const t0 = _template("foo")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n2 = _createComponent(_component_Comp, null, null, () => [_createForSlots(_ctx.list, (_, __, index) => ({
const n2 = _createComponent(_component_Comp, null, null, [() => (_createForSlots(_ctx.list, (_, __, index) => ({
name: index,
fn: () => {
const n0 = t0()
return n0
}
}))], true)
})))], true)
return n2
}"
`;
@ -59,7 +59,7 @@ const t2 = _template("else condition")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n6 = _createComponent(_component_Comp, null, null, () => [_ctx.condition
const n6 = _createComponent(_component_Comp, null, null, [() => (_ctx.condition
? {
name: "condition",
fn: () => {
@ -84,7 +84,7 @@ export function render(_ctx) {
return n4
},
key: "2"
}], true)
})], true)
return n6
}"
`;
@ -151,13 +151,13 @@ exports[`compiler: transform slot > on component dynamically named slot 1`] = `
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n1 = _createComponent(_component_Comp, null, { }, () => [{
const n1 = _createComponent(_component_Comp, null, { }, [() => ({
name: _ctx.named,
fn: _withDestructure(({ foo }) => [foo], (_ctx0) => {
const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar])
return n0
})
}], true)
})], true)
return n1
}"
`;

View File

@ -168,24 +168,30 @@ function genDynamicSlots(
dynamicSlots: ComponentDynamicSlot[],
context: CodegenContext,
) {
const slotsExpr = genMulti(
return genMulti(
dynamicSlots.length > 1 ? DELIMITERS_ARRAY_NEWLINE : DELIMITERS_ARRAY,
...dynamicSlots.map(slot => genDynamicSlot(slot, context)),
...dynamicSlots.map(slot => genDynamicSlot(slot, context, true)),
)
return ['() => ', ...slotsExpr]
}
function genDynamicSlot(
slot: ComponentDynamicSlot,
context: CodegenContext,
top = false,
): CodeFragment[] {
switch (slot.slotType) {
case DynamicSlotType.BASIC:
return genBasicDynamicSlot(slot, context)
return top
? ['() => (', ...genBasicDynamicSlot(slot, context), ')']
: genBasicDynamicSlot(slot, context)
case DynamicSlotType.LOOP:
return genLoopSlot(slot, context)
return top
? ['() => (', ...genLoopSlot(slot, context), ')']
: genLoopSlot(slot, context)
case DynamicSlotType.CONDITIONAL:
return genConditionalSlot(slot, context)
return top
? ['() => (', ...genConditionalSlot(slot, context), ')']
: genConditionalSlot(slot, context)
}
}

View File

@ -97,10 +97,11 @@ describe('component: slots', () => {
const { render } = define({
render() {
return createComponent(Child, {}, { _: 2 as any }, () => [
flag1.value
? { name: 'one', fn: () => template('<span></span>')() }
: { name: 'two', fn: () => template('<div></div>')() },
return createComponent(Child, {}, { _: 2 as any }, [
() =>
flag1.value
? { name: 'one', fn: () => template('<span></span>')() }
: { name: 'two', fn: () => template('<div></div>')() },
])
},
})
@ -132,10 +133,11 @@ describe('component: slots', () => {
const { render } = define({
setup() {
return createComponent(Child, {}, {}, () => [
flag1.value
? [{ name: 'header', fn: () => template('header')() }]
: [{ name: 'footer', fn: () => template('footer')() }],
return createComponent(Child, {}, {}, [
() =>
flag1.value
? [{ name: 'header', fn: () => template('header')() }]
: [{ name: 'footer', fn: () => template('footer')() }],
])
},
})
@ -178,8 +180,8 @@ describe('component: slots', () => {
return template('content')()
},
},
() => [
[
[
() => [
{
name: 'inVFor',
fn: () => {
@ -188,14 +190,14 @@ describe('component: slots', () => {
},
},
],
{
() => ({
name: 'inVIf',
key: '1',
fn: () => {
instanceInVIfSlot = getCurrentInstance()
return template('content')()
},
},
}),
],
)
},
@ -206,6 +208,61 @@ describe('component: slots', () => {
expect(instanceInVIfSlot).toBe(instance)
})
test('dynamicSlots should update separately', async () => {
const flag1 = ref(true)
const flag2 = ref(true)
const slotFn1 = vitest.fn()
const slotFn2 = vitest.fn()
let instance: any
const Child = () => {
instance = getCurrentInstance()
return template('child')()
}
const { render } = define({
render() {
return createComponent(Child, {}, {}, [
() => {
slotFn1()
return flag1.value
? { name: 'one', fn: () => template('one')() }
: { name: 'two', fn: () => template('two')() }
},
() => {
slotFn2()
return flag2.value
? { name: 'three', fn: () => template('three')() }
: { name: 'four', fn: () => template('four')() }
},
])
},
})
render()
expect(instance.slots).toHaveProperty('one')
expect(instance.slots).toHaveProperty('three')
expect(slotFn1).toHaveBeenCalledTimes(1)
expect(slotFn2).toHaveBeenCalledTimes(1)
flag1.value = false
await nextTick()
expect(instance.slots).toHaveProperty('two')
expect(instance.slots).toHaveProperty('three')
expect(slotFn1).toHaveBeenCalledTimes(2)
expect(slotFn2).toHaveBeenCalledTimes(1)
flag2.value = false
await nextTick()
expect(instance.slots).toHaveProperty('two')
expect(instance.slots).toHaveProperty('four')
expect(slotFn1).toHaveBeenCalledTimes(2)
expect(slotFn2).toHaveBeenCalledTimes(2)
})
test.todo('should respect $stable flag', async () => {
// TODO: $stable flag?
})
@ -299,8 +356,11 @@ describe('component: slots', () => {
const { host } = define(() => {
// dynamic slot
return createComponent(Comp, {}, {}, () => [
{ name: 'header', fn: ({ title }) => template(`${title()}`)() },
return createComponent(Comp, {}, {}, [
() => ({
name: 'header',
fn: ({ title }) => template(`${title()}`)(),
}),
])
}).render()
@ -363,10 +423,11 @@ describe('component: slots', () => {
})
const { host } = define(() => {
return createComponent(Child, {}, {}, () => [
flag1.value
? { name: 'one', fn: () => template('one content')() }
: { name: 'two', fn: () => template('two content')() },
return createComponent(Child, {}, {}, [
() =>
flag1.value
? { name: 'one', fn: () => template('one content')() }
: { name: 'two', fn: () => template('two content')() },
])
}).render()

View File

@ -33,7 +33,9 @@ export interface DynamicSlot {
key?: string
}
export type DynamicSlots = () => (DynamicSlot | DynamicSlot[])[]
type DynamicSlotFn = () => DynamicSlot | DynamicSlot[]
export type DynamicSlots = DynamicSlotFn[]
export function initSlots(
instance: ComponentInternalInstance,
@ -51,50 +53,51 @@ export function initSlots(
if (dynamicSlots) {
slots = shallowReactive(slots)
const dynamicSlotKeys: Record<string, true> = {}
firstEffect(instance, () => {
const _dynamicSlots: (DynamicSlot | DynamicSlot[])[] =
callWithAsyncErrorHandling(
dynamicSlots,
instance,
VaporErrorCodes.RENDER_FUNCTION,
)
for (let i = 0; i < _dynamicSlots.length; i++) {
const slot = _dynamicSlots[i]
const dynamicSlotRecords: Record<string, boolean>[] = []
dynamicSlots.forEach((fn, index) => {
firstEffect(instance, () => {
const slotRecord = (dynamicSlotRecords[index] =
dynamicSlotRecords[index] || {})
const dynamicSlot: DynamicSlot | DynamicSlot[] =
callWithAsyncErrorHandling(
fn,
instance,
VaporErrorCodes.RENDER_FUNCTION,
)
// array of dynamic slot generated by <template v-for="..." #[...]>
if (isArray(slot)) {
for (let j = 0; j < slot.length; j++) {
slots[slot[j].name] = withCtx(slot[j].fn)
dynamicSlotKeys[slot[j].name] = true
if (isArray(dynamicSlot)) {
for (let j = 0; j < dynamicSlot.length; j++) {
slots[dynamicSlot[j].name] = withCtx(dynamicSlot[j].fn)
slotRecord[dynamicSlot[j].name] = true
}
} else if (slot) {
} else if (dynamicSlot) {
// conditional single slot generated by <template v-if="..." #foo>
slots[slot.name] = withCtx(
slot.key
slots[dynamicSlot.name] = withCtx(
dynamicSlot.key
? (...args: any[]) => {
const res = slot.fn(...args)
const res = dynamicSlot.fn(...args)
// attach branch key so each conditional branch is considered a
// different fragment
if (res) (res as any).key = slot.key
if (res) (res as any).key = dynamicSlot.key
return res
}
: slot.fn,
: dynamicSlot.fn,
)
dynamicSlotKeys[slot.name] = true
slotRecord[dynamicSlot.name] = true
}
}
// delete stale slots
for (const key in dynamicSlotKeys) {
if (
!_dynamicSlots.some(slot =>
slot && isArray(slot)
? slot.some(s => s.name === key)
: slot.name === key,
)
) {
delete slots[key]
// delete stale slots
for (const key in slotRecord) {
if (
slotRecord[key] &&
!(dynamicSlot && isArray(dynamicSlot)
? dynamicSlot.some(s => s.name === key)
: dynamicSlot.name === key)
) {
slotRecord[key] = false
delete slots[key]
}
}
}
})
})
}

View File

@ -88,12 +88,12 @@ export function callWithErrorHandling(
return res
}
export function callWithAsyncErrorHandling(
fn: Function | Function[],
export function callWithAsyncErrorHandling<F extends Function | Function[]>(
fn: F,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
args?: unknown[],
): any[] {
): F extends Function ? any : any[] {
if (isFunction(fn)) {
const res = callWithErrorHandling(fn, instance, type, args)
if (res && isPromise(res)) {