1
- import { findShadowRoots , generateHTML } from 'declarative-shadow-dom-polyfill' ;
2
1
import { ReadableStream } from 'web-streams-polyfill' ;
3
2
import {
4
3
diffKeys ,
@@ -12,6 +11,12 @@ import {
12
11
13
12
import { DataObject , VNode } from './VDOM' ;
14
13
14
+ export interface UpdateTask {
15
+ index ?: number ;
16
+ oldVNode ?: VNode ;
17
+ newVNode ?: VNode ;
18
+ }
19
+
15
20
export class DOMRenderer {
16
21
eventPattern = / ^ o n [ A - Z ] / ;
17
22
ariaPattern = / ^ a i r a [ A - Z ] / ;
@@ -32,6 +37,7 @@ export class DOMRenderer {
32
37
: this . eventPattern . test ( key )
33
38
? key . toLowerCase ( )
34
39
: key ;
40
+ protected attrsNameOf = ( key : string ) => VNode . propsMap [ key ] || key ;
35
41
36
42
protected updateProps < N extends DataObject , P extends DataObject > (
37
43
node : N ,
@@ -50,20 +56,6 @@ export class DOMRenderer {
50
56
else Reflect . set ( node , key , newProps [ key ] ) ;
51
57
}
52
58
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
-
67
59
protected deleteNode ( { ref, node, children } : VNode ) {
68
60
if ( node instanceof DocumentFragment ) children ?. forEach ( this . deleteNode ) ;
69
61
else if ( node ) {
@@ -73,53 +65,47 @@ export class DOMRenderer {
73
65
}
74
66
}
75
67
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 ] ;
88
70
89
- oldNode . before ( ... beforeNodes ) ;
71
+ if ( targetNode === node ) return ;
90
72
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 ) ;
95
75
}
96
76
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
+ ) ;
99
84
const deletingGroup =
100
85
group [ DiffStatus . Old ] &&
101
86
groupBy (
102
- group [ DiffStatus . Old ] . map ( ( [ key ] ) => this . vNodeOf ( oldList , key ) ) ,
87
+ group [ DiffStatus . Old ] . map ( ( [ key ] ) => this . vNodeOf ( oldVNode . children ! , key ) ) ,
103
88
( { selector } ) => selector + ''
104
89
) ;
105
- const newNodes = newList . map ( ( vNode , index ) => {
106
- const key = this . keyOf ( vNode , index ) ;
107
90
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 ) ;
109
93
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 ( ) ;
111
98
112
- return vNode . text != null
113
- ? ( vNode . node = oldVNode . node )
114
- : this . patch ( oldVNode , vNode ) . node ;
115
- } ) ;
99
+ yield { index, oldVNode : oldVChild , newVNode : newVChild } ;
116
100
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 : [ ] } ) ;
119
103
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 } ;
123
109
}
124
110
125
111
protected handleCustomEvent ( node : EventTarget , event : string ) {
@@ -139,12 +125,12 @@ export class DOMRenderer {
139
125
this . eventPattern . test ( key )
140
126
? ( node [ key . toLowerCase ( ) ] = null )
141
127
: node . removeAttribute (
142
- this . ariaPattern . test ( key ) ? toHyphenCase ( key ) : VNode . propsMap [ key ] || key
128
+ this . ariaPattern . test ( key ) ? toHyphenCase ( key ) : this . attrsNameOf ( key )
143
129
) ;
144
130
protected setProperty = ( node : Element , key : string , value : string ) => {
145
131
const isXML = templateOf ( node . tagName ) && elementTypeOf ( node . tagName ) === 'xml' ;
146
132
147
- if ( isXML || key . includes ( '-' ) ) node . setAttribute ( key , value ) ;
133
+ if ( isXML || key . includes ( '-' ) ) node . setAttribute ( this . attrsNameOf ( key ) , value ) ;
148
134
else
149
135
try {
150
136
const name = this . propsKeyOf ( key ) ;
@@ -154,11 +140,11 @@ export class DOMRenderer {
154
140
155
141
node [ name ] = value ;
156
142
} catch {
157
- node . setAttribute ( key , value ) ;
143
+ node . setAttribute ( this . attrsNameOf ( key ) , value ) ;
158
144
}
159
145
} ;
160
146
161
- patch ( oldVNode : VNode , newVNode : VNode ) : VNode {
147
+ protected patchNode ( oldVNode : VNode , newVNode : VNode ) {
162
148
this . updateProps (
163
149
oldVNode . node as Element ,
164
150
oldVNode . props ,
@@ -170,17 +156,44 @@ export class DOMRenderer {
170
156
( oldVNode . node as HTMLElement ) . style ,
171
157
oldVNode . style ,
172
158
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 )
180
161
) ;
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 ) ;
182
189
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 ;
184
197
}
185
198
186
199
render ( vNode : VNode , node : ParentNode = globalThis . document ?. body ) {
@@ -195,27 +208,11 @@ export class DOMRenderer {
195
208
return root ;
196
209
}
197
210
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
-
208
211
renderToStaticMarkup ( tree : VNode ) {
209
- const { body, shadowRoots } = this . buildRenderTree ( tree ) ;
210
-
211
- return body . getHTML ( { serializableShadowRoots : true , shadowRoots } ) ;
212
+ return [ ...tree . generateXML ( ) ] . join ( '' ) ;
212
213
}
213
214
214
215
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 ( ) ) ;
220
217
}
221
218
}
0 commit comments