refactor: make portal tree-shakeable
This commit is contained in:
parent
0fc720c34a
commit
9d2ac6675a
@ -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', () => {
|
@ -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
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
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
|
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> = {
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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__) {
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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 { 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,
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user