1+ enum NodeType {
2+ ELEMENT_NODE = 1 ,
3+ TEXT_NODE = 3 ,
4+ }
5+
16type Writable = {
27 write ( html : string ) : void ;
38 abort ( err : Error ) : void ;
@@ -50,22 +55,15 @@ export = function writableDOM(
5055 const root = ( doc . body . firstChild as HTMLTemplateElement ) . content ;
5156 const walker = doc . createTreeWalker ( root ) ;
5257 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 > ( ) ;
5460 let scanNode : Node | null = null ;
5561 let resolve : void | ( ( ) => void ) ;
5662 let isBlocked = false ;
57- let inlineHostNode : Node | null = null ;
5863
5964 return {
6065 write ( chunk : string ) {
6166 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-
6967 walk ( ) ;
7068 } ,
7169 abort ( ) {
@@ -74,22 +72,18 @@ export = function writableDOM(
7472 }
7573 } ,
7674 close ( ) {
77- const promise = isBlocked
75+ return isBlocked
7876 ? new Promise < void > ( ( _ ) => ( resolve = _ ) )
7977 : Promise . resolve ( ) ;
80-
81- return promise . then ( ( ) => {
82- appendInlineTextIfNeeded ( pendingText , inlineHostNode ) ;
83- } ) ;
8478 } ,
8579 } ;
8680
8781 function walk ( ) : void {
82+ const startNode = walker . currentNode ;
8883 let node : Node | null ;
8984 if ( isBlocked ) {
9085 // If we are blocked, we walk ahead and preload
9186 // any assets we can ahead of the last checked node.
92- const blockedNode = walker . currentNode ;
9387 if ( scanNode ) walker . currentNode = scanNode ;
9488
9589 while ( ( node = walker . nextNode ( ) ) ) {
@@ -100,48 +94,76 @@ export = function writableDOM(
10094 }
10195 }
10296
103- walker . currentNode = blockedNode ;
97+ walker . currentNode = startNode ;
10498 } 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+ }
110107 } 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 ;
120122 }
121123 }
122124
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 ;
124128 targetNodes . set ( node , clone ) ;
125129
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+ }
131138
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+ } ;
137146 }
138147
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+ ) ;
141157 }
142158
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+ }
145167 }
146168 }
147169} as {
@@ -154,7 +176,7 @@ export = function writableDOM(
154176
155177function isBlocking ( node : any ) : node is HTMLElement {
156178 return (
157- node . nodeType === Node . ELEMENT_NODE &&
179+ node . nodeType === NodeType . ELEMENT_NODE &&
158180 ( ( node . tagName === "SCRIPT" &&
159181 node . src &&
160182 ! (
@@ -171,7 +193,7 @@ function isBlocking(node: any): node is HTMLElement {
171193
172194function getPreloadLink ( node : any ) {
173195 let link : HTMLLinkElement | undefined ;
174- if ( node . nodeType === Node . ELEMENT_NODE ) {
196+ if ( node . nodeType === NodeType . ELEMENT_NODE ) {
175197 switch ( node . tagName ) {
176198 case "SCRIPT" :
177199 if ( node . src && ! node . noModule ) {
@@ -223,16 +245,7 @@ function getPreloadLink(node: any) {
223245 return link ;
224246}
225247
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 ) {
236249 const { tagName } = node as Element ;
237250 return (
238251 ( tagName === "SCRIPT" && ! ( node as HTMLScriptElement ) . src ) ||
0 commit comments