22import { VNodeDataChar , VNodeDataSeparator } from '../shared/vnode-data-types' ;
33import type { ContainerElement , QDocument } from './types' ;
44import type { ElementVNode } from '../shared/vnode/element-vnode' ;
5+ import { createMacroTask } from '../shared/platform/next-tick' ;
6+
7+ const VNODE_DATA_YIELD_INTERVAL = 1000 / 60 ;
8+ const Q_CONTAINER = 'q:container' ;
9+ const Q_CONTAINER_END = '/' + Q_CONTAINER ;
10+ const Q_PROPS_SEPARATOR = ':' ;
11+ const Q_SHADOW_ROOT = 'q:shadowroot' ;
12+ const Q_IGNORE = 'q:ignore' ;
13+ const Q_IGNORE_END = '/' + Q_IGNORE ;
14+ const Q_CONTAINER_ISLAND = 'q:container-island' ;
15+ const Q_CONTAINER_ISLAND_END = '/' + Q_CONTAINER_ISLAND ;
16+
17+ const enum NodeType {
18+ CONTAINER_MASK /* ***************** */ = 0b0000001 ,
19+ ELEMENT /* ************************ */ = 0b0000010 , // regular element
20+ ELEMENT_CONTAINER /* ************** */ = 0b0000011 , // container element need to descend into it
21+ ELEMENT_SHADOW_ROOT_WRAPPER /* **** */ = 0b0000110 , // shadow root wrapper element with q:shadowroot attribute
22+ COMMENT_SKIP_START /* ************* */ = 0b0001001 , // Comment but skip the content until COMMENT_SKIP_END
23+ COMMENT_SKIP_END /* *************** */ = 0b0001000 , // Comment end
24+ COMMENT_IGNORE_START /* *********** */ = 0b0010000 , // Comment ignore, descend into children and skip the content until COMMENT_ISLAND_START
25+ COMMENT_IGNORE_END /* ************* */ = 0b0100000 , // Comment ignore end
26+ COMMENT_ISLAND_START /* *********** */ = 0b1000001 , // Comment island, count elements for parent container until COMMENT_ISLAND_END
27+ COMMENT_ISLAND_END /* ************* */ = 0b1000000 , // Comment island end
28+ OTHER /* ************************** */ = 0b0000000 ,
29+ }
30+
31+ interface ProcessVNodeDataState {
32+ $document$ : QDocument ;
33+ $iterator$ : Generator < void , void , void > ;
34+ $schedule$ : ( ) => void ;
35+ $scheduled$ : boolean ;
36+ }
537
638/**
739 * Process the VNodeData script tags and store the VNodeData in the VNodeDataMap.
@@ -54,15 +86,102 @@ import type { ElementVNode } from '../shared/vnode/element-vnode';
5486 * - Attach all `qwik/vnode` scripts (not the data contain within them) to the `q:container` element.
5587 * - Walk the tree and process each `q:container` element.
5688 */
57- export function processVNodeData ( document : Document ) {
58- const Q_CONTAINER = 'q:container' ;
59- const Q_CONTAINER_END = '/' + Q_CONTAINER ;
60- const Q_PROPS_SEPARATOR = ':' ;
61- const Q_SHADOW_ROOT = 'q:shadowroot' ;
62- const Q_IGNORE = 'q:ignore' ;
63- const Q_IGNORE_END = '/' + Q_IGNORE ;
64- const Q_CONTAINER_ISLAND = 'q:container-island' ;
65- const Q_CONTAINER_ISLAND_END = '/' + Q_CONTAINER_ISLAND ;
89+ export function processVNodeData ( document : Document ) : void {
90+ const qDocument = document as QDocument ;
91+ if ( qDocument . qVNodeDataStarted || qDocument . qVNodeDataReady ) {
92+ return ;
93+ }
94+ qDocument . qVNodeDataStarted = true ;
95+ qDocument . qVNodeData || ( qDocument . qVNodeData = new WeakMap < Element , string > ( ) ) ;
96+ const state : ProcessVNodeDataState = {
97+ $document$ : qDocument ,
98+ $iterator$ : processVNodeDataIterator ( document ) ,
99+ $schedule$ : undefined ! ,
100+ $scheduled$ : false ,
101+ } ;
102+ state . $schedule$ = createMacroTask ( ( ) => runProcessVNodeData ( state ) ) ;
103+ qDocument . qVNodeDataState = state ;
104+ scheduleProcessVNodeData ( state ) ;
105+ }
106+
107+ export const onVNodeDataReady = ( document : Document , callback : ( ) => void ) : void => {
108+ const qDocument = document as QDocument ;
109+ if ( qDocument . qVNodeDataReady ) {
110+ callback ( ) ;
111+ } else {
112+ ( qDocument . qVNodeDataCallbacks ||= [ ] ) . push ( callback ) ;
113+ }
114+ } ;
115+
116+ export const whenVNodeDataReady = < T > (
117+ document : Document ,
118+ callback : ( ) => T | Promise < T >
119+ ) : T | Promise < T > => {
120+ const qDocument = document as QDocument ;
121+ if ( qDocument . qVNodeDataReady ) {
122+ return callback ( ) ;
123+ }
124+ return new Promise < T > ( ( resolve , reject ) => {
125+ onVNodeDataReady ( document , ( ) => {
126+ try {
127+ resolve ( callback ( ) ) ;
128+ } catch ( error ) {
129+ reject ( error ) ;
130+ }
131+ } ) ;
132+ } ) ;
133+ } ;
134+
135+ function scheduleProcessVNodeData ( state : ProcessVNodeDataState ) : void {
136+ if ( ! state . $scheduled$ ) {
137+ state . $scheduled$ = true ;
138+ state . $schedule$ ( ) ;
139+ }
140+ }
141+
142+ function runProcessVNodeData ( state : ProcessVNodeDataState ) : void {
143+ if ( state . $document$ . qVNodeDataState !== state ) {
144+ return ;
145+ }
146+ state . $scheduled$ = false ;
147+ const deadline = performance . now ( ) + VNODE_DATA_YIELD_INTERVAL ;
148+ let count = 0 ;
149+ try {
150+ while ( true ) {
151+ const result = state . $iterator$ . next ( ) ;
152+ if ( result . done ) {
153+ markVNodeDataReady ( state . $document$ ) ;
154+ return ;
155+ }
156+ // Sampling the clock every 32 steps keeps `performance.now()` out of the hottest path.
157+ if ( ( ++ count & 31 ) === 0 && performance . now ( ) >= deadline ) {
158+ scheduleProcessVNodeData ( state ) ;
159+ return ;
160+ }
161+ }
162+ } catch ( error ) {
163+ state . $document$ . qVNodeDataStarted = false ;
164+ state . $document$ . qVNodeDataState = undefined ;
165+ throw error ;
166+ }
167+ }
168+
169+ function markVNodeDataReady ( document : QDocument ) : void {
170+ if ( document . qVNodeDataReady ) {
171+ return ;
172+ }
173+ document . qVNodeDataReady = true ;
174+ document . qVNodeDataState = undefined ;
175+ const callbacks = document . qVNodeDataCallbacks ;
176+ document . qVNodeDataCallbacks = undefined ;
177+ if ( callbacks ) {
178+ for ( let i = 0 ; i < callbacks . length ; i ++ ) {
179+ callbacks [ i ] ( ) ;
180+ }
181+ }
182+ }
183+
184+ function * processVNodeDataIterator ( document : Document ) : Generator < void , void , void > {
66185 const qDocument = document as QDocument ;
67186 const vNodeDataMap =
68187 qDocument . qVNodeData || ( qDocument . qVNodeData = new WeakMap < Element , string > ( ) ) ;
@@ -84,41 +203,33 @@ export function processVNodeData(document: Document) {
84203 const getNodeType = getter ( prototype , 'nodeType' ) as ( this : Node ) => number ;
85204
86205 // Process all of the `qwik/vnode` script tags by attaching them to the corresponding containers.
87- const attachVnodeDataAndRefs = ( element : Document | ShadowRoot ) => {
206+ const attachVnodeDataAndRefs = function * (
207+ element : Document | ShadowRoot
208+ ) : Generator < void , void , void > {
88209 const scripts = element . querySelectorAll ( 'script[type="qwik/vnode"]' ) ;
89210 for ( let i = 0 ; i < scripts . length ; i ++ ) {
90211 const script = scripts [ i ] ;
91212 const qContainerElement = script . closest ( '[q\\:container]' ) as ContainerElement | null ;
92213 qContainerElement ! . qVnodeData = script . textContent ! ;
93214 qContainerElement ! . qVNodeRefs = new Map < number , Element | ElementVNode > ( ) ;
215+ yield ;
94216 }
95217 const shadowRoots = element . querySelectorAll ( '[q\\:shadowroot]' ) ;
96218 for ( let i = 0 ; i < shadowRoots . length ; i ++ ) {
97219 const parent = shadowRoots [ i ] ;
98220 const shadowRoot = parent . shadowRoot ;
99- shadowRoot && attachVnodeDataAndRefs ( shadowRoot ) ;
221+ if ( shadowRoot ) {
222+ yield * attachVnodeDataAndRefs ( shadowRoot ) ;
223+ }
224+ yield ;
100225 }
101226 } ;
102- attachVnodeDataAndRefs ( document ) ;
227+ yield * attachVnodeDataAndRefs ( document ) ;
103228
104229 ///////////////////////////////
105230 // Functions to consume the tree.
106231 ///////////////////////////////
107232
108- const enum NodeType {
109- CONTAINER_MASK /* ***************** */ = 0b0000001 ,
110- ELEMENT /* ************************ */ = 0b0000010 , // regular element
111- ELEMENT_CONTAINER /* ************** */ = 0b0000011 , // container element need to descend into it
112- ELEMENT_SHADOW_ROOT_WRAPPER /* **** */ = 0b0000110 , // shadow root wrapper element with q:shadowroot attribute
113- COMMENT_SKIP_START /* ************* */ = 0b0001001 , // Comment but skip the content until COMMENT_SKIP_END
114- COMMENT_SKIP_END /* *************** */ = 0b0001000 , // Comment end
115- COMMENT_IGNORE_START /* *********** */ = 0b0010000 , // Comment ignore, descend into children and skip the content until COMMENT_ISLAND_START
116- COMMENT_IGNORE_END /* ************* */ = 0b0100000 , // Comment ignore end
117- COMMENT_ISLAND_START /* *********** */ = 0b1000001 , // Comment island, count elements for parent container until COMMENT_ISLAND_END
118- COMMENT_ISLAND_END /* ************* */ = 0b1000000 , // Comment island end
119- OTHER /* ************************** */ = 0b0000000 ,
120- }
121-
122233 /**
123234 * Looks up which type of node this is in a monomorphic way which should be faster.
124235 *
@@ -170,15 +281,15 @@ export function processVNodeData(document: Document) {
170281 * @param exitNode The node which represents the last node and we should exit.
171282 * @param qVNodeRefs Place to store the VNodeRefs
172283 */
173- const walkContainer = (
284+ const walkContainer = function * (
174285 walker : TreeWalker ,
175286 containerNode : Node | null ,
176287 node : Node | null ,
177288 exitNode : Node | null ,
178289 vData : string ,
179290 qVNodeRefs : Map < number , Element | ElementVNode > ,
180291 prefix : string
181- ) = > {
292+ ) : Generator < void , void , void > {
182293 const vData_length = vData . length ;
183294 /// Stores the current element index as the TreeWalker traverses the DOM.
184295 let elementIdx = 0 ;
@@ -206,6 +317,10 @@ export function processVNodeData(document: Document) {
206317 return elementsToSkip ;
207318 } ;
208319
320+ if ( ! node ) {
321+ return ;
322+ }
323+
209324 do {
210325 if ( node === exitNode ) {
211326 return ;
@@ -215,12 +330,12 @@ export function processVNodeData(document: Document) {
215330 if ( nodeType === NodeType . ELEMENT_CONTAINER ) {
216331 // If we are in a container, we need to skip the children.
217332 const container = node as ContainerElement ;
218- let cursor = node ;
333+ let cursor : Node | null = node ;
219334 while ( cursor && ! ( nextNode = nextSibling ( cursor ) ) ) {
220335 cursor = cursor ! . parentNode ;
221336 }
222337 // console.log('EXIT', nextNode?.outerHTML);
223- walkContainer (
338+ yield * walkContainer (
224339 walker ,
225340 container ,
226341 node ,
@@ -230,7 +345,7 @@ export function processVNodeData(document: Document) {
230345 prefix + ' '
231346 ) ;
232347 } else if ( nodeType === NodeType . COMMENT_IGNORE_START ) {
233- let islandNode = node ;
348+ let islandNode : Node | null = node ;
234349 do {
235350 islandNode = walker . nextNode ( ) ;
236351 if ( ! islandNode ) {
@@ -264,14 +379,14 @@ export function processVNodeData(document: Document) {
264379 }
265380 } while ( getFastNodeType ( nextNode ) !== NodeType . COMMENT_SKIP_END ) ;
266381 // console.log('EXIT', nextNode?.outerHTML);
267- walkContainer ( walker , node , node , nextNode , '' , null ! , prefix + ' ' ) ;
382+ yield * walkContainer ( walker , node , node , nextNode , '' , null ! , prefix + ' ' ) ;
268383 } else if ( nodeType === NodeType . ELEMENT_SHADOW_ROOT_WRAPPER ) {
269384 // If we are in a shadow root, we need to get the shadow root element.
270385 nextNode = nextSibling ( node ) ;
271386 const shadowRootContainer = node as Element | null ;
272387 const shadowRoot = shadowRootContainer ?. shadowRoot ;
273388 if ( shadowRoot ) {
274- walkContainer (
389+ yield * walkContainer (
275390 // we need to create a new walker for the shadow root
276391 document . createTreeWalker (
277392 shadowRoot ,
@@ -331,6 +446,7 @@ export function processVNodeData(document: Document) {
331446 }
332447 elementIdx ++ ;
333448 }
449+ yield ;
334450 } while ( ( node = nextNode || walker . nextNode ( ) ) ) ;
335451 } ;
336452
@@ -340,7 +456,7 @@ export function processVNodeData(document: Document) {
340456 0x1 /* NodeFilter.SHOW_ELEMENT */ | 0x80 /* NodeFilter.SHOW_COMMENT */
341457 ) ;
342458
343- walkContainer ( walker , null , walker . firstChild ( ) , null , '' , null ! , '' ) ;
459+ yield * walkContainer ( walker , null , walker . firstChild ( ) , null , '' , null ! , '' ) ;
344460}
345461
346462const isSeparator = ( ch : number ) =>
0 commit comments