refactor: make portal tree-shakeable
This commit is contained in:
parent
0fc720c34a
commit
9d2ac6675a
@ -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', () => {
|
@ -141,19 +141,18 @@ export function createAppAPI<HostNode, HostElement>(
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
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
|
||||
},
|
||||
|
||||
|
@ -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
|
||||
|
118
packages/runtime-core/src/components/Portal.ts
Normal file
118
packages/runtime-core/src/components/Portal.ts
Normal 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 }
|
||||
}
|
@ -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<HostNode, HostElement>(
|
||||
rendererInternals: RendererInternals<HostNode, HostElement>
|
||||
): SuspenseBoundary<HostNode, HostElement> {
|
||||
const {
|
||||
patch,
|
||||
move,
|
||||
unmount,
|
||||
next,
|
||||
options: { parentNode }
|
||||
p: patch,
|
||||
m: move,
|
||||
um: unmount,
|
||||
n: next,
|
||||
o: { parentNode }
|
||||
} = rendererInternals
|
||||
|
||||
const suspense: SuspenseBoundary<HostNode, HostElement> = {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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<Node, Element>,
|
||||
@ -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<Node, Element>,
|
||||
patchProp: RendererOptions['patchProp']
|
||||
) {
|
||||
export function createHydrationFunctions({
|
||||
mt: mountComponent,
|
||||
o: { patchProp }
|
||||
}: RendererInternals<Node, Element>) {
|
||||
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__) {
|
||||
|
@ -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 {
|
||||
|
@ -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<HostNode = any, HostElement = any> {
|
||||
}
|
||||
|
||||
// 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> {
|
||||
patch: PatchFn<HostNode, HostElement>
|
||||
unmount: UnmountFn<HostNode, HostElement>
|
||||
move: MoveFn<HostNode, HostElement>
|
||||
next: NextFn<HostNode, HostElement>
|
||||
options: RendererOptions<HostNode, HostElement>
|
||||
p: PatchFn<HostNode, HostElement>
|
||||
um: UnmountFn<HostNode, HostElement>
|
||||
m: MoveFn<HostNode, HostElement>
|
||||
mt: MountComponentFn<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
|
||||
@ -136,11 +142,35 @@ type PatchFn<HostNode, HostElement> = (
|
||||
optimized?: boolean
|
||||
) => void
|
||||
|
||||
type UnmountFn<HostNode, HostElement> = (
|
||||
vnode: VNode<HostNode, HostElement>,
|
||||
type MountChildrenFn<HostNode, HostElement> = (
|
||||
children: VNodeArrayChildren<HostNode, HostElement>,
|
||||
container: HostElement,
|
||||
anchor: HostNode | null,
|
||||
parentComponent: ComponentInternalInstance | 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
|
||||
|
||||
type MoveFn<HostNode, HostElement> = (
|
||||
@ -155,6 +185,13 @@ type NextFn<HostNode, HostElement> = (
|
||||
vnode: VNode<HostNode, HostElement>
|
||||
) => 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> = (
|
||||
children: VNode<HostNode, HostElement>[],
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
@ -172,6 +209,13 @@ export type MountComponentFn<HostNode, HostElement> = (
|
||||
isSVG: boolean
|
||||
) => 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> = (
|
||||
instance: ComponentInternalInstance,
|
||||
initialVNode: VNode<HostNode, HostElement>,
|
||||
@ -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<HostNode, HostElement> = (
|
||||
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<HostNode, HostElement> = (
|
||||
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<HostNode, HostElement> = (
|
||||
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<HostNode, HostElement> = (
|
||||
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<HostNode, HostElement> = (
|
||||
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<HostNode, HostElement> = {
|
||||
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<typeof createHydrationFunctions>[0] | undefined
|
||||
let hydrateNode: ReturnType<typeof createHydrationFunctions>[1] | undefined
|
||||
if (createHydrationFns) {
|
||||
;[hydrate, hydrateNode] = createHydrationFns(
|
||||
mountComponent as MountComponentFn<Node, Element>,
|
||||
hostPatchProp
|
||||
)
|
||||
;[hydrate, hydrateNode] = createHydrationFns(internals as RendererInternals<
|
||||
Node,
|
||||
Element
|
||||
>)
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -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
|
||||
}
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user