1- import { findShadowRoots , generateHTML } from 'declarative-shadow-dom-polyfill' ;
21import { ReadableStream } from 'web-streams-polyfill' ;
32import {
43 diffKeys ,
@@ -12,6 +11,12 @@ import {
1211
1312import { DataObject , VNode } from './VDOM' ;
1413
14+ export interface UpdateTask {
15+ index ?: number ;
16+ oldVNode ?: VNode ;
17+ newVNode ?: VNode ;
18+ }
19+
1520export class DOMRenderer {
1621 eventPattern = / ^ o n [ A - Z ] / ;
1722 ariaPattern = / ^ a i r a [ A - Z ] / ;
@@ -32,6 +37,7 @@ export class DOMRenderer {
3237 : this . eventPattern . test ( key )
3338 ? key . toLowerCase ( )
3439 : key ;
40+ protected attrsNameOf = ( key : string ) => VNode . propsMap [ key ] || key ;
3541
3642 protected updateProps < N extends DataObject , P extends DataObject > (
3743 node : N ,
@@ -50,20 +56,6 @@ export class DOMRenderer {
5056 else Reflect . set ( node , key , newProps [ key ] ) ;
5157 }
5258
53- protected createNode ( vNode : VNode , reusedVNodes ?: Record < string , VNode [ ] > ) {
54- if ( vNode . text ) return vNode . createDOM ( this . document ) ;
55-
56- const reusedVNode = vNode . selector && reusedVNodes ?. [ vNode . selector ] ?. shift ( ) ;
57-
58- vNode . node = reusedVNode ?. node || vNode . createDOM ( this . document ) ;
59-
60- const { node } = this . patch (
61- reusedVNode || new VNode ( { tagName : vNode . tagName , node : vNode . node } ) ,
62- vNode
63- ) ;
64- return node ;
65- }
66-
6759 protected deleteNode ( { ref, node, children } : VNode ) {
6860 if ( node instanceof DocumentFragment ) children ?. forEach ( this . deleteNode ) ;
6961 else if ( node ) {
@@ -73,53 +65,47 @@ export class DOMRenderer {
7365 }
7466 }
7567
76- protected commitChildren ( root : ParentNode , newNodes : ChildNode [ ] ) {
77- for ( const oldNode of [ ...root . childNodes ] ) {
78- const index = newNodes . indexOf ( oldNode ) ;
79-
80- if ( index < 0 ) continue ;
81- else if ( index === 0 ) {
82- newNodes . shift ( ) ;
83- continue ;
84- }
85- const beforeNodes = newNodes . slice ( 0 , index ) ;
86-
87- if ( ! beforeNodes [ 0 ] ) continue ;
68+ protected commitChild ( root : ParentNode , node : Node , index = 0 ) {
69+ const targetNode = root . childNodes [ index ] ;
8870
89- oldNode . before ( ... beforeNodes ) ;
71+ if ( targetNode === node ) return ;
9072
91- newNodes = newNodes . slice ( index + 1 ) ;
92- }
93-
94- if ( newNodes [ 0 ] ) root . append ( ...newNodes ) ;
73+ if ( ! targetNode ) root . append ( node ) ;
74+ else targetNode . before ( node ) ;
9575 }
9676
97- protected updateChildren ( node : ParentNode , oldList : VNode [ ] , newList : VNode [ ] ) {
98- const { map, group } = diffKeys ( oldList . map ( this . keyOf ) , newList . map ( this . keyOf ) ) ;
77+ protected * diffVChildren ( oldVNode : VNode , newVNode : VNode ) : Generator < UpdateTask > {
78+ newVNode . children = newVNode . children . map ( vNode => new VNode ( vNode ) ) ;
79+
80+ const { map, group } = diffKeys (
81+ oldVNode . children ! . map ( this . keyOf ) ,
82+ newVNode . children ! . map ( this . keyOf )
83+ ) ;
9984 const deletingGroup =
10085 group [ DiffStatus . Old ] &&
10186 groupBy (
102- group [ DiffStatus . Old ] . map ( ( [ key ] ) => this . vNodeOf ( oldList , key ) ) ,
87+ group [ DiffStatus . Old ] . map ( ( [ key ] ) => this . vNodeOf ( oldVNode . children ! , key ) ) ,
10388 ( { selector } ) => selector + ''
10489 ) ;
105- const newNodes = newList . map ( ( vNode , index ) => {
106- const key = this . keyOf ( vNode , index ) ;
10790
108- if ( map [ key ] !== DiffStatus . Same ) return this . createNode ( vNode , deletingGroup ) ;
91+ for ( const [ index , newVChild ] of newVNode . children ! . entries ( ) ) {
92+ const key = this . keyOf ( newVChild , index ) ;
10993
110- const oldVNode = this . vNodeOf ( oldList , key ) ! ;
94+ let oldVChild =
95+ map [ key ] === DiffStatus . Same
96+ ? this . vNodeOf ( oldVNode . children ! , key )
97+ : deletingGroup ?. [ newVChild . selector ] ?. shift ( ) ;
11198
112- return vNode . text != null
113- ? ( vNode . node = oldVNode . node )
114- : this . patch ( oldVNode , vNode ) . node ;
115- } ) ;
99+ yield { index, oldVNode : oldVChild , newVNode : newVChild } ;
116100
117- for ( const selector in deletingGroup )
118- for ( const vNode of deletingGroup [ selector ] ) this . deleteNode ( vNode ) ;
101+ if ( oldVChild ?. children [ 0 ] || newVChild . children [ 0 ] ) {
102+ oldVChild ||= new VNode ( { ... newVChild , children : [ ] } ) ;
119103
120- this . commitChildren ( node , newNodes as ChildNode [ ] ) ;
121-
122- for ( const { ref, node } of newList ) ref ?.( node ) ;
104+ yield * this . diffVChildren ( oldVChild , newVChild ) ;
105+ }
106+ }
107+ for ( const selector in deletingGroup )
108+ for ( const oldVNode of deletingGroup [ selector ] ) yield { oldVNode } ;
123109 }
124110
125111 protected handleCustomEvent ( node : EventTarget , event : string ) {
@@ -139,12 +125,12 @@ export class DOMRenderer {
139125 this . eventPattern . test ( key )
140126 ? ( node [ key . toLowerCase ( ) ] = null )
141127 : node . removeAttribute (
142- this . ariaPattern . test ( key ) ? toHyphenCase ( key ) : VNode . propsMap [ key ] || key
128+ this . ariaPattern . test ( key ) ? toHyphenCase ( key ) : this . attrsNameOf ( key )
143129 ) ;
144130 protected setProperty = ( node : Element , key : string , value : string ) => {
145131 const isXML = templateOf ( node . tagName ) && elementTypeOf ( node . tagName ) === 'xml' ;
146132
147- if ( isXML || key . includes ( '-' ) ) node . setAttribute ( key , value ) ;
133+ if ( isXML || key . includes ( '-' ) ) node . setAttribute ( this . attrsNameOf ( key ) , value ) ;
148134 else
149135 try {
150136 const name = this . propsKeyOf ( key ) ;
@@ -154,11 +140,11 @@ export class DOMRenderer {
154140
155141 node [ name ] = value ;
156142 } catch {
157- node . setAttribute ( key , value ) ;
143+ node . setAttribute ( this . attrsNameOf ( key ) , value ) ;
158144 }
159145 } ;
160146
161- patch ( oldVNode : VNode , newVNode : VNode ) : VNode {
147+ protected patchNode ( oldVNode : VNode , newVNode : VNode ) {
162148 this . updateProps (
163149 oldVNode . node as Element ,
164150 oldVNode . props ,
@@ -170,17 +156,44 @@ export class DOMRenderer {
170156 ( oldVNode . node as HTMLElement ) . style ,
171157 oldVNode . style ,
172158 newVNode . style ,
173- ( node , key ) => node . removeProperty ( toHyphenCase ( key ) ) ,
174- ( node , key , value ) => node . setProperty ( toHyphenCase ( key ) , value )
175- ) ;
176- this . updateChildren (
177- oldVNode . node as ParentNode ,
178- oldVNode . children || [ ] ,
179- ( newVNode . children = newVNode . children ?. map ( vNode => new VNode ( vNode ) ) || [ ] )
159+ ( style , key ) => style . removeProperty ( toHyphenCase ( key ) ) ,
160+ ( style , key , value ) => style . setProperty ( toHyphenCase ( key ) , value )
180161 ) ;
181- newVNode . node = oldVNode . node ;
162+ newVNode . node ||= oldVNode . node ;
163+ }
164+
165+ patch ( oldVRoot : VNode , newVRoot : VNode ) {
166+ if ( VNode . isFragment ( newVRoot ) )
167+ newVRoot = new VNode ( { ...oldVRoot , children : newVRoot . children } ) ;
168+
169+ this . patchNode ( oldVRoot , newVRoot ) ;
170+
171+ for ( let { index, oldVNode, newVNode } of this . diffVChildren ( oldVRoot , newVRoot ) ) {
172+ if ( ! newVNode ) {
173+ this . deleteNode ( oldVNode ) ;
174+ continue ;
175+ }
176+ const inserting = ! oldVNode ;
177+
178+ if ( oldVNode ) newVNode . node = oldVNode . node ;
179+ else {
180+ newVNode . createDOM ( this . document ) ;
181+
182+ const { tagName, node, parent } = newVNode ;
183+
184+ oldVNode = new VNode ( { tagName, node, parent } ) ;
185+ }
186+
187+ if ( newVNode . text ) oldVNode . node . nodeValue = newVNode . text ;
188+ else if ( ! VNode . isFragment ( newVNode ) ) this . patchNode ( oldVNode , newVNode ) ;
182189
183- return newVNode ;
190+ if ( oldVNode . parent ) {
191+ this . commitChild ( oldVNode . parent . node as ParentNode , newVNode . node , index ) ;
192+
193+ if ( inserting ) newVNode . ref ?.( newVNode . node ) ;
194+ }
195+ }
196+ return newVRoot ;
184197 }
185198
186199 render ( vNode : VNode , node : ParentNode = globalThis . document ?. body ) {
@@ -195,27 +208,11 @@ export class DOMRenderer {
195208 return root ;
196209 }
197210
198- protected buildRenderTree ( tree : VNode ) {
199- const { body } = this . document . implementation . createHTMLDocument ( ) ;
200-
201- this . render ( tree , body ) ;
202-
203- const shadowRoots = [ ...findShadowRoots ( body ) ] ;
204-
205- return { body, shadowRoots } ;
206- }
207-
208211 renderToStaticMarkup ( tree : VNode ) {
209- const { body, shadowRoots } = this . buildRenderTree ( tree ) ;
210-
211- return body . getHTML ( { serializableShadowRoots : true , shadowRoots } ) ;
212+ return [ ...tree . generateXML ( ) ] . join ( '' ) ;
212213 }
213214
214215 renderToReadableStream ( tree : VNode ) {
215- const { body, shadowRoots } = this . buildRenderTree ( tree ) ;
216-
217- return ReadableStream . from (
218- generateHTML ( body , { serializableShadowRoots : true , shadowRoots } )
219- ) ;
216+ return ReadableStream . from ( tree . generateXML ( ) ) ;
220217 }
221218}
0 commit comments