refactor: make portal tree-shakeable

This commit is contained in:
Evan You 2020-02-15 11:40:09 -05:00
parent 0fc720c34a
commit 9d2ac6675a
14 changed files with 293 additions and 240 deletions

View File

@ -12,7 +12,7 @@ import {
TestElement, TestElement,
TestNode TestNode
} from '@vue/runtime-test' } from '@vue/runtime-test'
import { VNodeArrayChildren } from '../src/vnode' import { VNodeArrayChildren } from '../../src/vnode'
describe('renderer: portal', () => { describe('renderer: portal', () => {
test('should work', () => { test('should work', () => {

View File

@ -141,19 +141,18 @@ export function createAppAPI<HostNode, HostElement>(
}, },
mixin(mixin: ComponentOptions) { mixin(mixin: ComponentOptions) {
if (__DEV__ && !__FEATURE_OPTIONS__) { 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('Mixins are only available in builds supporting Options API') warn('Mixins are only available in builds supporting Options API')
} }
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}` : '')
)
}
return app return app
}, },

View File

@ -72,9 +72,9 @@ const KeepAliveImpl = {
const sink = instance.sink as KeepAliveSink const sink = instance.sink as KeepAliveSink
const { const {
renderer: { renderer: {
move, m: move,
unmount: _unmount, um: _unmount,
options: { createElement } o: { createElement }
}, },
parentSuspense parentSuspense
} = sink } = sink

View File

@ -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 }
}

View File

@ -13,6 +13,8 @@ export interface SuspenseProps {
onRecede?: () => void onRecede?: () => void
} }
export const isSuspense = (type: any): boolean => type.__isSuspense
// Suspense exposes a component-like API, and is treated like a component // 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 // in the compiler, but internally it's a special built-in type that hooks
// directly into the renderer. // directly into the renderer.
@ -79,8 +81,8 @@ function mountSuspense(
rendererInternals: RendererInternals rendererInternals: RendererInternals
) { ) {
const { const {
patch, p: patch,
options: { createElement } o: { createElement }
} = rendererInternals } = rendererInternals
const hiddenContainer = createElement('div') const hiddenContainer = createElement('div')
const suspense = (n2.suspense = createSuspenseBoundary( const suspense = (n2.suspense = createSuspenseBoundary(
@ -138,7 +140,7 @@ function patchSuspense(
parentComponent: ComponentInternalInstance | null, parentComponent: ComponentInternalInstance | null,
isSVG: boolean, isSVG: boolean,
optimized: boolean, optimized: boolean,
{ patch }: RendererInternals { p: patch }: RendererInternals
) { ) {
const suspense = (n2.suspense = n1.suspense)! const suspense = (n2.suspense = n1.suspense)!
suspense.vnode = n2 suspense.vnode = n2
@ -236,11 +238,11 @@ function createSuspenseBoundary<HostNode, HostElement>(
rendererInternals: RendererInternals<HostNode, HostElement> rendererInternals: RendererInternals<HostNode, HostElement>
): SuspenseBoundary<HostNode, HostElement> { ): SuspenseBoundary<HostNode, HostElement> {
const { const {
patch, p: patch,
move, m: move,
unmount, um: unmount,
next, n: next,
options: { parentNode } o: { parentNode }
} = rendererInternals } = rendererInternals
const suspense: SuspenseBoundary<HostNode, HostElement> = { const suspense: SuspenseBoundary<HostNode, HostElement> = {

View File

@ -4,9 +4,9 @@ import {
createVNode, createVNode,
VNodeArrayChildren, VNodeArrayChildren,
Fragment, Fragment,
Portal,
isVNode isVNode
} from './vnode' } from './vnode'
import { Portal, PortalProps } from './components/Portal'
import { Suspense, SuspenseProps } from './components/Suspense' import { Suspense, SuspenseProps } from './components/Suspense'
import { isObject, isArray } from '@vue/shared' import { isObject, isArray } from '@vue/shared'
import { RawSlots } from './componentSlots' import { RawSlots } from './componentSlots'
@ -95,7 +95,7 @@ export function h(
// portal (target prop is required) // portal (target prop is required)
export function h( export function h(
type: typeof Portal, type: typeof Portal,
props: RawProps & { target: any }, props: RawProps & PortalProps,
children: RawChildren children: RawChildren
): VNode ): VNode

View File

@ -1,12 +1,4 @@
import { import { VNode, normalizeVNode, Text, Comment, Static, Fragment } from './vnode'
VNode,
normalizeVNode,
Text,
Comment,
Static,
Fragment,
Portal
} from './vnode'
import { queuePostFlushCb, flushPostFlushCbs } from './scheduler' import { queuePostFlushCb, flushPostFlushCbs } from './scheduler'
import { ComponentInternalInstance } from './component' import { ComponentInternalInstance } from './component'
import { invokeDirectiveHook } from './directives' import { invokeDirectiveHook } from './directives'
@ -18,7 +10,7 @@ import {
isOn, isOn,
isString isString
} from '@vue/shared' } from '@vue/shared'
import { RendererOptions, MountComponentFn } from './renderer' import { RendererInternals } from './renderer'
export type RootHydrateFunction = ( export type RootHydrateFunction = (
vnode: VNode<Node, Element>, vnode: VNode<Node, Element>,
@ -30,10 +22,10 @@ export type RootHydrateFunction = (
// it out creates a ton of unnecessary complexity. // it out creates a ton of unnecessary complexity.
// Hydration also depends on some renderer internal logic which needs to be // Hydration also depends on some renderer internal logic which needs to be
// passed in via arguments. // passed in via arguments.
export function createHydrationFunctions( export function createHydrationFunctions({
mountComponent: MountComponentFn<Node, Element>, mt: mountComponent,
patchProp: RendererOptions['patchProp'] o: { patchProp }
) { }: RendererInternals<Node, Element>) {
const hydrate: RootHydrateFunction = (vnode, container) => { const hydrate: RootHydrateFunction = (vnode, container) => {
if (__DEV__ && !container.hasChildNodes()) { if (__DEV__ && !container.hasChildNodes()) {
warn(`Attempting to hydrate existing markup but container is empty.`) 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 // TODO handle potential hydration error if fragment didn't get
// back anchor as expected. // back anchor as expected.
return anchor.nextSibling return anchor.nextSibling
case Portal:
hydratePortal(vnode, parentComponent)
return node.nextSibling
default: default:
if (shapeFlag & ShapeFlags.ELEMENT) { if (shapeFlag & ShapeFlags.ELEMENT) {
return hydrateElement(node as Element, vnode, parentComponent) return hydrateElement(node as Element, vnode, parentComponent)
@ -78,6 +67,9 @@ export function createHydrationFunctions(
mountComponent(vnode, null, null, parentComponent, null, false) mountComponent(vnode, null, null, parentComponent, null, false)
const subTree = vnode.component!.subTree const subTree = vnode.component!.subTree
return (subTree.anchor || subTree.el).nextSibling return (subTree.anchor || subTree.el).nextSibling
} else if (shapeFlag & ShapeFlags.PORTAL) {
hydratePortal(vnode, parentComponent)
return node.nextSibling
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
// TODO Suspense // TODO Suspense
} else if (__DEV__) { } else if (__DEV__) {

View File

@ -49,7 +49,8 @@ export {
createBlock createBlock
} from './vnode' } from './vnode'
// Internal Components // 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 { Suspense, SuspenseProps } from './components/Suspense'
export { KeepAlive, KeepAliveProps } from './components/KeepAlive' export { KeepAlive, KeepAliveProps } from './components/KeepAlive'
export { export {

View File

@ -2,7 +2,6 @@ import {
Text, Text,
Fragment, Fragment,
Comment, Comment,
Portal,
cloneIfMounted, cloneIfMounted,
normalizeVNode, normalizeVNode,
VNode, VNode,
@ -59,9 +58,10 @@ import {
queueEffectWithSuspense, queueEffectWithSuspense,
SuspenseImpl SuspenseImpl
} from './components/Suspense' } from './components/Suspense'
import { ErrorCodes, callWithErrorHandling } from './errorHandling' import { PortalImpl } from './components/Portal'
import { KeepAliveSink, isKeepAlive } from './components/KeepAlive' import { KeepAliveSink, isKeepAlive } from './components/KeepAlive'
import { registerHMR, unregisterHMR } from './hmr' import { registerHMR, unregisterHMR } from './hmr'
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
import { createHydrationFunctions, RootHydrateFunction } from './hydration' import { createHydrationFunctions, RootHydrateFunction } from './hydration'
const __HMR__ = __BUNDLER__ && __DEV__ const __HMR__ = __BUNDLER__ && __DEV__
@ -113,13 +113,19 @@ export interface RendererOptions<HostNode = any, HostElement = any> {
} }
// An object exposing the internals of a renderer, passed to tree-shakeable // 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<HostNode = any, HostElement = any> { export interface RendererInternals<HostNode = any, HostElement = any> {
patch: PatchFn<HostNode, HostElement> p: PatchFn<HostNode, HostElement>
unmount: UnmountFn<HostNode, HostElement> um: UnmountFn<HostNode, HostElement>
move: MoveFn<HostNode, HostElement> m: MoveFn<HostNode, HostElement>
next: NextFn<HostNode, HostElement> mt: MountComponentFn<HostNode, HostElement>
options: RendererOptions<HostNode, HostElement> mc: MountChildrenFn<HostNode, HostElement>
pc: PatchChildrenFn<HostNode, HostElement>
pbc: PatchBlockChildrenFn<HostNode, HostElement>
n: NextFn<HostNode, HostElement>
o: RendererOptions<HostNode, HostElement>
c: ProcessTextOrCommentFn<HostNode, HostElement>
} }
// These functions are created inside a closure and therefore there types cannot // These functions are created inside a closure and therefore there types cannot
@ -136,11 +142,35 @@ type PatchFn<HostNode, HostElement> = (
optimized?: boolean optimized?: boolean
) => void ) => void
type UnmountFn<HostNode, HostElement> = ( type MountChildrenFn<HostNode, HostElement> = (
vnode: VNode<HostNode, HostElement>, children: VNodeArrayChildren<HostNode, HostElement>,
container: HostElement,
anchor: HostNode | null,
parentComponent: ComponentInternalInstance | null, parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null, parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
doRemove?: boolean isSVG: boolean,
optimized: boolean,
start?: number
) => void
type PatchChildrenFn<HostNode, HostElement> = (
n1: VNode<HostNode, HostElement> | null,
n2: VNode<HostNode, HostElement>,
container: HostElement,
anchor: HostNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
isSVG: boolean,
optimized?: boolean
) => void
type PatchBlockChildrenFn<HostNode, HostElement> = (
oldChildren: VNode<HostNode, HostElement>[],
newChildren: VNode<HostNode, HostElement>[],
fallbackContainer: HostElement,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
isSVG: boolean
) => void ) => void
type MoveFn<HostNode, HostElement> = ( type MoveFn<HostNode, HostElement> = (
@ -155,6 +185,13 @@ type NextFn<HostNode, HostElement> = (
vnode: VNode<HostNode, HostElement> vnode: VNode<HostNode, HostElement>
) => HostNode | null ) => HostNode | null
type UnmountFn<HostNode, HostElement> = (
vnode: VNode<HostNode, HostElement>,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
doRemove?: boolean
) => void
type UnmountChildrenFn<HostNode, HostElement> = ( type UnmountChildrenFn<HostNode, HostElement> = (
children: VNode<HostNode, HostElement>[], children: VNode<HostNode, HostElement>[],
parentComponent: ComponentInternalInstance | null, parentComponent: ComponentInternalInstance | null,
@ -172,6 +209,13 @@ export type MountComponentFn<HostNode, HostElement> = (
isSVG: boolean isSVG: boolean
) => void ) => void
type ProcessTextOrCommentFn<HostNode, HostElement> = (
n1: VNode<HostNode, HostElement> | null,
n2: VNode<HostNode, HostElement>,
container: HostElement,
anchor: HostNode | null
) => void
export type SetupRenderEffectFn<HostNode, HostElement> = ( export type SetupRenderEffectFn<HostNode, HostElement> = (
instance: ComponentInternalInstance, instance: ComponentInternalInstance,
initialVNode: VNode<HostNode, HostElement>, initialVNode: VNode<HostNode, HostElement>,
@ -282,7 +326,6 @@ function baseCreateRenderer<
setElementText: hostSetElementText, setElementText: hostSetElementText,
parentNode: hostParentNode, parentNode: hostParentNode,
nextSibling: hostNextSibling, nextSibling: hostNextSibling,
querySelector: hostQuerySelector,
setScopeId: hostSetScopeId = NOOP, setScopeId: hostSetScopeId = NOOP,
cloneNode: hostCloneNode, cloneNode: hostCloneNode,
insertStaticContent: hostInsertStaticContent insertStaticContent: hostInsertStaticContent
@ -332,18 +375,6 @@ function baseCreateRenderer<
optimized optimized
) )
break break
case Portal:
processPortal(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
break
default: default:
if (shapeFlag & ShapeFlags.ELEMENT) { if (shapeFlag & ShapeFlags.ELEMENT) {
processElement( processElement(
@ -367,6 +398,18 @@ function baseCreateRenderer<
isSVG, isSVG,
optimized 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) { } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
;(type as typeof SuspenseImpl).process( ;(type as typeof SuspenseImpl).process(
n1, n1,
@ -385,11 +428,11 @@ function baseCreateRenderer<
} }
} }
const processText = ( const processText: ProcessTextOrCommentFn<HostNode, HostElement> = (
n1: HostVNode | null, n1,
n2: HostVNode, n2,
container: HostElement, container,
anchor: HostNode | null anchor
) => { ) => {
if (n1 == null) { if (n1 == null) {
hostInsert( hostInsert(
@ -405,11 +448,11 @@ function baseCreateRenderer<
} }
} }
const processCommentNode = ( const processCommentNode: ProcessTextOrCommentFn<HostNode, HostElement> = (
n1: HostVNode | null, n1,
n2: HostVNode, n2,
container: HostElement, container,
anchor: HostNode | null anchor
) => { ) => {
if (n1 == null) { if (n1 == null) {
hostInsert( hostInsert(
@ -552,15 +595,15 @@ function baseCreateRenderer<
} }
} }
const mountChildren = ( const mountChildren: MountChildrenFn<HostNode, HostElement> = (
children: HostVNodeChildren, children,
container: HostElement, container,
anchor: HostNode | null, anchor,
parentComponent: ComponentInternalInstance | null, parentComponent,
parentSuspense: HostSuspenseBoundary | null, parentSuspense,
isSVG: boolean, isSVG,
optimized: boolean, optimized,
start: number = 0 start = 0
) => { ) => {
for (let i = start; i < children.length; i++) { for (let i = start; i < children.length; i++) {
const child = (children[i] = optimized const child = (children[i] = optimized
@ -716,13 +759,13 @@ function baseCreateRenderer<
} }
// The fast path for blocks. // The fast path for blocks.
const patchBlockChildren = ( const patchBlockChildren: PatchBlockChildrenFn<HostNode, HostElement> = (
oldChildren: HostVNode[], oldChildren,
newChildren: HostVNode[], newChildren,
fallbackContainer: HostElement, fallbackContainer,
parentComponent: ComponentInternalInstance | null, parentComponent,
parentSuspense: HostSuspenseBoundary | null, parentSuspense,
isSVG: boolean isSVG
) => { ) => {
for (let i = 0; i < newChildren.length; i++) { for (let i = 0; i < newChildren.length; i++) {
const oldVNode = oldChildren[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 = ( const processComponent = (
n1: HostVNode | null, n1: HostVNode | null,
n2: HostVNode, n2: HostVNode,
@ -1222,15 +1171,15 @@ function baseCreateRenderer<
resolveSlots(instance, nextVNode.children) resolveSlots(instance, nextVNode.children)
} }
const patchChildren = ( const patchChildren: PatchChildrenFn<HostNode, HostElement> = (
n1: HostVNode | null, n1,
n2: HostVNode, n2,
container: HostElement, container,
anchor: HostNode | null, anchor,
parentComponent: ComponentInternalInstance | null, parentComponent,
parentSuspense: HostSuspenseBoundary | null, parentSuspense,
isSVG: boolean, isSVG,
optimized: boolean = false optimized = false
) => { ) => {
const c1 = n1 && n1.children const c1 = n1 && n1.children
const prevShapeFlag = n1 ? n1.shapeFlag : 0 const prevShapeFlag = n1 ? n1.shapeFlag : 0
@ -1899,20 +1848,25 @@ function baseCreateRenderer<
} }
const internals: RendererInternals<HostNode, HostElement> = { const internals: RendererInternals<HostNode, HostElement> = {
patch, p: patch,
unmount, um: unmount,
move, m: move,
next: getNextHostNode, mt: mountComponent,
options mc: mountChildren,
pc: patchChildren,
pbc: patchBlockChildren,
n: getNextHostNode,
c: processCommentNode,
o: options
} }
let hydrate: ReturnType<typeof createHydrationFunctions>[0] | undefined let hydrate: ReturnType<typeof createHydrationFunctions>[0] | undefined
let hydrateNode: ReturnType<typeof createHydrationFunctions>[1] | undefined let hydrateNode: ReturnType<typeof createHydrationFunctions>[1] | undefined
if (createHydrationFns) { if (createHydrationFns) {
;[hydrate, hydrateNode] = createHydrationFns( ;[hydrate, hydrateNode] = createHydrationFns(internals as RendererInternals<
mountComponent as MountComponentFn<Node, Element>, Node,
hostPatchProp Element
) >)
} }
return { return {

View File

@ -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
}

View File

@ -19,12 +19,16 @@ import {
import { RawSlots } from './componentSlots' import { RawSlots } from './componentSlots'
import { isReactive, Ref } from '@vue/reactivity' import { isReactive, Ref } from '@vue/reactivity'
import { AppContext } from './apiCreateApp' import { AppContext } from './apiCreateApp'
import { SuspenseBoundary } from './components/Suspense' import {
SuspenseImpl,
isSuspense,
SuspenseBoundary
} from './components/Suspense'
import { DirectiveBinding } from './directives' import { DirectiveBinding } from './directives'
import { SuspenseImpl } from './components/Suspense'
import { TransitionHooks } from './components/BaseTransition' import { TransitionHooks } from './components/BaseTransition'
import { warn } from './warning' import { warn } from './warning'
import { currentScopeId } from './helpers/scopeId' import { currentScopeId } from './helpers/scopeId'
import { PortalImpl, isPortal } from './components/Portal'
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as { export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
__isFragment: true __isFragment: true
@ -32,12 +36,6 @@ export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
$props: VNodeProps $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 Text = Symbol(__DEV__ ? 'Text' : undefined)
export const Comment = Symbol(__DEV__ ? 'Comment' : undefined) export const Comment = Symbol(__DEV__ ? 'Comment' : undefined)
export const Static = Symbol(__DEV__ ? 'Static' : undefined) export const Static = Symbol(__DEV__ ? 'Static' : undefined)
@ -45,11 +43,11 @@ export const Static = Symbol(__DEV__ ? 'Static' : undefined)
export type VNodeTypes = export type VNodeTypes =
| string | string
| Component | Component
| typeof Fragment
| typeof Portal
| typeof Text | typeof Text
| typeof Static | typeof Static
| typeof Comment | typeof Comment
| typeof Fragment
| typeof PortalImpl
| typeof SuspenseImpl | typeof SuspenseImpl
export interface VNodeProps { export interface VNodeProps {
@ -239,13 +237,15 @@ export function createVNode(
// encode the vnode type information into a bitmap // encode the vnode type information into a bitmap
const shapeFlag = isString(type) const shapeFlag = isString(type)
? ShapeFlags.ELEMENT ? ShapeFlags.ELEMENT
: __FEATURE_SUSPENSE__ && (type as any).__isSuspense === true : __FEATURE_SUSPENSE__ && isSuspense(type)
? ShapeFlags.SUSPENSE ? ShapeFlags.SUSPENSE
: isObject(type) : isPortal(type)
? ShapeFlags.STATEFUL_COMPONENT ? ShapeFlags.PORTAL
: isFunction(type) : isObject(type)
? ShapeFlags.FUNCTIONAL_COMPONENT ? ShapeFlags.STATEFUL_COMPONENT
: 0 : isFunction(type)
? ShapeFlags.FUNCTIONAL_COMPONENT
: 0
const vnode: VNode = { const vnode: VNode = {
_isVNode: true, _isVNode: true,

View File

@ -8,7 +8,6 @@ import {
Text, Text,
Comment, Comment,
Fragment, Fragment,
Portal,
ssrUtils, ssrUtils,
Slots, Slots,
warn, warn,
@ -251,14 +250,13 @@ function renderVNode(
renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent) renderVNodeChildren(push, children as VNodeArrayChildren, parentComponent)
push(`<!---->`) push(`<!---->`)
break break
case Portal:
renderPortal(vnode, parentComponent)
break
default: default:
if (shapeFlag & ShapeFlags.ELEMENT) { if (shapeFlag & ShapeFlags.ELEMENT) {
renderElement(push, vnode, parentComponent) renderElement(push, vnode, parentComponent)
} else if (shapeFlag & ShapeFlags.COMPONENT) { } else if (shapeFlag & ShapeFlags.COMPONENT) {
push(renderComponentVNode(vnode, parentComponent)) push(renderComponentVNode(vnode, parentComponent))
} else if (shapeFlag & ShapeFlags.PORTAL) {
renderPortal(vnode, parentComponent)
} else if (shapeFlag & ShapeFlags.SUSPENSE) { } else if (shapeFlag & ShapeFlags.SUSPENSE) {
// TODO // TODO
} else { } else {

View File

@ -5,8 +5,9 @@ export const enum ShapeFlags {
TEXT_CHILDREN = 1 << 3, TEXT_CHILDREN = 1 << 3,
ARRAY_CHILDREN = 1 << 4, ARRAY_CHILDREN = 1 << 4,
SLOTS_CHILDREN = 1 << 5, SLOTS_CHILDREN = 1 << 5,
SUSPENSE = 1 << 6, PORTAL = 1 << 6,
COMPONENT_SHOULD_KEEP_ALIVE = 1 << 7, SUSPENSE = 1 << 7,
COMPONENT_KEPT_ALIVE = 1 << 8, COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
COMPONENT_KEPT_ALIVE = 1 << 9,
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT
} }