import { Text, Fragment, Comment, cloneIfMounted, normalizeVNode, VNode, VNodeArrayChildren, createVNode, isSameVNodeType, Static, VNodeNormalizedRef, VNodeHook, VNodeNormalizedRefAtom, VNodeProps } from './vnode' import { ComponentInternalInstance, createComponentInstance, Data, setupComponent } from './component' import { filterSingleRoot, renderComponentRoot, shouldUpdateComponent, updateHOCHostEl } from './componentRenderUtils' import { isString, EMPTY_OBJ, EMPTY_ARR, isReservedProp, isFunction, PatchFlags, ShapeFlags, NOOP, hasOwn, invokeArrayFns, isArray, getGlobalThis } from '@vue/shared' import { queueJob, queuePostFlushCb, flushPostFlushCbs, invalidateJob, flushPreFlushCbs, SchedulerCb } from './scheduler' import { effect, stop, ReactiveEffectOptions, isRef, pauseTracking, resetTracking } 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, TeleportVNode } 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 './componentPublicInstance' import { devtoolsComponentAdded, devtoolsComponentRemoved, devtoolsComponentUpdated, setDevtoolsHook } from './devtools' import { initFeatureFlags } from './featureFlags' import { isAsyncWrapper } from './apiAsyncComponent' export interface Renderer { render: RootRenderFunction createApp: CreateAppFunction } export interface HydrationRenderer extends Renderer { hydrate: RootHydrateFunction } export type RootRenderFunction = ( vnode: VNode | null, container: HostElement, isSVG?: boolean ) => 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, vnodeProps?: (VNodeProps & { [key: string]: any }) | null ): 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, slotScopeIds?: string[] | null, optimized?: boolean ) => void type MountChildrenFn = ( children: VNodeArrayChildren, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, slotScopeIds: string[] | null, 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, slotScopeIds: string[] | null, optimized: boolean ) => void type PatchBlockChildrenFn = ( oldChildren: VNode[], newChildren: VNode[], fallbackContainer: RendererElement, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, slotScopeIds: string[] | null ) => 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, optimized?: boolean ) => void type RemoveFn = (vnode: VNode) => void type UnmountChildrenFn = ( children: VNode[], parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, doRemove?: boolean, optimized?: 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, // #1801, #2043 component render effects should allow recursive updates allowRecurse: true } function createDevEffectOptions( instance: ComponentInternalInstance ): ReactiveEffectOptions { return { scheduler: queueJob, allowRecurse: true, 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, parentSuspense: SuspenseBoundary | null, vnode: VNode | null ) => { if (isArray(rawRef)) { rawRef.forEach((r, i) => setRef( r, oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef), parentSuspense, vnode ) ) return } let value: ComponentPublicInstance | RendererNode | Record | null if (!vnode) { // means unmount value = null } else if (isAsyncWrapper(vnode)) { // when mounting async components, nothing needs to be done, // because the template ref is forwarded to inner component return } else if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { value = vnode.component!.exposed || vnode.component!.proxy } else { value = vnode.el } const { i: owner, r: 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 as VNodeNormalizedRefAtom).r 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)) { const doSet = () => { refs[ref] = value if (hasOwn(setupState, ref)) { setupState[ref] = value } } // #1789: for non-null values, set them after render // null values means this is unmount and it should not overwrite another // ref with the same key if (value) { ;(doSet as SchedulerCb).id = -1 queuePostRenderEffect(doSet, parentSuspense) } else { doSet() } } else if (isRef(ref)) { const doSet = () => { ref.value = value } if (value) { ;(doSet as SchedulerCb).id = -1 queuePostRenderEffect(doSet, parentSuspense) } else { doSet() } } else if (isFunction(ref)) { callWithErrorHandling(ref, owner, 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 { // compile-time feature flags check if (__ESM_BUNDLER__ && !__TEST__) { initFeatureFlags() } if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { const target = getGlobalThis() target.__VUE__ = true setDevtoolsHook(target.__VUE_DEVTOOLS_GLOBAL_HOOK__) } 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, slotScopeIds = null, 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, slotScopeIds, optimized ) break default: if (shapeFlag & ShapeFlags.ELEMENT) { processElement( n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ) } else if (shapeFlag & ShapeFlags.COMPONENT) { processComponent( n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ) } else if (shapeFlag & ShapeFlags.TELEPORT) { ;(type as typeof TeleportImpl).process( n1 as TeleportVNode, n2 as TeleportVNode, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, internals ) } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { ;(type as typeof SuspenseImpl).process( n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, internals ) } else if (__DEV__) { warn('Invalid VNode type:', type, `(${typeof type})`) } } // set ref if (ref != null && parentComponent) { setRef(ref, n1 && n1.ref, parentSuspense, 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 } } const moveStaticNode = ( { el, anchor }: VNode, container: RendererElement, nextSibling: RendererNode | null ) => { let next while (el && el !== anchor) { next = hostNextSibling(el) hostInsert(el, container, nextSibling) el = next } hostInsert(anchor!, container, nextSibling) } const removeStaticNode = ({ el, anchor }: VNode) => { let next while (el && el !== anchor) { next = hostNextSibling(el) hostRemove(el) el = next } hostRemove(anchor!) } const processElement = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, slotScopeIds: string[] | null, optimized: boolean ) => { isSVG = isSVG || (n2.type as string) === 'svg' if (n1 == null) { mountElement( n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ) } else { patchElement( n1, n2, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ) } } const mountElement = ( vnode: VNode, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, slotScopeIds: string[] | null, optimized: boolean ) => { let el: RendererElement let vnodeHook: VNodeHook | undefined | null const { type, props, shapeFlag, transition, patchFlag, dirs } = vnode if ( !__DEV__ && 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. // only do this in production since cloned trees cannot be HMR updated. el = vnode.el = hostCloneNode(vnode.el) } else { el = vnode.el = hostCreateElement( vnode.type as string, isSVG, props && props.is, props ) // mount children first, since some props may rely on child content // being already rendered, e.g. `