Skip to content

Commit 685e3f3

Browse files
committedJul 12, 2024··
fix(runtime-core): more edge case fix for manually rendered compiled slot
close #11336
1 parent 314ce82 commit 685e3f3

File tree

2 files changed

+84
-10
lines changed

2 files changed

+84
-10
lines changed
 

‎packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts

+65
Original file line numberDiff line numberDiff line change
@@ -1040,4 +1040,69 @@ describe('renderer: optimized mode', () => {
10401040
expect(app.config.errorHandler).not.toHaveBeenCalled()
10411041
}
10421042
})
1043+
1044+
// #11336
1045+
test('should bail manually rendered compiler slots for both mount and update (2)', async () => {
1046+
// only reproducible in prod
1047+
__DEV__ = false
1048+
const n = ref(0)
1049+
function Outer(_: any, { slots }: any) {
1050+
n.value // track
1051+
return slots.default()
1052+
}
1053+
const Mid = {
1054+
render(ctx: any) {
1055+
return (
1056+
openBlock(),
1057+
createElementBlock('div', null, [renderSlot(ctx.$slots, 'default')])
1058+
)
1059+
},
1060+
}
1061+
const show = ref(false)
1062+
const App = {
1063+
render() {
1064+
return (
1065+
openBlock(),
1066+
createBlock(Outer, null, {
1067+
default: withCtx(() => [
1068+
createVNode(Mid, null, {
1069+
default: withCtx(() => [
1070+
createElementVNode('div', null, [
1071+
show.value
1072+
? (openBlock(),
1073+
createElementBlock('div', { key: 0 }, '1'))
1074+
: createCommentVNode('v-if', true),
1075+
createElementVNode('div', null, '2'),
1076+
createElementVNode('div', null, '3'),
1077+
]),
1078+
createElementVNode('div', null, '4'),
1079+
]),
1080+
_: 1 /* STABLE */,
1081+
}),
1082+
]),
1083+
_: 1 /* STABLE */,
1084+
})
1085+
)
1086+
},
1087+
}
1088+
1089+
const app = createApp(App)
1090+
app.config.errorHandler = vi.fn()
1091+
1092+
try {
1093+
app.mount(root)
1094+
1095+
// force Outer update, which will assign new slots to Mid
1096+
// we want to make sure the compiled slot flag doesn't accidentally
1097+
// get assigned again
1098+
n.value++
1099+
await nextTick()
1100+
1101+
show.value = true
1102+
await nextTick()
1103+
} finally {
1104+
__DEV__ = true
1105+
expect(app.config.errorHandler).not.toHaveBeenCalled()
1106+
}
1107+
})
10431108
})

‎packages/runtime-core/src/componentSlots.ts

+19-10
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
ShapeFlags,
1313
SlotFlags,
1414
def,
15-
extend,
1615
isArray,
1716
isFunction,
1817
} from '@vue/shared'
@@ -161,6 +160,22 @@ const normalizeVNodeSlots = (
161160
instance.slots.default = () => normalized
162161
}
163162

163+
const assignSlots = (
164+
slots: InternalSlots,
165+
children: Slots,
166+
optimized: boolean,
167+
) => {
168+
for (const key in children) {
169+
// #2893
170+
// when rendering the optimized slots by manually written render function,
171+
// do not copy the `slots._` compiler flag so that `renderSlot` creates
172+
// slot Fragment with BAIL patchFlag to force full updates
173+
if (optimized || key !== '_') {
174+
slots[key] = children[key]
175+
}
176+
}
177+
}
178+
164179
export const initSlots = (
165180
instance: ComponentInternalInstance,
166181
children: VNodeNormalizedChildren,
@@ -170,16 +185,10 @@ export const initSlots = (
170185
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
171186
const type = (children as RawSlots)._
172187
if (type) {
173-
extend(slots, children as InternalSlots)
188+
assignSlots(slots, children as Slots, optimized)
174189
// make compiler marker non-enumerable
175190
if (optimized) {
176191
def(slots, '_', type, true)
177-
} else {
178-
// #2893
179-
// when rendering the optimized slots by manually written render function,
180-
// we need to delete the `slots._` flag if necessary to make subsequent
181-
// updates reliable, i.e. let the `renderSlot` create the bailed Fragment
182-
delete slots._
183192
}
184193
} else {
185194
normalizeObjectSlots(children as RawSlots, slots, instance)
@@ -204,7 +213,7 @@ export const updateSlots = (
204213
if (__DEV__ && isHmrUpdating) {
205214
// Parent was HMR updated so slot content may have changed.
206215
// force update slots and mark instance for hmr as well
207-
extend(slots, children as Slots)
216+
assignSlots(slots, children as Slots, optimized)
208217
trigger(instance, TriggerOpTypes.SET, '$slots')
209218
} else if (optimized && type === SlotFlags.STABLE) {
210219
// compiled AND stable.
@@ -213,7 +222,7 @@ export const updateSlots = (
213222
} else {
214223
// compiled but dynamic (v-if/v-for on slots) - update slots, but skip
215224
// normalization.
216-
extend(slots, children as Slots)
225+
assignSlots(slots, children as Slots, optimized)
217226
}
218227
} else {
219228
needDeletionCheck = !(children as RawSlots).$stable

0 commit comments

Comments
 (0)
Please sign in to comment.