import { Text, Fragment, Comment, cloneIfMounted, normalizeVNode, VNode, VNodeArrayChildren, createVNode, isSameVNodeType, Static, VNodeNormalizedRef, VNodeHook, isVNode } from './vnode' import { ComponentInternalInstance, createComponentInstance, Data, setupComponent, Component } from './component' import { renderComponentRoot, shouldUpdateComponent, updateHOCHostEl } from './componentRenderUtils' import { isString, EMPTY_OBJ, EMPTY_ARR, isReservedProp, isFunction, PatchFlags, ShapeFlags, NOOP, hasOwn, invokeArrayFns, isArray } from '@vue/shared' import { queueJob, queuePostFlushCb, flushPostFlushCbs, invalidateJob } from './scheduler' import { effect, stop, ReactiveEffectOptions, isRef } from '@vue/reactivity' import { updateProps } from './componentProps' import { updateSlots } from './componentSlots' import { pushWarningContext, popWarningContext, warn } from './warning' import { createAppAPI, CreateAppFunction } from './apiCreateApp' import { SuspenseBoundary, queueEffectWithSuspense, SuspenseImpl } from './components/Suspense' import { TeleportImpl } from './components/Teleport' import { isKeepAlive, KeepAliveContext } from './components/KeepAlive' import { registerHMR, unregisterHMR, isHmrUpdating } from './hmr' import { ErrorCodes, callWithErrorHandling, callWithAsyncErrorHandling } from './errorHandling' import { createHydrationFunctions, RootHydrateFunction } from './hydration' import { invokeDirectiveHook } from './directives' import { startMeasure, endMeasure } from './profiling' import { ComponentPublicInstance } from './componentProxy' export interface Renderer { render: RootRenderFunction createApp: CreateAppFunction } export interface HydrationRenderer extends Renderer { hydrate: RootHydrateFunction } export type RootRenderFunction = ( vnode: VNode | null, container: HostElement ) => void export interface RendererOptions< HostNode = RendererNode, HostElement = RendererElement > { patchProp( el: HostElement, key: string, prevValue: any, nextValue: any, isSVG?: boolean, prevChildren?: VNode[], parentComponent?: ComponentInternalInstance | null, parentSuspense?: SuspenseBoundary | null, unmountChildren?: UnmountChildrenFn ): void forcePatchProp?(el: HostElement, key: string): boolean insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void remove(el: HostNode): void createElement( type: string, isSVG?: boolean, isCustomizedBuiltIn?: string ): HostElement createText(text: string): HostNode createComment(text: string): HostNode setText(node: HostNode, text: string): void setElementText(node: HostElement, text: string): void parentNode(node: HostNode): HostElement | null nextSibling(node: HostNode): HostNode | null querySelector?(selector: string): HostElement | null setScopeId?(el: HostElement, id: string): void cloneNode?(node: HostNode): HostNode insertStaticContent?( content: string, parent: HostElement, anchor: HostNode | null, isSVG: boolean ): HostElement[] } // Renderer Node can technically be any object in the context of core renderer // logic - they are never directly operated on and always passed to the node op // functions provided via options, so the internal constraint is really just // a generic object. export interface RendererNode { [key: string]: any } export interface RendererElement extends RendererNode {} // An object exposing the internals of a renderer, passed to tree-shakeable // features so that they can be decoupled from this file. Keys are shortened // to optimize bundle size. export interface RendererInternals< HostNode = RendererNode, HostElement = RendererElement > { p: PatchFn um: UnmountFn r: RemoveFn m: MoveFn mt: MountComponentFn mc: MountChildrenFn pc: PatchChildrenFn pbc: PatchBlockChildrenFn n: NextFn o: RendererOptions } // These functions are created inside a closure and therefore their types cannot // be directly exported. In order to avoid maintaining function signatures in // two places, we declare them once here and use them inside the closure. type PatchFn = ( n1: VNode | null, // null means this is a mount n2: VNode, container: RendererElement, anchor?: RendererNode | null, parentComponent?: ComponentInternalInstance | null, parentSuspense?: SuspenseBoundary | null, isSVG?: boolean, optimized?: boolean ) => void type MountChildrenFn = ( children: VNodeArrayChildren, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, optimized: boolean, start?: number ) => void type PatchChildrenFn = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, optimized?: boolean ) => void type PatchBlockChildrenFn = ( oldChildren: VNode[], newChildren: VNode[], fallbackContainer: RendererElement, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean ) => void type MoveFn = ( vnode: VNode, container: RendererElement, anchor: RendererNode | null, type: MoveType, parentSuspense?: SuspenseBoundary | null ) => void type NextFn = (vnode: VNode) => RendererNode | null type UnmountFn = ( vnode: VNode, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, doRemove?: boolean ) => void type RemoveFn = (vnode: VNode) => void type UnmountChildrenFn = ( children: VNode[], parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, doRemove?: boolean, start?: number ) => void export type MountComponentFn = ( initialVNode: VNode, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, optimized: boolean ) => void type ProcessTextOrCommentFn = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererNode | null ) => void export type SetupRenderEffectFn = ( instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, anchor: RendererNode | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, optimized: boolean ) => void export const enum MoveType { ENTER, LEAVE, REORDER } const prodEffectOptions = { scheduler: queueJob } function createDevEffectOptions( instance: ComponentInternalInstance ): ReactiveEffectOptions { return { scheduler: queueJob, onTrack: instance.rtc ? e => invokeArrayFns(instance.rtc!, e) : void 0, onTrigger: instance.rtg ? e => invokeArrayFns(instance.rtg!, e) : void 0 } } export const queuePostRenderEffect = __FEATURE_SUSPENSE__ ? queueEffectWithSuspense : queuePostFlushCb export const setRef = ( rawRef: VNodeNormalizedRef, oldRawRef: VNodeNormalizedRef | null, parent: ComponentInternalInstance, vnode: VNode | null ) => { let value: ComponentPublicInstance | RendererNode | null if (!vnode) { value = null } else { const { el, component, shapeFlag, type } = vnode if (shapeFlag & ShapeFlags.COMPONENT && (type as Component).inheritRef) { return } if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { value = component!.proxy } else { value = el } } const [owner, ref] = rawRef if (__DEV__ && !owner) { warn( `Missing ref owner context. ref cannot be used on hoisted vnodes. ` + `A vnode with ref must be created inside the render function.` ) return } const oldRef = oldRawRef && oldRawRef[1] const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs const setupState = owner.setupState // unset old ref if (oldRef != null && oldRef !== ref) { if (isString(oldRef)) { refs[oldRef] = null if (hasOwn(setupState, oldRef)) { setupState[oldRef] = null } } else if (isRef(oldRef)) { oldRef.value = null } } if (isString(ref)) { refs[ref] = value if (hasOwn(setupState, ref)) { setupState[ref] = value } } else if (isRef(ref)) { ref.value = value } else if (isFunction(ref)) { callWithErrorHandling(ref, parent, ErrorCodes.FUNCTION_REF, [value, refs]) } else if (__DEV__) { warn('Invalid template ref type:', value, `(${typeof value})`) } } /** * The createRenderer function accepts two generic arguments: * HostNode and HostElement, corresponding to Node and Element types in the * host environment. For example, for runtime-dom, HostNode would be the DOM * `Node` interface and HostElement would be the DOM `Element` interface. * * Custom renderers can pass in the platform specific types like this: * * ``` js * const { render, createApp } = createRenderer({ * patchProp, * ...nodeOps * }) * ``` */ export function createRenderer< HostNode = RendererNode, HostElement = RendererElement >(options: RendererOptions) { return baseCreateRenderer(options) } // Separate API for creating hydration-enabled renderer. // Hydration logic is only used when calling this function, making it // tree-shakable. export function createHydrationRenderer( options: RendererOptions ) { return baseCreateRenderer(options, createHydrationFunctions) } // overload 1: no hydration function baseCreateRenderer< HostNode = RendererNode, HostElement = RendererElement >(options: RendererOptions): Renderer // overload 2: with hydration function baseCreateRenderer( options: RendererOptions, createHydrationFns: typeof createHydrationFunctions ): HydrationRenderer // implementation function baseCreateRenderer( options: RendererOptions, createHydrationFns?: typeof createHydrationFunctions ): any { const { insert: hostInsert, remove: hostRemove, patchProp: hostPatchProp, forcePatchProp: hostForcePatchProp, createElement: hostCreateElement, createText: hostCreateText, createComment: hostCreateComment, setText: hostSetText, setElementText: hostSetElementText, parentNode: hostParentNode, nextSibling: hostNextSibling, setScopeId: hostSetScopeId = NOOP, cloneNode: hostCloneNode, insertStaticContent: hostInsertStaticContent } = options // Note: functions inside this closure should use `const xxx = () => {}` // style in order to prevent being inlined by minifiers. const patch: PatchFn = ( n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false ) => { // patching & not same type, unmount old tree if (n1 && !isSameVNodeType(n1, n2)) { anchor = getNextHostNode(n1) unmount(n1, parentComponent, parentSuspense, true) n1 = null } if (n2.patchFlag === PatchFlags.BAIL) { optimized = false n2.dynamicChildren = null } const { type, ref, shapeFlag } = n2 switch (type) { case Text: processText(n1, n2, container, anchor) break case Comment: processCommentNode(n1, n2, container, anchor) break case Static: if (n1 == null) { mountStaticNode(n2, container, anchor, isSVG) } else if (__DEV__) { patchStaticNode(n1, n2, container, isSVG) } break case Fragment: processFragment( n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) break default: if (shapeFlag & ShapeFlags.ELEMENT) { processElement( n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) } else if (shapeFlag & ShapeFlags.COMPONENT) { processComponent( n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) } else if (shapeFlag & ShapeFlags.TELEPORT) { ;(type as typeof TeleportImpl).process( n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals ) } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { ;(type as typeof SuspenseImpl).process( n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals ) } else if (__DEV__) { warn('Invalid VNode type:', type, `(${typeof type})`) } } // set ref if (ref != null && parentComponent) { setRef(ref, n1 && n1.ref, parentComponent, n2) } } const processText: ProcessTextOrCommentFn = (n1, n2, container, anchor) => { if (n1 == null) { hostInsert( (n2.el = hostCreateText(n2.children as string)), container, anchor ) } else { const el = (n2.el = n1.el!) if (n2.children !== n1.children) { hostSetText(el, n2.children as string) } } } const processCommentNode: ProcessTextOrCommentFn = ( n1, n2, container, anchor ) => { if (n1 == null) { hostInsert( (n2.el = hostCreateComment((n2.children as string) || '')), container, anchor ) } else { // there's no support for dynamic comments n2.el = n1.el } } const mountStaticNode = ( n2: VNode, container: RendererElement, anchor: RendererNode | null, isSVG: boolean ) => { // static nodes are only present when used with compiler-dom/runtime-dom // which guarantees presence of hostInsertStaticContent. ;[n2.el, n2.anchor] = hostInsertStaticContent!( n2.children as string, container, anchor, isSVG ) } /** * Dev / HMR only */ const patchStaticNode = ( n1: VNode, n2: VNode, container: RendererElement, isSVG: boolean ) => { // static nodes are only patched during dev for HMR if (n2.children !== n1.children) { const anchor = hostNextSibling(n1.anchor!) // remove existing removeStaticNode(n1) // insert new ;[n2.el, n2.anchor] = hostInsertStaticContent!( n2.children as string, container, anchor, isSVG ) } else { n2.el = n1.el n2.anchor = n1.anchor } } /** * Dev / HMR only */ const moveStaticNode = ( vnode: VNode, container: RendererElement, anchor: RendererNode | null ) => { let cur = vnode.el const end = vnode.anchor! while (cur && cur !== end) { const next = hostNextSibling(cur) hostInsert(cur, container, anchor) cur = next } hostInsert(end, container, anchor) } /** * Dev / HMR only */ const removeStaticNode = (vnode: VNode) => { let cur = vnode.el while (cur && cur !== vnode.anchor) { const next = hostNextSibling(cur) hostRemove(cur) cur = next } hostRemove(vnode.anchor!) } const processElement = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, optimized: boolean ) => { isSVG = isSVG || (n2.type as string) === 'svg' if (n1 == null) { mountElement( n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) } else { patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized) } } const mountElement = ( vnode: VNode, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, optimized: boolean ) => { let el: RendererElement let vnodeHook: VNodeHook | undefined | null const { type, props, shapeFlag, transition, scopeId, patchFlag, dirs } = vnode if ( vnode.el && hostCloneNode !== undefined && patchFlag === PatchFlags.HOISTED ) { // If a vnode has non-null el, it means it's being reused. // Only static vnodes can be reused, so its mounted DOM nodes should be // exactly the same, and we can simply do a clone here. el = vnode.el = hostCloneNode(vnode.el) } else { el = vnode.el = hostCreateElement( vnode.type as string, isSVG, props && props.is ) // mount children first, since some props may rely on child content // being already rendered, e.g. `