Skip to content

Commit 9f24195

Browse files
committed
fix(suspense): fix suspense patching in optimized mode
fix #3828
1 parent f0eb197 commit 9f24195

File tree

3 files changed

+74
-29
lines changed

3 files changed

+74
-29
lines changed

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

+37
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
createApp
2424
} from '@vue/runtime-test'
2525
import { PatchFlags, SlotFlags } from '@vue/shared'
26+
import { SuspenseImpl } from '../src/components/Suspense'
2627

2728
describe('renderer: optimized mode', () => {
2829
let root: TestElement
@@ -784,4 +785,40 @@ describe('renderer: optimized mode', () => {
784785
await nextTick()
785786
expect(inner(root)).toBe('<div><div><span>loading</span></div></div>')
786787
})
788+
789+
// #3828
790+
test('patch Suspense in optimized mode w/ nested dynamic nodes', async () => {
791+
const show = ref(false)
792+
793+
const app = createApp({
794+
render() {
795+
return (
796+
openBlock(),
797+
createBlock(
798+
Fragment,
799+
null,
800+
[
801+
(openBlock(),
802+
createBlock(SuspenseImpl, null, {
803+
default: withCtx(() => [
804+
createVNode('div', null, [
805+
createVNode('div', null, show.value, PatchFlags.TEXT)
806+
])
807+
]),
808+
_: SlotFlags.STABLE
809+
}))
810+
],
811+
PatchFlags.STABLE_FRAGMENT
812+
)
813+
)
814+
}
815+
})
816+
817+
app.mount(root)
818+
expect(inner(root)).toBe('<div><div>false</div></div>')
819+
820+
show.value = true
821+
await nextTick()
822+
expect(inner(root)).toBe('<div><div>true</div></div>')
823+
})
787824
})

packages/runtime-core/src/components/Suspense.ts

+34-23
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import {
22
VNode,
33
normalizeVNode,
4-
VNodeChild,
54
VNodeProps,
6-
isSameVNodeType
5+
isSameVNodeType,
6+
openBlock,
7+
closeBlock,
8+
currentBlock,
9+
createVNode
710
} from '../vnode'
811
import { isFunction, isArray, ShapeFlags, toNumber } from '@vue/shared'
912
import { ComponentInternalInstance, handleSetupResult } from '../component'
@@ -79,7 +82,8 @@ export const SuspenseImpl = {
7982
}
8083
},
8184
hydrate: hydrateSuspense,
82-
create: createSuspenseBoundary
85+
create: createSuspenseBoundary,
86+
normalize: normalizeSuspenseChildren
8387
}
8488

8589
// Force-casted public typing for h and TSX props inference
@@ -709,31 +713,34 @@ function hydrateSuspense(
709713
/* eslint-enable no-restricted-globals */
710714
}
711715

712-
export function normalizeSuspenseChildren(
713-
vnode: VNode
714-
): {
715-
content: VNode
716-
fallback: VNode
717-
} {
716+
function normalizeSuspenseChildren(vnode: VNode) {
718717
const { shapeFlag, children } = vnode
719-
let content: VNode
720-
let fallback: VNode
721-
if (shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
722-
content = normalizeSuspenseSlot((children as Slots).default)
723-
fallback = normalizeSuspenseSlot((children as Slots).fallback)
724-
} else {
725-
content = normalizeSuspenseSlot(children as VNodeChild)
726-
fallback = normalizeVNode(null)
727-
}
728-
return {
729-
content,
730-
fallback
731-
}
718+
const isSlotChildren = shapeFlag & ShapeFlags.SLOTS_CHILDREN
719+
vnode.ssContent = normalizeSuspenseSlot(
720+
isSlotChildren ? (children as Slots).default : children
721+
)
722+
vnode.ssFallback = isSlotChildren
723+
? normalizeSuspenseSlot((children as Slots).fallback)
724+
: createVNode(Comment)
732725
}
733726

734727
function normalizeSuspenseSlot(s: any) {
728+
let block: VNode[] | null | undefined
735729
if (isFunction(s)) {
730+
const isCompiledSlot = s._c
731+
if (isCompiledSlot) {
732+
// disableTracking: false
733+
// allow block tracking for compiled slots
734+
// (see ./componentRenderContext.ts)
735+
s._d = false
736+
openBlock()
737+
}
736738
s = s()
739+
if (isCompiledSlot) {
740+
s._d = true
741+
block = currentBlock
742+
closeBlock()
743+
}
737744
}
738745
if (isArray(s)) {
739746
const singleChild = filterSingleRoot(s)
@@ -742,7 +749,11 @@ function normalizeSuspenseSlot(s: any) {
742749
}
743750
s = singleChild
744751
}
745-
return normalizeVNode(s)
752+
s = normalizeVNode(s)
753+
if (block) {
754+
s.dynamicChildren = block.filter(c => c !== s)
755+
}
756+
return s
746757
}
747758

748759
export function queueEffectWithSuspense(

packages/runtime-core/src/vnode.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ import { AppContext } from './apiCreateApp'
2626
import {
2727
SuspenseImpl,
2828
isSuspense,
29-
SuspenseBoundary,
30-
normalizeSuspenseChildren
29+
SuspenseBoundary
3130
} from './components/Suspense'
3231
import { DirectiveBinding } from './directives'
3332
import { TransitionHooks } from './components/BaseTransition'
@@ -186,7 +185,7 @@ export interface VNode<
186185
// structure would be stable. This allows us to skip most children diffing
187186
// and only worry about the dynamic nodes (indicated by patch flags).
188187
export const blockStack: (VNode[] | null)[] = []
189-
let currentBlock: VNode[] | null = null
188+
export let currentBlock: VNode[] | null = null
190189

191190
/**
192191
* Open a block.
@@ -452,9 +451,7 @@ function _createVNode(
452451

453452
// normalize suspense children
454453
if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
455-
const { content, fallback } = normalizeSuspenseChildren(vnode)
456-
vnode.ssContent = content
457-
vnode.ssFallback = fallback
454+
;(type as typeof SuspenseImpl).normalize(vnode)
458455
}
459456

460457
if (

0 commit comments

Comments
 (0)