1
+ enum NodeType {
2
+ ELEMENT_NODE = 1 ,
3
+ TEXT_NODE = 3 ,
4
+ }
5
+
1
6
type Writable = {
2
7
write ( html : string ) : void ;
3
8
abort ( err : Error ) : void ;
@@ -50,22 +55,15 @@ export = function writableDOM(
50
55
const root = ( doc . body . firstChild as HTMLTemplateElement ) . content ;
51
56
const walker = doc . createTreeWalker ( root ) ;
52
57
const targetNodes = new WeakMap < Node , Node > ( [ [ root , target ] ] ) ;
53
- let pendingText : Text | null = null ;
58
+ const targetFragments = new WeakMap < ParentNode , DocumentFragment > ( ) ;
59
+ let appendedTargets = new Set < ParentNode > ( ) ;
54
60
let scanNode : Node | null = null ;
55
61
let resolve : void | ( ( ) => void ) ;
56
62
let isBlocked = false ;
57
- let inlineHostNode : Node | null = null ;
58
63
59
64
return {
60
65
write ( chunk : string ) {
61
66
doc . write ( chunk ) ;
62
-
63
- if ( pendingText && ! inlineHostNode ) {
64
- // When we left on text, it's possible more text was written to the same node.
65
- // here we copy in the final text content from the detached dom to the live dom.
66
- ( targetNodes . get ( pendingText ) as Text ) . data = pendingText . data ;
67
- }
68
-
69
67
walk ( ) ;
70
68
} ,
71
69
abort ( ) {
@@ -74,22 +72,18 @@ export = function writableDOM(
74
72
}
75
73
} ,
76
74
close ( ) {
77
- const promise = isBlocked
75
+ return isBlocked
78
76
? new Promise < void > ( ( _ ) => ( resolve = _ ) )
79
77
: Promise . resolve ( ) ;
80
-
81
- return promise . then ( ( ) => {
82
- appendInlineTextIfNeeded ( pendingText , inlineHostNode ) ;
83
- } ) ;
84
78
} ,
85
79
} ;
86
80
87
81
function walk ( ) : void {
82
+ const startNode = walker . currentNode ;
88
83
let node : Node | null ;
89
84
if ( isBlocked ) {
90
85
// If we are blocked, we walk ahead and preload
91
86
// any assets we can ahead of the last checked node.
92
- const blockedNode = walker . currentNode ;
93
87
if ( scanNode ) walker . currentNode = scanNode ;
94
88
95
89
while ( ( node = walker . nextNode ( ) ) ) {
@@ -100,48 +94,76 @@ export = function writableDOM(
100
94
}
101
95
}
102
96
103
- walker . currentNode = blockedNode ;
97
+ walker . currentNode = startNode ;
104
98
} else {
105
- while ( ( node = walker . nextNode ( ) ) ) {
106
- const clone = document . importNode ( node , false ) ;
107
- const previousPendingText = pendingText ;
108
- if ( node . nodeType === Node . TEXT_NODE ) {
109
- pendingText = node as Text ;
99
+ if ( startNode . nodeType === NodeType . TEXT_NODE ) {
100
+ if ( isInlineScriptOrStyleTag ( startNode . parentNode ! ) ) {
101
+ if ( resolve || walker . nextNode ( ) ) {
102
+ targetNodes
103
+ . get ( startNode . parentNode ! ) !
104
+ . appendChild ( document . importNode ( startNode , false ) ) ;
105
+ walker . currentNode = startNode ;
106
+ }
110
107
} else {
111
- pendingText = null ;
112
-
113
- if ( isBlocking ( clone ) ) {
114
- isBlocked = true ;
115
- clone . onload = clone . onerror = ( ) => {
116
- isBlocked = false ;
117
- // Continue the normal content injecting walk.
118
- if ( clone . parentNode ) walk ( ) ;
119
- } ;
108
+ ( targetNodes . get ( startNode ) as Text ) . data = ( startNode as Text ) . data ;
109
+ }
110
+ }
111
+
112
+ while ( ( node = walker . nextNode ( ) ) ) {
113
+ if (
114
+ ! resolve &&
115
+ node . nodeType === NodeType . TEXT_NODE &&
116
+ isInlineScriptOrStyleTag ( node . parentNode ! )
117
+ ) {
118
+ if ( walker . nextNode ( ) ) {
119
+ walker . currentNode = node ;
120
+ } else {
121
+ break ;
120
122
}
121
123
}
122
124
123
- const parentNode = targetNodes . get ( node . parentNode ! ) ! ;
125
+ const parentNode = targetNodes . get ( node . parentNode ! ) as ParentNode ;
126
+ const clone = document . importNode ( node , false ) ;
127
+ let insertParent : ParentNode = parentNode ;
124
128
targetNodes . set ( node , clone ) ;
125
129
126
- if ( isInlineHost ( parentNode ! ) ) {
127
- inlineHostNode = parentNode ;
128
- } else {
129
- appendInlineTextIfNeeded ( previousPendingText , inlineHostNode ) ;
130
- inlineHostNode = null ;
130
+ if ( parentNode . isConnected ) {
131
+ appendedTargets . add ( parentNode ) ;
132
+ ( insertParent = targetFragments . get ( parentNode ) ! ) ||
133
+ targetFragments . set (
134
+ parentNode ,
135
+ ( insertParent = new DocumentFragment ( ) ) ,
136
+ ) ;
137
+ }
131
138
132
- if ( parentNode === target ) {
133
- target . insertBefore ( clone , nextSibling ) ;
134
- } else {
135
- parentNode . appendChild ( clone ) ;
136
- }
139
+ if ( isBlocking ( clone ) ) {
140
+ isBlocked = true ;
141
+ clone . onload = clone . onerror = ( ) => {
142
+ isBlocked = false ;
143
+ // Continue the normal content injecting walk.
144
+ if ( clone . parentNode ) walk ( ) ;
145
+ } ;
137
146
}
138
147
139
- // Start walking for preloads.
140
- if ( isBlocked ) return walk ( ) ;
148
+ insertParent . appendChild ( clone ) ;
149
+ if ( isBlocked ) break ;
150
+ }
151
+
152
+ for ( const targetNode of appendedTargets ) {
153
+ targetNode . insertBefore (
154
+ targetFragments . get ( targetNode ) ! ,
155
+ targetNode === target ? nextSibling : null ,
156
+ ) ;
141
157
}
142
158
143
- // Some blocking content could have prevented load.
144
- if ( resolve ) resolve ( ) ;
159
+ appendedTargets = new Set ( ) ;
160
+
161
+ if ( isBlocked ) {
162
+ walk ( ) ;
163
+ } else if ( resolve ) {
164
+ // Some blocking content could have prevented load.
165
+ resolve ( ) ;
166
+ }
145
167
}
146
168
}
147
169
} as {
@@ -154,7 +176,7 @@ export = function writableDOM(
154
176
155
177
function isBlocking ( node : any ) : node is HTMLElement {
156
178
return (
157
- node . nodeType === Node . ELEMENT_NODE &&
179
+ node . nodeType === NodeType . ELEMENT_NODE &&
158
180
( ( node . tagName === "SCRIPT" &&
159
181
node . src &&
160
182
! (
@@ -171,7 +193,7 @@ function isBlocking(node: any): node is HTMLElement {
171
193
172
194
function getPreloadLink ( node : any ) {
173
195
let link : HTMLLinkElement | undefined ;
174
- if ( node . nodeType === Node . ELEMENT_NODE ) {
196
+ if ( node . nodeType === NodeType . ELEMENT_NODE ) {
175
197
switch ( node . tagName ) {
176
198
case "SCRIPT" :
177
199
if ( node . src && ! node . noModule ) {
@@ -223,16 +245,7 @@ function getPreloadLink(node: any) {
223
245
return link ;
224
246
}
225
247
226
- function appendInlineTextIfNeeded (
227
- pendingText : Text | null ,
228
- inlineTextHostNode : Node | null ,
229
- ) {
230
- if ( pendingText && inlineTextHostNode ) {
231
- inlineTextHostNode . appendChild ( pendingText ) ;
232
- }
233
- }
234
-
235
- function isInlineHost ( node : Node ) {
248
+ function isInlineScriptOrStyleTag ( node : Node ) {
236
249
const { tagName } = node as Element ;
237
250
return (
238
251
( tagName === "SCRIPT" && ! ( node as HTMLScriptElement ) . src ) ||
0 commit comments