refactor: make portal tree-shakeable
This commit is contained in:
@@ -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> = {
|
||||
|
||||
Reference in New Issue
Block a user