import { Comment, Fragment, Static, Text, type VNode, type VNodeArrayChildren, type VNodeHook, type VNodeProps, cloneIfMounted, createVNode, invokeVNodeHook, isSameVNodeType, normalizeVNode, } from './vnode' import { type ComponentInternalInstance, type ComponentOptions, type Data, type LifecycleHook, createComponentInstance, setupComponent, } from './component' import { filterSingleRoot, renderComponentRoot, shouldUpdateComponent, updateHOCHostEl, } from './componentRenderUtils' import { EMPTY_ARR, EMPTY_OBJ, NOOP, PatchFlags, ShapeFlags, def, getGlobalThis, invokeArrayFns, isArray, isReservedProp, } from '@vue/shared' import { type SchedulerJob, SchedulerJobFlags, type SchedulerJobs, flushPostFlushCbs, flushPreFlushCbs, queueJob, queuePostFlushCb, } from './scheduler' import { EffectFlags, ReactiveEffect, pauseTracking, resetTracking, } from '@vue/reactivity' import { updateProps } from './componentProps' import { updateSlots } from './componentSlots' import { popWarningContext, pushWarningContext, warn } from './warning' import { type CreateAppFunction, createAppAPI } from './apiCreateApp' import { setRef } from './rendererTemplateRef' import { type SuspenseBoundary, type SuspenseImpl, isSuspense, queueEffectWithSuspense, } from './components/Suspense' import { TeleportEndKey, type TeleportImpl, type TeleportVNode, } from './components/Teleport' import { type KeepAliveContext, isKeepAlive } from './components/KeepAlive' import { isHmrUpdating, registerHMR, unregisterHMR } from './hmr' import { type RootHydrateFunction, createHydrationFunctions } from './hydration' import { invokeDirectiveHook } from './directives' import { endMeasure, startMeasure } from './profiling' import { devtoolsComponentAdded, devtoolsComponentRemoved, devtoolsComponentUpdated, setDevtoolsHook, } from './devtools' import { initFeatureFlags } from './featureFlags' import { isAsyncWrapper } from './apiAsyncComponent' import { isCompatEnabled } from './compat/compatConfig' import { DeprecationTypes } from './compat/compatConfig' import type { TransitionHooks } from './components/BaseTransition' export interface Renderer { render: RootRenderFunction createApp: CreateAppFunction } export interface HydrationRenderer extends Renderer { hydrate: RootHydrateFunction } export type ElementNamespace = 'svg' | 'mathml' | undefined export type RootRenderFunction = ( vnode: VNode | null, container: HostElement, namespace?: ElementNamespace, ) => void export interface RendererOptions< HostNode = RendererNode, HostElement = RendererElement, > { patchProp( el: HostElement, key: string, prevValue: any, nextValue: any, namespace?: ElementNamespace, parentComponent?: ComponentInternalInstance | null, ): void insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void remove(el: HostNode): void createElement( type: string, namespace?: ElementNamespace, 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, namespace: ElementNamespace, start?: HostNode | null, end?: HostNode | null, ): [HostNode, HostNode] } // 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 | symbol]: 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, namespace?: ElementNamespace, slotScopeIds?: string[] | null, optimized?: boolean, ) => void type MountChildrenFn = ( children: VNodeArrayChildren, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, namespace: ElementNamespace, 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, namespace: ElementNamespace, slotScopeIds: string[] | null, optimized: boolean, ) => void type PatchBlockChildrenFn = ( oldChildren: VNode[], newChildren: VNode[], fallbackContainer: RendererElement, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, namespace: ElementNamespace, 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, namespace: ElementNamespace, 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, namespace: ElementNamespace, optimized: boolean, ) => void export enum MoveType { ENTER, LEAVE, REORDER, } export const queuePostRenderEffect: ( fn: SchedulerJobs, suspense: SuspenseBoundary | null, ) => void = __FEATURE_SUSPENSE__ ? __TEST__ ? // vitest can't seem to handle eager circular dependency (fn: Function | Function[], suspense: SuspenseBoundary | null) => queueEffectWithSuspense(fn, suspense) : queueEffectWithSuspense : queuePostFlushCb /** * 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): Renderer { 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, ): HydrationRenderer { 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() } const target = getGlobalThis() target.__VUE__ = true if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { setDevtoolsHook(target.__VUE_DEVTOOLS_GLOBAL_HOOK__, target) } const { insert: hostInsert, remove: hostRemove, patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, createComment: hostCreateComment, setText: hostSetText, setElementText: hostSetElementText, parentNode: hostParentNode, nextSibling: hostNextSibling, setScopeId: hostSetScopeId = NOOP, 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, namespace = undefined, slotScopeIds = null, optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren, ) => { if (n1 === n2) { return } // 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, namespace) } else if (__DEV__) { patchStaticNode(n1, n2, container, namespace) } break case Fragment: processFragment( n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, ) break default: if (shapeFlag & ShapeFlags.ELEMENT) { processElement( n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, ) } else if (shapeFlag & ShapeFlags.COMPONENT) { processComponent( n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, ) } else if (shapeFlag & ShapeFlags.TELEPORT) { ;(type as typeof TeleportImpl).process( n1 as TeleportVNode, n2 as TeleportVNode, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, internals, ) } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { ;(type as typeof SuspenseImpl).process( n1, n2, container, anchor, parentComponent, parentSuspense, namespace, 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 || n1, !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, namespace: ElementNamespace, ) => { // 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, namespace, n2.el, n2.anchor, ) } /** * Dev / HMR only */ const patchStaticNode = ( n1: VNode, n2: VNode, container: RendererElement, namespace: ElementNamespace, ) => { // 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, namespace, ) } 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, namespace: ElementNamespace, slotScopeIds: string[] | null, optimized: boolean, ) => { if (n2.type === 'svg') { namespace = 'svg' } else if (n2.type === 'math') { namespace = 'mathml' } if (n1 == null) { mountElement( n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, ) } else { patchElement( n1, n2, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, ) } } const mountElement = ( vnode: VNode, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, namespace: ElementNamespace, slotScopeIds: string[] | null, optimized: boolean, ) => { let el: RendererElement let vnodeHook: VNodeHook | undefined | null const { props, shapeFlag, transition, dirs } = vnode el = vnode.el = hostCreateElement( vnode.type as string, namespace, props && props.is, props, ) // mount children first, since some props may rely on child content // being already rendered, e.g. `