From 9d2ac6675a4054313d1afc2b52ee8758ab9ccf67 Mon Sep 17 00:00:00 2001 From: Evan You Date: Sat, 15 Feb 2020 11:40:09 -0500 Subject: [PATCH] refactor: make portal tree-shakeable --- .../Portal.spec.ts} | 2 +- .../__snapshots__/Portal.spec.ts.snap} | 0 packages/runtime-core/src/apiCreateApp.ts | 21 +- .../runtime-core/src/components/KeepAlive.ts | 6 +- .../runtime-core/src/components/Portal.ts | 118 ++++++++ .../runtime-core/src/components/Suspense.ts | 18 +- packages/runtime-core/src/h.ts | 4 +- packages/runtime-core/src/hydration.ts | 26 +- packages/runtime-core/src/index.ts | 3 +- packages/runtime-core/src/renderer.ts | 278 ++++++++---------- packages/runtime-core/src/shapeFlags.ts | 12 - packages/runtime-core/src/vnode.ts | 32 +- .../server-renderer/src/renderToString.ts | 6 +- packages/shared/src/shapeFlags.ts | 7 +- 14 files changed, 293 insertions(+), 240 deletions(-) rename packages/runtime-core/__tests__/{rendererPortal.spec.ts => components/Portal.spec.ts} (97%) rename packages/runtime-core/__tests__/{__snapshots__/rendererPortal.spec.ts.snap => components/__snapshots__/Portal.spec.ts.snap} (100%) create mode 100644 packages/runtime-core/src/components/Portal.ts delete mode 100644 packages/runtime-core/src/shapeFlags.ts diff --git a/packages/runtime-core/__tests__/rendererPortal.spec.ts b/packages/runtime-core/__tests__/components/Portal.spec.ts similarity index 97% rename from packages/runtime-core/__tests__/rendererPortal.spec.ts rename to packages/runtime-core/__tests__/components/Portal.spec.ts index c7d600bfca..b71d5d6fba 100644 --- a/packages/runtime-core/__tests__/rendererPortal.spec.ts +++ b/packages/runtime-core/__tests__/components/Portal.spec.ts @@ -12,7 +12,7 @@ import { TestElement, TestNode } from '@vue/runtime-test' -import { VNodeArrayChildren } from '../src/vnode' +import { VNodeArrayChildren } from '../../src/vnode' describe('renderer: portal', () => { test('should work', () => { diff --git a/packages/runtime-core/__tests__/__snapshots__/rendererPortal.spec.ts.snap b/packages/runtime-core/__tests__/components/__snapshots__/Portal.spec.ts.snap similarity index 100% rename from packages/runtime-core/__tests__/__snapshots__/rendererPortal.spec.ts.snap rename to packages/runtime-core/__tests__/components/__snapshots__/Portal.spec.ts.snap diff --git a/packages/runtime-core/src/apiCreateApp.ts b/packages/runtime-core/src/apiCreateApp.ts index d093b787c8..e022d92f95 100644 --- a/packages/runtime-core/src/apiCreateApp.ts +++ b/packages/runtime-core/src/apiCreateApp.ts @@ -141,19 +141,18 @@ export function createAppAPI( }, mixin(mixin: ComponentOptions) { - if (__DEV__ && !__FEATURE_OPTIONS__) { - warn('Mixins are only available in builds supporting Options API') - } - - if (!context.mixins.includes(mixin)) { - context.mixins.push(mixin) + if (__FEATURE_OPTIONS__) { + if (!context.mixins.includes(mixin)) { + context.mixins.push(mixin) + } else if (__DEV__) { + warn( + 'Mixin has already been applied to target app' + + (mixin.name ? `: ${mixin.name}` : '') + ) + } } else if (__DEV__) { - warn( - 'Mixin has already been applied to target app' + - (mixin.name ? `: ${mixin.name}` : '') - ) + warn('Mixins are only available in builds supporting Options API') } - return app }, diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index 4125131d2d..78bb824f97 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -72,9 +72,9 @@ const KeepAliveImpl = { const sink = instance.sink as KeepAliveSink const { renderer: { - move, - unmount: _unmount, - options: { createElement } + m: move, + um: _unmount, + o: { createElement } }, parentSuspense } = sink diff --git a/packages/runtime-core/src/components/Portal.ts b/packages/runtime-core/src/components/Portal.ts new file mode 100644 index 0000000000..bbbaaad4c9 --- /dev/null +++ b/packages/runtime-core/src/components/Portal.ts @@ -0,0 +1,118 @@ +import { ComponentInternalInstance } from '../component' +import { SuspenseBoundary } from './Suspense' +import { RendererInternals, MoveType } from '../renderer' +import { VNode, VNodeArrayChildren } from '../vnode' +import { isString, ShapeFlags, PatchFlags } from '@vue/shared' +import { warn } from '../warning' + +export const isPortal = (type: any): boolean => type.__isPortal + +export interface PortalProps { + target: string | object +} + +export const PortalImpl = { + __isPortal: true, + process( + n1: VNode | null, + n2: VNode, + container: object, + anchor: object | null, + parentComponent: ComponentInternalInstance | null, + parentSuspense: SuspenseBoundary | null, + isSVG: boolean, + optimized: boolean, + { + mc: mountChildren, + pc: patchChildren, + pbc: patchBlockChildren, + m: move, + c: insertComment, + o: { querySelector, setElementText } + }: RendererInternals + ) { + const targetSelector = n2.props && n2.props.target + const { patchFlag, shapeFlag, children } = n2 + if (n1 == null) { + if (__DEV__ && isString(targetSelector) && !querySelector) { + warn( + `Current renderer does not support string target for Portals. ` + + `(missing querySelector renderer option)` + ) + } + const target = (n2.target = isString(targetSelector) + ? querySelector!(targetSelector) + : targetSelector) + if (target != null) { + if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { + setElementText(target, children as string) + } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { + mountChildren( + children as VNodeArrayChildren, + target, + null, + parentComponent, + parentSuspense, + isSVG, + optimized + ) + } + } else if (__DEV__) { + warn('Invalid Portal target on mount:', target, `(${typeof target})`) + } + } else { + // update content + const target = (n2.target = n1.target)! + if (patchFlag === PatchFlags.TEXT) { + setElementText(target, children as string) + } else if (n2.dynamicChildren) { + // fast path when the portal happens to be a block root + patchBlockChildren( + n1.dynamicChildren!, + n2.dynamicChildren, + container, + parentComponent, + parentSuspense, + isSVG + ) + } else if (!optimized) { + patchChildren( + n1, + n2, + target, + null, + parentComponent, + parentSuspense, + isSVG + ) + } + // target changed + if (targetSelector !== (n1.props && n1.props.target)) { + const nextTarget = (n2.target = isString(targetSelector) + ? querySelector!(targetSelector) + : targetSelector) + if (nextTarget != null) { + // move content + if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { + setElementText(target, '') + setElementText(nextTarget, children as string) + } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { + for (let i = 0; i < (children as VNode[]).length; i++) { + move((children as VNode[])[i], nextTarget, null, MoveType.REORDER) + } + } + } else if (__DEV__) { + warn('Invalid Portal target on update:', target, `(${typeof target})`) + } + } + } + // insert an empty node as the placeholder for the portal + insertComment(n1, n2, container, anchor) + } +} + +// Force-casted public typing for h and TSX props inference +export const Portal = (PortalImpl as any) as { + __isPortal: true + new (): { $props: PortalProps } +} diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts index c8f7d5abcb..56a4d5da38 100644 --- a/packages/runtime-core/src/components/Suspense.ts +++ b/packages/runtime-core/src/components/Suspense.ts @@ -13,6 +13,8 @@ export interface SuspenseProps { onRecede?: () => void } +export const isSuspense = (type: any): boolean => type.__isSuspense + // Suspense exposes a component-like API, and is treated like a component // in the compiler, but internally it's a special built-in type that hooks // directly into the renderer. @@ -79,8 +81,8 @@ function mountSuspense( rendererInternals: RendererInternals ) { const { - patch, - options: { createElement } + p: patch, + o: { createElement } } = rendererInternals const hiddenContainer = createElement('div') const suspense = (n2.suspense = createSuspenseBoundary( @@ -138,7 +140,7 @@ function patchSuspense( parentComponent: ComponentInternalInstance | null, isSVG: boolean, optimized: boolean, - { patch }: RendererInternals + { p: patch }: RendererInternals ) { const suspense = (n2.suspense = n1.suspense)! suspense.vnode = n2 @@ -236,11 +238,11 @@ function createSuspenseBoundary( rendererInternals: RendererInternals ): SuspenseBoundary { const { - patch, - move, - unmount, - next, - options: { parentNode } + p: patch, + m: move, + um: unmount, + n: next, + o: { parentNode } } = rendererInternals const suspense: SuspenseBoundary = { diff --git a/packages/runtime-core/src/h.ts b/packages/runtime-core/src/h.ts index fae16b8502..30cb7a90fc 100644 --- a/packages/runtime-core/src/h.ts +++ b/packages/runtime-core/src/h.ts @@ -4,9 +4,9 @@ import { createVNode, VNodeArrayChildren, Fragment, - Portal, isVNode } from './vnode' +import { Portal, PortalProps } from './components/Portal' import { Suspense, SuspenseProps } from './components/Suspense' import { isObject, isArray } from '@vue/shared' import { RawSlots } from './componentSlots' @@ -95,7 +95,7 @@ export function h( // portal (target prop is required) export function h( type: typeof Portal, - props: RawProps & { target: any }, + props: RawProps & PortalProps, children: RawChildren ): VNode diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index 2a9e0b1650..8861a19840 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -1,12 +1,4 @@ -import { - VNode, - normalizeVNode, - Text, - Comment, - Static, - Fragment, - Portal -} from './vnode' +import { VNode, normalizeVNode, Text, Comment, Static, Fragment } from './vnode' import { queuePostFlushCb, flushPostFlushCbs } from './scheduler' import { ComponentInternalInstance } from './component' import { invokeDirectiveHook } from './directives' @@ -18,7 +10,7 @@ import { isOn, isString } from '@vue/shared' -import { RendererOptions, MountComponentFn } from './renderer' +import { RendererInternals } from './renderer' export type RootHydrateFunction = ( vnode: VNode, @@ -30,10 +22,10 @@ export type RootHydrateFunction = ( // it out creates a ton of unnecessary complexity. // Hydration also depends on some renderer internal logic which needs to be // passed in via arguments. -export function createHydrationFunctions( - mountComponent: MountComponentFn, - patchProp: RendererOptions['patchProp'] -) { +export function createHydrationFunctions({ + mt: mountComponent, + o: { patchProp } +}: RendererInternals) { const hydrate: RootHydrateFunction = (vnode, container) => { if (__DEV__ && !container.hasChildNodes()) { warn(`Attempting to hydrate existing markup but container is empty.`) @@ -65,9 +57,6 @@ export function createHydrationFunctions( // TODO handle potential hydration error if fragment didn't get // back anchor as expected. return anchor.nextSibling - case Portal: - hydratePortal(vnode, parentComponent) - return node.nextSibling default: if (shapeFlag & ShapeFlags.ELEMENT) { return hydrateElement(node as Element, vnode, parentComponent) @@ -78,6 +67,9 @@ export function createHydrationFunctions( mountComponent(vnode, null, null, parentComponent, null, false) const subTree = vnode.component!.subTree return (subTree.anchor || subTree.el).nextSibling + } else if (shapeFlag & ShapeFlags.PORTAL) { + hydratePortal(vnode, parentComponent) + return node.nextSibling } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { // TODO Suspense } else if (__DEV__) { diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index f78c61a80f..7a4be08e42 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -49,7 +49,8 @@ export { createBlock } from './vnode' // Internal Components -export { Text, Comment, Fragment, Portal } from './vnode' +export { Text, Comment, Fragment } from './vnode' +export { Portal, PortalProps } from './components/Portal' export { Suspense, SuspenseProps } from './components/Suspense' export { KeepAlive, KeepAliveProps } from './components/KeepAlive' export { diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index dce719fa26..9d1c2a414a 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -2,7 +2,6 @@ import { Text, Fragment, Comment, - Portal, cloneIfMounted, normalizeVNode, VNode, @@ -59,9 +58,10 @@ import { queueEffectWithSuspense, SuspenseImpl } from './components/Suspense' -import { ErrorCodes, callWithErrorHandling } from './errorHandling' +import { PortalImpl } from './components/Portal' import { KeepAliveSink, isKeepAlive } from './components/KeepAlive' import { registerHMR, unregisterHMR } from './hmr' +import { ErrorCodes, callWithErrorHandling } from './errorHandling' import { createHydrationFunctions, RootHydrateFunction } from './hydration' const __HMR__ = __BUNDLER__ && __DEV__ @@ -113,13 +113,19 @@ export interface RendererOptions { } // An object exposing the internals of a renderer, passed to tree-shakeable -// features so that they can be decoupled from this file. +// features so that they can be decoupled from this file. Keys are shortened +// to optimize bundle size. export interface RendererInternals { - patch: PatchFn - unmount: UnmountFn - move: MoveFn - next: NextFn - options: RendererOptions + p: PatchFn + um: UnmountFn + m: MoveFn + mt: MountComponentFn + mc: MountChildrenFn + pc: PatchChildrenFn + pbc: PatchBlockChildrenFn + n: NextFn + o: RendererOptions + c: ProcessTextOrCommentFn } // These functions are created inside a closure and therefore there types cannot @@ -136,11 +142,35 @@ type PatchFn = ( optimized?: boolean ) => void -type UnmountFn = ( - vnode: VNode, +type MountChildrenFn = ( + children: VNodeArrayChildren, + container: HostElement, + anchor: HostNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, - doRemove?: boolean + isSVG: boolean, + optimized: boolean, + start?: number +) => void + +type PatchChildrenFn = ( + n1: VNode | null, + n2: VNode, + container: HostElement, + anchor: HostNode | null, + parentComponent: ComponentInternalInstance | null, + parentSuspense: SuspenseBoundary | null, + isSVG: boolean, + optimized?: boolean +) => void + +type PatchBlockChildrenFn = ( + oldChildren: VNode[], + newChildren: VNode[], + fallbackContainer: HostElement, + parentComponent: ComponentInternalInstance | null, + parentSuspense: SuspenseBoundary | null, + isSVG: boolean ) => void type MoveFn = ( @@ -155,6 +185,13 @@ type NextFn = ( vnode: VNode ) => HostNode | null +type UnmountFn = ( + vnode: VNode, + parentComponent: ComponentInternalInstance | null, + parentSuspense: SuspenseBoundary | null, + doRemove?: boolean +) => void + type UnmountChildrenFn = ( children: VNode[], parentComponent: ComponentInternalInstance | null, @@ -172,6 +209,13 @@ export type MountComponentFn = ( isSVG: boolean ) => void +type ProcessTextOrCommentFn = ( + n1: VNode | null, + n2: VNode, + container: HostElement, + anchor: HostNode | null +) => void + export type SetupRenderEffectFn = ( instance: ComponentInternalInstance, initialVNode: VNode, @@ -282,7 +326,6 @@ function baseCreateRenderer< setElementText: hostSetElementText, parentNode: hostParentNode, nextSibling: hostNextSibling, - querySelector: hostQuerySelector, setScopeId: hostSetScopeId = NOOP, cloneNode: hostCloneNode, insertStaticContent: hostInsertStaticContent @@ -332,18 +375,6 @@ function baseCreateRenderer< optimized ) break - case Portal: - processPortal( - n1, - n2, - container, - anchor, - parentComponent, - parentSuspense, - isSVG, - optimized - ) - break default: if (shapeFlag & ShapeFlags.ELEMENT) { processElement( @@ -367,6 +398,18 @@ function baseCreateRenderer< isSVG, optimized ) + } else if (shapeFlag & ShapeFlags.PORTAL) { + ;(type as typeof PortalImpl).process( + n1, + n2, + container, + anchor, + parentComponent, + parentSuspense, + isSVG, + optimized, + internals + ) } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { ;(type as typeof SuspenseImpl).process( n1, @@ -385,11 +428,11 @@ function baseCreateRenderer< } } - const processText = ( - n1: HostVNode | null, - n2: HostVNode, - container: HostElement, - anchor: HostNode | null + const processText: ProcessTextOrCommentFn = ( + n1, + n2, + container, + anchor ) => { if (n1 == null) { hostInsert( @@ -405,11 +448,11 @@ function baseCreateRenderer< } } - const processCommentNode = ( - n1: HostVNode | null, - n2: HostVNode, - container: HostElement, - anchor: HostNode | null + const processCommentNode: ProcessTextOrCommentFn = ( + n1, + n2, + container, + anchor ) => { if (n1 == null) { hostInsert( @@ -552,15 +595,15 @@ function baseCreateRenderer< } } - const mountChildren = ( - children: HostVNodeChildren, - container: HostElement, - anchor: HostNode | null, - parentComponent: ComponentInternalInstance | null, - parentSuspense: HostSuspenseBoundary | null, - isSVG: boolean, - optimized: boolean, - start: number = 0 + const mountChildren: MountChildrenFn = ( + children, + container, + anchor, + parentComponent, + parentSuspense, + isSVG, + optimized, + start = 0 ) => { for (let i = start; i < children.length; i++) { const child = (children[i] = optimized @@ -716,13 +759,13 @@ function baseCreateRenderer< } // The fast path for blocks. - const patchBlockChildren = ( - oldChildren: HostVNode[], - newChildren: HostVNode[], - fallbackContainer: HostElement, - parentComponent: ComponentInternalInstance | null, - parentSuspense: HostSuspenseBoundary | null, - isSVG: boolean + const patchBlockChildren: PatchBlockChildrenFn = ( + oldChildren, + newChildren, + fallbackContainer, + parentComponent, + parentSuspense, + isSVG ) => { for (let i = 0; i < newChildren.length; i++) { const oldVNode = oldChildren[i] @@ -883,100 +926,6 @@ function baseCreateRenderer< } } - const processPortal = ( - n1: HostVNode | null, - n2: HostVNode, - container: HostElement, - anchor: HostNode | null, - parentComponent: ComponentInternalInstance | null, - parentSuspense: HostSuspenseBoundary | null, - isSVG: boolean, - optimized: boolean - ) => { - const targetSelector = n2.props && n2.props.target - const { patchFlag, shapeFlag, children } = n2 - if (n1 == null) { - if (__DEV__ && isString(targetSelector) && !hostQuerySelector) { - warn( - `Current renderer does not support string target for Portals. ` + - `(missing querySelector renderer option)` - ) - } - const target = (n2.target = isString(targetSelector) - ? hostQuerySelector!(targetSelector) - : targetSelector) - if (target != null) { - if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { - hostSetElementText(target, children as string) - } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { - mountChildren( - children as HostVNodeChildren, - target, - null, - parentComponent, - parentSuspense, - isSVG, - optimized - ) - } - } else if (__DEV__) { - warn('Invalid Portal target on mount:', target, `(${typeof target})`) - } - } else { - // update content - const target = (n2.target = n1.target)! - if (patchFlag === PatchFlags.TEXT) { - hostSetElementText(target, children as string) - } else if (n2.dynamicChildren) { - // fast path when the portal happens to be a block root - patchBlockChildren( - n1.dynamicChildren!, - n2.dynamicChildren, - container, - parentComponent, - parentSuspense, - isSVG - ) - } else if (!optimized) { - patchChildren( - n1, - n2, - target, - null, - parentComponent, - parentSuspense, - isSVG - ) - } - // target changed - if (targetSelector !== (n1.props && n1.props.target)) { - const nextTarget = (n2.target = isString(targetSelector) - ? hostQuerySelector!(targetSelector) - : targetSelector) - if (nextTarget != null) { - // move content - if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { - hostSetElementText(target, '') - hostSetElementText(nextTarget, children as string) - } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { - for (let i = 0; i < (children as HostVNode[]).length; i++) { - move( - (children as HostVNode[])[i], - nextTarget, - null, - MoveType.REORDER - ) - } - } - } else if (__DEV__) { - warn('Invalid Portal target on update:', target, `(${typeof target})`) - } - } - } - // insert an empty node as the placeholder for the portal - processCommentNode(n1, n2, container, anchor) - } - const processComponent = ( n1: HostVNode | null, n2: HostVNode, @@ -1222,15 +1171,15 @@ function baseCreateRenderer< resolveSlots(instance, nextVNode.children) } - const patchChildren = ( - n1: HostVNode | null, - n2: HostVNode, - container: HostElement, - anchor: HostNode | null, - parentComponent: ComponentInternalInstance | null, - parentSuspense: HostSuspenseBoundary | null, - isSVG: boolean, - optimized: boolean = false + const patchChildren: PatchChildrenFn = ( + n1, + n2, + container, + anchor, + parentComponent, + parentSuspense, + isSVG, + optimized = false ) => { const c1 = n1 && n1.children const prevShapeFlag = n1 ? n1.shapeFlag : 0 @@ -1899,20 +1848,25 @@ function baseCreateRenderer< } const internals: RendererInternals = { - patch, - unmount, - move, - next: getNextHostNode, - options + p: patch, + um: unmount, + m: move, + mt: mountComponent, + mc: mountChildren, + pc: patchChildren, + pbc: patchBlockChildren, + n: getNextHostNode, + c: processCommentNode, + o: options } let hydrate: ReturnType[0] | undefined let hydrateNode: ReturnType[1] | undefined if (createHydrationFns) { - ;[hydrate, hydrateNode] = createHydrationFns( - mountComponent as MountComponentFn, - hostPatchProp - ) + ;[hydrate, hydrateNode] = createHydrationFns(internals as RendererInternals< + Node, + Element + >) } return { diff --git a/packages/runtime-core/src/shapeFlags.ts b/packages/runtime-core/src/shapeFlags.ts deleted file mode 100644 index c5d4f6f835..0000000000 --- a/packages/runtime-core/src/shapeFlags.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const enum ShapeFlags { - ELEMENT = 1, - FUNCTIONAL_COMPONENT = 1 << 1, - STATEFUL_COMPONENT = 1 << 2, - TEXT_CHILDREN = 1 << 3, - ARRAY_CHILDREN = 1 << 4, - SLOTS_CHILDREN = 1 << 5, - SUSPENSE = 1 << 6, - COMPONENT_SHOULD_KEEP_ALIVE = 1 << 7, - COMPONENT_KEPT_ALIVE = 1 << 8, - COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT -} diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 440eb270f1..7b415ddbc6 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -19,12 +19,16 @@ import { import { RawSlots } from './componentSlots' import { isReactive, Ref } from '@vue/reactivity' import { AppContext } from './apiCreateApp' -import { SuspenseBoundary } from './components/Suspense' +import { + SuspenseImpl, + isSuspense, + SuspenseBoundary +} from './components/Suspense' import { DirectiveBinding } from './directives' -import { SuspenseImpl } from './components/Suspense' import { TransitionHooks } from './components/BaseTransition' import { warn } from './warning' import { currentScopeId } from './helpers/scopeId' +import { PortalImpl, isPortal } from './components/Portal' export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as { __isFragment: true @@ -32,12 +36,6 @@ export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as { $props: VNodeProps } } -export const Portal = (Symbol(__DEV__ ? 'Portal' : undefined) as any) as { - __isPortal: true - new (): { - $props: VNodeProps & { target: string | object } - } -} export const Text = Symbol(__DEV__ ? 'Text' : undefined) export const Comment = Symbol(__DEV__ ? 'Comment' : undefined) export const Static = Symbol(__DEV__ ? 'Static' : undefined) @@ -45,11 +43,11 @@ export const Static = Symbol(__DEV__ ? 'Static' : undefined) export type VNodeTypes = | string | Component - | typeof Fragment - | typeof Portal | typeof Text | typeof Static | typeof Comment + | typeof Fragment + | typeof PortalImpl | typeof SuspenseImpl export interface VNodeProps { @@ -239,13 +237,15 @@ export function createVNode( // encode the vnode type information into a bitmap const shapeFlag = isString(type) ? ShapeFlags.ELEMENT - : __FEATURE_SUSPENSE__ && (type as any).__isSuspense === true + : __FEATURE_SUSPENSE__ && isSuspense(type) ? ShapeFlags.SUSPENSE - : isObject(type) - ? ShapeFlags.STATEFUL_COMPONENT - : isFunction(type) - ? ShapeFlags.FUNCTIONAL_COMPONENT - : 0 + : isPortal(type) + ? ShapeFlags.PORTAL + : isObject(type) + ? ShapeFlags.STATEFUL_COMPONENT + : isFunction(type) + ? ShapeFlags.FUNCTIONAL_COMPONENT + : 0 const vnode: VNode = { _isVNode: true, diff --git a/packages/server-renderer/src/renderToString.ts b/packages/server-renderer/src/renderToString.ts index b7bd38d313..3e6d8056bd 100644 --- a/packages/server-renderer/src/renderToString.ts +++ b/packages/server-renderer/src/renderToString.ts @@ -8,7 +8,6 @@ import { Text, Comment, Fragment, - Portal, ssrUtils, Slots, warn, @@ -251,14 +250,13 @@ function renderVNode( renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent) push(``) break - case Portal: - renderPortal(vnode, parentComponent) - break default: if (shapeFlag & ShapeFlags.ELEMENT) { renderElement(push, vnode, parentComponent) } else if (shapeFlag & ShapeFlags.COMPONENT) { push(renderComponentVNode(vnode, parentComponent)) + } else if (shapeFlag & ShapeFlags.PORTAL) { + renderPortal(vnode, parentComponent) } else if (shapeFlag & ShapeFlags.SUSPENSE) { // TODO } else { diff --git a/packages/shared/src/shapeFlags.ts b/packages/shared/src/shapeFlags.ts index c5d4f6f835..93c5f78234 100644 --- a/packages/shared/src/shapeFlags.ts +++ b/packages/shared/src/shapeFlags.ts @@ -5,8 +5,9 @@ export const enum ShapeFlags { TEXT_CHILDREN = 1 << 3, ARRAY_CHILDREN = 1 << 4, SLOTS_CHILDREN = 1 << 5, - SUSPENSE = 1 << 6, - COMPONENT_SHOULD_KEEP_ALIVE = 1 << 7, - COMPONENT_KEPT_ALIVE = 1 << 8, + PORTAL = 1 << 6, + SUSPENSE = 1 << 7, + COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8, + COMPONENT_KEPT_ALIVE = 1 << 9, COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT } -- 2.47.3