perf(compiler-vapor/runtime-vapor): finer update granularity (#222)
This commit is contained in:
parent
208dbc6d65
commit
8a59311a22
|
@ -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
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
|
Loading…
Reference in New Issue