import { RootRenderFunction , VNode , PatchFn , ShapeFlags , ComponentFn , ComponentInstance , SetupRenderEffectFn , RendererElement , RendererOptions , RendererNode , PatchFlags , Data , VNodeArrayChildren } from '../shared/svu' ; import { EMPTY_OBJ , isSameVNodeType , invokeArrayFns } from '../shared' ; import { createVnode , Text , Fragment , openBlock , createBlock } from './vnode' ; import { createComponentInstance , setupComponent } from './component' ; import { renderComponentRoot , normalizeVNode } from './componentRenderUtils' ; import { effect } from '../reactivity' ; import { getSequence } from './getSequence' ; import { queueJob , queuePostRenderEffect } from './scheduler' ; function createRenderer < HostNode = RendererNode , HostElement = RendererElement > ( options ) { const { insert : hostInsert , remove : hostRemove , patchProp : hostPatchProp , createElement : hostCreateElement , createText : hostCreateText , setText : hostSetText , setElementText : hostSetElementText , parentNode : hostParentNode , nextSibling : hostNextSibling , setScopeId : hostSetScopeId = ( ) => { } , cloneNode : hostCloneNode , } = options ; const patch = ( n1 , n2 , container , anchor ) => { if ( n1 && ! isSameVNodeType ( n1 , n2 ) ) { hostRemove ( n1 . el as any ) ; n1 = null ; } let { shapeFlag , type } = n2 ; switch ( type ) { case Text : processText ( n1 , n2 , container ) ; break ; default : if ( shapeFlag & ShapeFlags . ELEMENT ) { processElement ( n1 , n2 , container , anchor ) ; } else if ( shapeFlag & ShapeFlags . COMPONENT ) { processComponent ( n1 , n2 , container ) ; } break ; } } ; const processText = ( n1 , n2 , container ) => { if ( n1 == null ) { hostInsert ( n2 . el = hostCreateText ( n2 . children as string ) , container ) ; } else { const el = ( n2 . el = n1 . el ! ) ; if ( n2 . children !== n1 . children ) { hostSetText ( el , n2 . children as string ) ; } } } ; const processElement = ( n1 , n2 , container , anchor ) => { if ( n1 == null ) { mountElement ( n2 , container , anchor ) ; } else { patchElement ( n1 , n2 ) ; } } ; const mountElement = ( vnode , container , anchor ? ) => { const { shapeFlag , props } = vnode ; let el ; el = vnode . el = hostCreateElement ( vnode . type ) ; if ( shapeFlag & ShapeFlags . TEXT_CHILDREN ) { hostSetElementText ( el , vnode . children as string ) ; } else if ( shapeFlag & ShapeFlags . ARRAY_CHILDREN ) { mountChildren ( vnode . children , el ) ; } if ( props ) { for ( const key in props ) { hostPatchProp ( el , key , null , props [ key ] ) ; } } hostInsert ( el , container , anchor ) ; } ; const mountChildren = ( children , container , start = 0 ) => { for ( let i = start ; i < children . length ; i ++ ) { const child = ( children [ i ] = normalizeVNode ( children [ i ] ) ) ; patch ( null , child , container ) ; } } ; const patchElement = ( n1 , n2 ) => { const el = ( n2 . el = n1 . el ! ) ; let { patchFlag , dynamicChildren } = n2 ; patchFlag |= n1 . patchFlag & PatchFlags . FULL_PROPS ; const oldProps = n1 . props || EMPTY_OBJ ; const newProps = n2 . props || EMPTY_OBJ ; if ( patchFlag > 0 ) { if ( patchFlag & PatchFlags . FULL_PROPS ) { patchProps ( el , n2 , oldProps , newProps ) ; } else { if ( patchFlag & PatchFlags . CLASS ) { if ( oldProps ! . class != newProps ! . class ) { hostPatchProp ( el , 'class' , null , newProps . class ) ; } } if ( patchFlag & PatchFlags . STYLE ) { hostPatchProp ( el , 'style' , oldProps . style , newProps . style ) ; } if ( patchFlag & PatchFlags . PROPS ) { const propsToUpdate = n2 . dynamicProps ! ; for ( let i = 0 , len = propsToUpdate . length ; i < len ; i ++ ) { const key = propsToUpdate [ i ] ; const prev = oldProps [ key ] ; const next = newProps [ key ] ; if ( next !== prev ) { hostPatchProp ( el , key , prev , next ) ; } } } } if ( patchFlag & PatchFlags . TEXT ) { if ( n1 . children !== n2 . children ) { hostSetElementText ( el , n2 . children as string ) ; } } } else { patchProps ( el , n2 , oldProps , newProps ) ; } if ( dynamicChildren ) { patchBlockChildren ( n1 . dynamicChildren ! , dynamicChildren , el ) ; } else { patchChildren ( n1 , n2 , el ) ; } } ; const patchProps = ( el , vnode , oldProps , newProps ) => { if ( oldProps !== newProps ) { for ( let key in newProps ) { const next = newProps [ key ] ; const prev = oldProps [ key ] ; if ( next !== prev ) { hostPatchProp ( el , key , prev , next ) ; } } for ( const key in oldProps ) { if ( ! ( key in newProps ) ) { hostPatchProp ( el , key , oldProps [ key ] , null ) ; } } } } ; const patchBlockChildren = ( oldChildren , newChildren , fallbackContainer ) => { for ( let i = 0 , len = newChildren . length ; i < len ; i ++ ) { const oldVNode = oldChildren [ i ] ; const newVNode = newChildren [ i ] ; const container = oldVNode . type === Fragment || ! isSameVNodeType ( oldVNode , newVNode ) || oldVNode . shapeFlag & ShapeFlags . COMPONENT ? hostParentNode ( oldVNode . el ! ) ! : fallbackContainer ; patch ( oldVNode , newVNode , container ) ; } } ; const patchChildren = ( n1 , n2 , container ) => { const c1 = n1 && n1 . children ; const prevShapeFlag = n1 ? n1 . shapeFlag : 0 ; const c2 = n2 . children ; const { patchFlag , shapeFlag } = n2 ; if ( shapeFlag & ShapeFlags . TEXT_CHILDREN ) { if ( prevShapeFlag & ShapeFlags . ARRAY_CHILDREN ) { container . innerHTML = '' ; } if ( c2 !== c1 ) { hostSetElementText ( container , c2 as string ) ; } } else { if ( prevShapeFlag & ShapeFlags . ARRAY_CHILDREN ) { if ( shapeFlag & ShapeFlags . ARRAY_CHILDREN ) { patchKeyedChildren ( c1 as VNode [ ] , c2 as VNodeArrayChildren , container ) ; } else { container . innerHTML = '' ; } } else { if ( prevShapeFlag & ShapeFlags . TEXT_CHILDREN ) { hostSetElementText ( container , '' ) ; } if ( shapeFlag & ShapeFlags . ARRAY_CHILDREN ) { mountChildren ( c2 , container ) ; } } } } ; const patchKeyedChildren = ( c1 , c2 , container ) => { let i = 0 , l2 = c2 . length , e1 = c1 . length - 1 , e2 = l2 - 1 ; while ( i <= e1 && i <= e2 ) { const n1 = c1 [ i ] ; const n2 = c2 [ i ] = normalizeVNode ( c2 [ i ] ) ; if ( isSameVNodeType ( n1 , n2 ) ) { patch ( n1 , n2 , container ) ; } else { break ; } i ++ ; } while ( i <= e1 && i <= e2 ) { const n1 = c1 [ e1 ] ; const n2 = c2 [ e2 ] = normalizeVNode ( c2 [ e2 ] ) ; if ( isSameVNodeType ( n1 , n2 ) ) { patch ( n1 , n2 , container ) ; } else { break ; } e1 -- ; e2 -- ; } if ( i > e1 ) { while ( i <= e2 ) { patch ( null , c2 [ i ] = normalizeVNode ( c2 [ i ] ) , container ) ; i ++ ; } } else if ( i > e2 ) { while ( i <= e1 ) { hostRemove ( c1 [ i ] . el as any ) ; i ++ ; } } else { const s1 = i , s2 = i ; const keyToNewIndexMap = new Map ( ) ; for ( i = s2 ; i <= e2 ; i ++ ) { const nextChild = ( c2 [ i ] = normalizeVNode ( c2 [ i ] ) ) ; const key = nextChild . key ; if ( key != null ) { keyToNewIndexMap . set ( key , i ) ; } } let j ; let patched = 0 , toBePatched = e2 - s2 + 1 ; let maxNewIndexSoFar = 0 , moved = false ; const newIndexToOldIndexMap = new Array ( toBePatched ) . fill ( 0 ) ; for ( i = s1 ; i <= e1 ; i ++ ) { const prevChild = c1 [ i ] ; if ( patched >= toBePatched ) { hostRemove ( prevChild . el as any ) ; continue ; } let newIndex , prevChildKey = prevChild . key ; if ( prevChildKey != null ) { newIndex = keyToNewIndexMap . get ( prevChildKey ) ; } else { for ( j = s2 ; j <= e2 ; j ++ ) { if ( newIndexToOldIndexMap [ j - s2 ] === 0 && isSameVNodeType ( prevChild , c2 [ j ] as VNode ) ) { newIndex = j ; break ; } } } if ( newIndex === undefined ) { hostRemove ( prevChild . el as any ) ; } else { newIndexToOldIndexMap [ newIndex - s2 ] = i + 1 ; if ( newIndex >= maxNewIndexSoFar ) { maxNewIndexSoFar = newIndex ; } else { moved = true ; } patch ( prevChild , c2 [ newIndex ] as VNode , container ) ; patched ++ ; } } const increasingNewIndexSequence = moved ? getSequence ( newIndexToOldIndexMap ) : [ ] ; j = increasingNewIndexSequence . length - 1 ; for ( i = toBePatched - 1 ; i >= 0 ; i -- ) { const nextIndex = s2 + i ; const nextChild = c2 [ nextIndex ] as VNode ; const anchor = nextIndex + 1 < l2 ? ( c2 [ nextIndex + 1 ] as VNode ) . el : null ; if ( newIndexToOldIndexMap [ i ] === 0 ) { patch ( null , nextChild , container , anchor ) ; } else if ( moved ) { if ( j < 0 || i !== increasingNewIndexSequence [ j ] ) { hostInsert ( nextChild . el ! , container , anchor ) ; } else { j -- ; } } } } } ; const processComponent = ( n1 , n2 , container ) => { if ( n1 == null ) { mountComponent ( n2 , container ) ; } else { const instance = ( n2 . component = n1 . component ) ! ; instance . next = n2 ; instance . update ( ) ; } } ; const mountComponent = ( initialVNode , container ) => { const instance = ( initialVNode . component = createComponentInstance ( initialVNode ) ) ; setupComponent ( instance ) ; setupRenderEffect ( instance , container ) ; } ; const setupRenderEffect = ( instance , container ) => { instance . update = effect ( ( ) => { if ( ! instance . isMounted ) { let { bm , m } = instance ; bm && invokeArrayFns ( bm ) ; const subTree = ( instance . subTree = renderComponentRoot ( instance ) ) ; patch ( null , subTree , container ) ; m && queuePostRenderEffect ( m ) ; instance . isMounted = true ; } else { const { bu , u } = instance ; bu && invokeArrayFns ( bu ) ; const nextTree = renderComponentRoot ( instance ) ; const prevTree = instance . subTree ; instance . subTree = nextTree ; patch ( prevTree , nextTree , hostParentNode ( prevTree . el ! ) ! ) ; u && queuePostRenderEffect ( u ) ; } } , { scheduler : job => { queueJob ( job ) ; } } ) ; } ; const render = ( vnode , container ) => { patch ( null , vnode , container ) ; } ; return { createApp : createAppAPI ( render ) } ; } function createAppAPI < HostElement > ( render ) { return function createApp ( root ) { const app = { mount ( container ) { let vnode = createVnode ( root ) ; render ( vnode , container ) ; } } ; return app ; } ; } export { createRenderer , createVnode , openBlock , createBlock } ;