Skip to content

Commit

Permalink
fix(suspense): fix suspense patching in optimized mode
Browse files Browse the repository at this point in the history
fix #3828
  • Loading branch information
yyx990803 committed May 27, 2021
1 parent f0eb197 commit 9f24195
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 29 deletions.
37 changes: 37 additions & 0 deletions packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
createApp
} from '@vue/runtime-test'
import { PatchFlags, SlotFlags } from '@vue/shared'
import { SuspenseImpl } from '../src/components/Suspense'

describe('renderer: optimized mode', () => {
let root: TestElement
Expand Down Expand Up @@ -784,4 +785,40 @@ describe('renderer: optimized mode', () => {
await nextTick()
expect(inner(root)).toBe('<div><div><span>loading</span></div></div>')
})

// #3828
test('patch Suspense in optimized mode w/ nested dynamic nodes', async () => {
const show = ref(false)

const app = createApp({
render() {
return (
openBlock(),
createBlock(
Fragment,
null,
[
(openBlock(),
createBlock(SuspenseImpl, null, {
default: withCtx(() => [
createVNode('div', null, [
createVNode('div', null, show.value, PatchFlags.TEXT)
])
]),
_: SlotFlags.STABLE
}))
],
PatchFlags.STABLE_FRAGMENT
)
)
}
})

app.mount(root)
expect(inner(root)).toBe('<div><div>false</div></div>')

show.value = true
await nextTick()
expect(inner(root)).toBe('<div><div>true</div></div>')
})
})
57 changes: 34 additions & 23 deletions packages/runtime-core/src/components/Suspense.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import {
VNode,
normalizeVNode,
VNodeChild,
VNodeProps,
isSameVNodeType
isSameVNodeType,
openBlock,
closeBlock,
currentBlock,
createVNode
} from '../vnode'
import { isFunction, isArray, ShapeFlags, toNumber } from '@vue/shared'
import { ComponentInternalInstance, handleSetupResult } from '../component'
Expand Down Expand Up @@ -79,7 +82,8 @@ export const SuspenseImpl = {
}
},
hydrate: hydrateSuspense,
create: createSuspenseBoundary
create: createSuspenseBoundary,
normalize: normalizeSuspenseChildren
}

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

export function normalizeSuspenseChildren(
vnode: VNode
): {
content: VNode
fallback: VNode
} {
function normalizeSuspenseChildren(vnode: VNode) {
const { shapeFlag, children } = vnode
let content: VNode
let fallback: VNode
if (shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
content = normalizeSuspenseSlot((children as Slots).default)
fallback = normalizeSuspenseSlot((children as Slots).fallback)
} else {
content = normalizeSuspenseSlot(children as VNodeChild)
fallback = normalizeVNode(null)
}
return {
content,
fallback
}
const isSlotChildren = shapeFlag & ShapeFlags.SLOTS_CHILDREN
vnode.ssContent = normalizeSuspenseSlot(
isSlotChildren ? (children as Slots).default : children
)
vnode.ssFallback = isSlotChildren
? normalizeSuspenseSlot((children as Slots).fallback)
: createVNode(Comment)
}

function normalizeSuspenseSlot(s: any) {
let block: VNode[] | null | undefined
if (isFunction(s)) {
const isCompiledSlot = s._c
if (isCompiledSlot) {
// disableTracking: false
// allow block tracking for compiled slots
// (see ./componentRenderContext.ts)
s._d = false
openBlock()
}
s = s()
if (isCompiledSlot) {
s._d = true
block = currentBlock
closeBlock()
}
}
if (isArray(s)) {
const singleChild = filterSingleRoot(s)
Expand All @@ -742,7 +749,11 @@ function normalizeSuspenseSlot(s: any) {
}
s = singleChild
}
return normalizeVNode(s)
s = normalizeVNode(s)
if (block) {
s.dynamicChildren = block.filter(c => c !== s)
}
return s
}

export function queueEffectWithSuspense(
Expand Down
9 changes: 3 additions & 6 deletions packages/runtime-core/src/vnode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ import { AppContext } from './apiCreateApp'
import {
SuspenseImpl,
isSuspense,
SuspenseBoundary,
normalizeSuspenseChildren
SuspenseBoundary
} from './components/Suspense'
import { DirectiveBinding } from './directives'
import { TransitionHooks } from './components/BaseTransition'
Expand Down Expand Up @@ -186,7 +185,7 @@ export interface VNode<
// structure would be stable. This allows us to skip most children diffing
// and only worry about the dynamic nodes (indicated by patch flags).
export const blockStack: (VNode[] | null)[] = []
let currentBlock: VNode[] | null = null
export let currentBlock: VNode[] | null = null

/**
* Open a block.
Expand Down Expand Up @@ -452,9 +451,7 @@ function _createVNode(

// normalize suspense children
if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
const { content, fallback } = normalizeSuspenseChildren(vnode)
vnode.ssContent = content
vnode.ssFallback = fallback
;(type as typeof SuspenseImpl).normalize(vnode)
}

if (
Expand Down

0 comments on commit 9f24195

Please sign in to comment.