From a16c87be638c9a2b1ba64dbfe7cf76927e959914 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 10 Sep 2019 11:01:11 -0400 Subject: [PATCH] wip: suspense refactor --- .../__tests__/rendererSuspense.spec.ts | 6 +- packages/runtime-core/src/createRenderer.ts | 106 ++++++++++-------- packages/runtime-core/src/suspense.ts | 30 ++++- 3 files changed, 86 insertions(+), 56 deletions(-) diff --git a/packages/runtime-core/__tests__/rendererSuspense.spec.ts b/packages/runtime-core/__tests__/rendererSuspense.spec.ts index a577c5e9..faafcbf3 100644 --- a/packages/runtime-core/__tests__/rendererSuspense.spec.ts +++ b/packages/runtime-core/__tests__/rendererSuspense.spec.ts @@ -23,7 +23,6 @@ describe('renderer: suspense', () => { } }) - // TODO test mounted hook & watch callback buffering const AsyncChild = createAsyncComponent( () => new Promise(resolve => { @@ -62,7 +61,6 @@ describe('renderer: suspense', () => { const Comp = { name: 'root', setup() { - // TODO test fallback return () => h(Suspense, [msg.value, h(Mid), h(AsyncChild2, { msg: 'child 2' })]) } @@ -79,7 +77,9 @@ describe('renderer: suspense', () => { ) }) - test.todo('fallback content update') + test.todo('buffer mounted/updated hooks & watch callbacks') + + test.todo('fallback content') test.todo('content update before suspense resolve') diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts index 7ea13d94..ff73dcdf 100644 --- a/packages/runtime-core/src/createRenderer.ts +++ b/packages/runtime-core/src/createRenderer.ts @@ -44,7 +44,11 @@ import { pushWarningContext, popWarningContext, warn } from './warning' import { invokeDirectiveHook } from './directives' import { ComponentPublicInstance } from './componentPublicInstanceProxy' import { App, createAppAPI } from './apiApp' -import { SuspenseBoundary, createSuspenseBoundary } from './suspense' +import { + SuspenseBoundary, + createSuspenseBoundary, + normalizeSuspenseChildren +} from './suspense' import { provide } from './apiInject' const prodEffectOptions = { @@ -609,26 +613,6 @@ export function createRenderer< parentSuspense: SuspenseBoundary | null = null ) { if (n1 == null) { - const contentContainer = hostCreateElement('div') - - function retry() { - processFragment( - suspense.oldSubTree, - suspense.subTree as HostVNode, - contentContainer, - null, - parentComponent, - isSVG, - optimized - ) - if (suspense.deps > 0) { - // still pending. - // TODO patch the fallback tree. - } else { - suspense.resolve() - } - } - function resolve() { // unmount fallback tree unmount(suspense.fallbackTree as HostVNode, parentComponent, true) @@ -657,7 +641,7 @@ export function createRenderer< const suspense = (n2.suspense = createSuspenseBoundary( n2, parentSuspense, - retry, + hostCreateElement('div'), resolve )) @@ -668,15 +652,16 @@ export function createRenderer< setCurrentInstance(null) } - // start mounting the subtree off-dom + const { content, fallback } = normalizeSuspenseChildren(n2) + suspense.subTree = content + suspense.fallbackTree = fallback + + // start mounting the content subtree in an off-dom container // TODO should buffer postQueue jobs on current boundary - const subTree = (suspense.subTree = suspense.oldSubTree = childrenToFragment( - n2 - )) - processFragment( + patch( null, - subTree as HostVNode, - contentContainer, + content, + suspense.container, null, parentComponent, isSVG, @@ -684,14 +669,19 @@ export function createRenderer< ) // now check if we have encountered any async deps if (suspense.deps > 0) { - // TODO mount the fallback tree. - processEmptyNode( + // mount the fallback tree + patch( null, - (suspense.fallbackTree = createVNode(Empty)), + fallback, container, - anchor + anchor, + parentComponent, + isSVG, + optimized ) + n2.el = fallback.el } else { + // Suspense has no async deps. Just resolve. suspense.resolve() } } else { @@ -700,34 +690,52 @@ export function createRenderer< HostElement > suspense.vnode = n2 + const { content, fallback } = normalizeSuspenseChildren(n2) const oldSubTree = (suspense.oldSubTree = suspense.subTree) - const newContentTree = (suspense.subTree = childrenToFragment(n2)) + suspense.subTree = content + const oldFallbackTree = (suspense.oldFallbackTree = suspense.fallbackTree) + suspense.fallbackTree = fallback if (!suspense.isResolved) { - suspense.retry() - } else { - // just normal patch inner content as a fragment - processFragment( + patch( oldSubTree, - newContentTree, - container, + content, + suspense.container, null, parentComponent, isSVG, optimized ) - n2.el = newContentTree.el + if (suspense.deps > 0) { + // still pending. patch the fallback tree. + patch( + oldFallbackTree, + fallback, + container, + anchor, + parentComponent, + isSVG, + optimized + ) + n2.el = fallback.el + } else { + suspense.resolve() + } + } else { + // just normal patch inner content as a fragment + patch( + oldSubTree, + content, + container, + anchor, + parentComponent, + isSVG, + optimized + ) + n2.el = content.el } } } - function childrenToFragment(vnode: HostVNode): HostVNode { - return vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN - ? createVNode(Fragment, null, vnode.children) - : vnode.shapeFlag & ShapeFlags.TEXT_CHILDREN - ? createVNode(Fragment, null, [vnode.children]) - : createVNode(Fragment, null, []) - } - function processComponent( n1: HostVNode | null, n2: HostVNode, diff --git a/packages/runtime-core/src/suspense.ts b/packages/runtime-core/src/suspense.ts index f5f2f84b..16571af4 100644 --- a/packages/runtime-core/src/suspense.ts +++ b/packages/runtime-core/src/suspense.ts @@ -1,4 +1,5 @@ -import { VNode } from './vnode' +import { VNode, normalizeVNode } from './vnode' +import { ShapeFlags } from '.' export const SuspenseSymbol = __DEV__ ? Symbol('Suspense key') : Symbol() @@ -9,6 +10,7 @@ export interface SuspenseBoundary< > { vnode: HostVNode parent: SuspenseBoundary | null + container: HostElement subTree: HostVNode | null oldSubTree: HostVNode | null fallbackTree: HostVNode | null @@ -16,19 +18,19 @@ export interface SuspenseBoundary< deps: number isResolved: boolean bufferedJobs: Function[] - retry(): void resolve(): void } export function createSuspenseBoundary( vnode: VNode, parent: SuspenseBoundary | null, - retry: () => void, + container: HostElement, resolve: () => void ): SuspenseBoundary { return { vnode, parent, + container, deps: 0, subTree: null, oldSubTree: null, @@ -36,7 +38,27 @@ export function createSuspenseBoundary( oldFallbackTree: null, isResolved: false, bufferedJobs: [], - retry, resolve } } + +export function normalizeSuspenseChildren( + vnode: VNode +): { + content: VNode + fallback: VNode +} { + const { shapeFlag } = vnode + const children = vnode.children as any + if (shapeFlag & ShapeFlags.SLOTS_CHILDREN) { + return { + content: normalizeVNode(children.default()), + fallback: normalizeVNode(children.fallback ? children.fallback() : null) + } + } else { + return { + content: normalizeVNode(children), + fallback: normalizeVNode(null) + } + } +}