@@ -14,7 +14,7 @@ import { JSXNodeImpl, isJSXNode } from '../shared/jsx/jsx-node';
1414import { Fragment , type Props } from '../shared/jsx/jsx-runtime' ;
1515import { directGetPropsProxyProp , type PropsProxy } from '../shared/jsx/props-proxy' ;
1616import { Slot } from '../shared/jsx/slot.public' ;
17- import type { JSXNodeInternal , JSXOutput } from '../shared/jsx/types/jsx-node' ;
17+ import type { JSXNodeInternal } from '../shared/jsx/types/jsx-node' ;
1818import type { JSXChildren } from '../shared/jsx/types/jsx-qwik-attributes' ;
1919import { SSRComment , SSRRaw , SkipRender } from '../shared/jsx/utils.public' ;
2020import type { QRLInternal } from '../shared/qrl/qrl-class' ;
@@ -44,7 +44,7 @@ import {
4444 Q_PREFIX ,
4545 dangerouslySetInnerHTML ,
4646} from '../shared/utils/markers' ;
47- import { isPromise } from '../shared/utils/promises' ;
47+ import { isPromise , retryOnPromise } from '../shared/utils/promises' ;
4848import { isSlotProp } from '../shared/utils/prop' ;
4949import { hasClassAttr } from '../shared/utils/scoped-styles' ;
5050import { serializeAttribute } from '../shared/utils/styles' ;
@@ -84,7 +84,7 @@ import { getAttributeNamespace, getNewElementNamespaceData } from './vnode-names
8484
8585export const vnode_diff = (
8686 container : ClientContainer ,
87- jsxNode : JSXOutput ,
87+ jsxNode : JSXChildren ,
8888 vStartNode : VNode ,
8989 scopedStyleIdPrefix : string | null
9090) => {
@@ -98,8 +98,7 @@ export const vnode_diff = (
9898 */
9999 const stack : any [ ] = [ ] ;
100100
101- const asyncQueue : Array < VNode | ValueOrPromise < JSXOutput > | Promise < JSXOutput | JSXChildren > > =
102- [ ] ;
101+ const asyncQueue : Array < VNode | ValueOrPromise < JSXChildren > | Promise < JSXChildren > > = [ ] ;
103102
104103 ////////////////////////////////
105104 //// Traverse state variables
@@ -151,7 +150,7 @@ export const vnode_diff = (
151150 //////////////////////////////////////////////
152151 //////////////////////////////////////////////
153152
154- function diff ( jsxNode : JSXOutput , vStartNode : VNode ) {
153+ function diff ( jsxNode : JSXChildren , vStartNode : VNode ) {
155154 assertFalse ( vnode_isVNode ( jsxNode ) , 'JSXNode should not be a VNode' ) ;
156155 assertTrue ( vnode_isVNode ( vStartNode ) , 'vStartNode should be a VNode' ) ;
157156 vParent = vStartNode as ElementVNode | VirtualVNode ;
@@ -182,12 +181,10 @@ export const vnode_diff = (
182181 EffectSubscriptionProp . CONSUMER
183182 ] ;
184183 if ( currentSignal !== unwrappedSignal ) {
184+ const vHost = ( vNewNode || vCurrent ) ! ;
185185 descend (
186- trackSignalAndAssignHost (
187- unwrappedSignal ,
188- ( vNewNode || vCurrent ) ! ,
189- EffectProperty . VNODE ,
190- container
186+ resolveSignalAndDescend ( ( ) =>
187+ trackSignalAndAssignHost ( unwrappedSignal , vHost , EffectProperty . VNODE , container )
191188 ) ,
192189 true
193190 ) ;
@@ -246,6 +243,21 @@ export const vnode_diff = (
246243 }
247244 }
248245
246+ function resolveSignalAndDescend ( fn : ( ) => ValueOrPromise < any > ) : ValueOrPromise < any > {
247+ try {
248+ return fn ( ) ;
249+ } catch ( e ) {
250+ // Signal threw a promise (async computed signal) - handle retry and async queue
251+ if ( isPromise ( e ) ) {
252+ // The thrown promise will resolve when the signal is ready, then retry fn() with retry logic
253+ const retryPromise = e . then ( ( ) => retryOnPromise ( fn ) ) ;
254+ asyncQueue . push ( retryPromise , vNewNode || vCurrent ) ;
255+ return null ;
256+ }
257+ throw e ;
258+ }
259+ }
260+
249261 function advance ( ) {
250262 if ( ! shouldAdvance ) {
251263 shouldAdvance = true ;
@@ -530,13 +542,19 @@ export const vnode_diff = (
530542
531543 function drainAsyncQueue ( ) : ValueOrPromise < void > {
532544 while ( asyncQueue . length ) {
533- const jsxNode = asyncQueue . shift ( ) as ValueOrPromise < JSXNodeInternal > ;
545+ const jsxNode = asyncQueue . shift ( ) as ValueOrPromise < JSXChildren > ;
534546 const vHostNode = asyncQueue . shift ( ) as VNode ;
547+
535548 if ( isPromise ( jsxNode ) ) {
536- return jsxNode . then ( ( jsxNode ) => {
537- diff ( jsxNode , vHostNode ) ;
538- return drainAsyncQueue ( ) ;
539- } ) ;
549+ return jsxNode
550+ . then ( ( jsxNode ) => {
551+ diff ( jsxNode , vHostNode ) ;
552+ return drainAsyncQueue ( ) ;
553+ } )
554+ . catch ( ( e ) => {
555+ container . handleError ( e , vHostNode ) ;
556+ return drainAsyncQueue ( ) ;
557+ } ) ;
540558 } else {
541559 diff ( jsxNode , vHostNode ) ;
542560 }
@@ -594,6 +612,21 @@ export const vnode_diff = (
594612 ) : boolean {
595613 const element = createElementWithNamespace ( elementName ) ;
596614
615+ function setAttribute ( key : string , value : any , vHost : ElementVNode ) {
616+ value = serializeAttribute ( key , value , scopedStyleIdPrefix ) ;
617+ if ( value != null ) {
618+ if ( vHost . flags & VNodeFlags . NS_svg ) {
619+ // only svg elements can have namespace attributes
620+ const namespace = getAttributeNamespace ( key ) ;
621+ if ( namespace ) {
622+ element . setAttributeNS ( namespace , key , String ( value ) ) ;
623+ return ;
624+ }
625+ }
626+ element . setAttribute ( key , String ( value ) ) ;
627+ }
628+ }
629+
597630 const { constProps } = jsx ;
598631 let needsQDispatchEventPatch = false ;
599632 if ( constProps ) {
@@ -637,15 +670,19 @@ export const vnode_diff = (
637670 }
638671
639672 if ( isSignal ( value ) ) {
640- value = trackSignalAndAssignHost (
641- value as Signal < unknown > ,
642- vNewNode as ElementVNode ,
643- key ,
644- container ,
645- CONST_SUBSCRIPTION_DATA
673+ const vHost = vNewNode as ElementVNode ;
674+ const signal = value as Signal < unknown > ;
675+ value = retryOnPromise ( ( ) =>
676+ trackSignalAndAssignHost ( signal , vHost , key , container , CONST_SUBSCRIPTION_DATA )
646677 ) ;
647678 }
648679
680+ if ( isPromise ( value ) ) {
681+ const vHost = vNewNode as ElementVNode ;
682+ value . then ( ( resolvedValue ) => setAttribute ( key , resolvedValue , vHost ) ) ;
683+ continue ;
684+ }
685+
649686 if ( key === dangerouslySetInnerHTML ) {
650687 if ( value ) {
651688 element . innerHTML = String ( value ) ;
@@ -665,18 +702,7 @@ export const vnode_diff = (
665702 continue ;
666703 }
667704
668- value = serializeAttribute ( key , value , scopedStyleIdPrefix ) ;
669- if ( value != null ) {
670- if ( vNewNode ! . flags & VNodeFlags . NS_svg ) {
671- // only svg elements can have namespace attributes
672- const namespace = getAttributeNamespace ( key ) ;
673- if ( namespace ) {
674- element . setAttributeNS ( namespace , key , String ( value ) ) ;
675- continue ;
676- }
677- }
678- element . setAttribute ( key , String ( value ) ) ;
679- }
705+ setAttribute ( key , value , vNewNode as ElementVNode ) ;
680706 }
681707 }
682708 const key = jsx . key ;
@@ -803,6 +829,14 @@ export const vnode_diff = (
803829 let dstIdx = 0 ;
804830 let patchEventDispatch = false ;
805831
832+ const setAttribute = ( key : string , value : any , vHost : ElementVNode ) => {
833+ vHost . setAttr (
834+ key ,
835+ value !== null ? serializeAttribute ( key , value , scopedStyleIdPrefix ) : null ,
836+ journal
837+ ) ;
838+ } ;
839+
806840 const record = ( key : string , value : any ) => {
807841 if ( key . startsWith ( ':' ) ) {
808842 vnode . setProp ( key , value ) ;
@@ -837,12 +871,16 @@ export const vnode_diff = (
837871 // Only if we want to track the signal again
838872 clearEffectSubscription ( container , currentEffect ) ;
839873 }
840- value = trackSignalAndAssignHost (
841- unwrappedSignal ,
842- vnode ,
843- key ,
844- container ,
845- NON_CONST_SUBSCRIPTION_DATA
874+
875+ const vHost = vnode as ElementVNode ;
876+ value = retryOnPromise ( ( ) =>
877+ trackSignalAndAssignHost (
878+ unwrappedSignal ,
879+ vHost ,
880+ key ,
881+ container ,
882+ NON_CONST_SUBSCRIPTION_DATA
883+ )
846884 ) ;
847885 } else {
848886 if ( currentEffect ) {
@@ -853,11 +891,13 @@ export const vnode_diff = (
853891 }
854892 }
855893
856- vnode . setAttr (
857- key ,
858- value !== null ? serializeAttribute ( key , value , scopedStyleIdPrefix ) : null ,
859- journal
860- ) ;
894+ if ( isPromise ( value ) ) {
895+ const vHost = vnode as ElementVNode ;
896+ value . then ( ( resolvedValue ) => setAttribute ( key , resolvedValue , vHost ) ) ;
897+ return ;
898+ }
899+
900+ setAttribute ( key , value , vnode ) ;
861901 } ;
862902
863903 const recordJsxEvent = ( key : string , value : any ) => {
@@ -1113,11 +1153,8 @@ export const vnode_diff = (
11131153 function expectVirtual ( type : VirtualType , jsxKey : string | null ) {
11141154 const checkKey = type === VirtualType . Fragment ;
11151155 const currentKey = getKey ( vCurrent ) ;
1116- const isSameNode =
1117- vCurrent &&
1118- vnode_isVirtualVNode ( vCurrent ) &&
1119- currentKey === jsxKey &&
1120- ( checkKey ? ! ! jsxKey : true ) ;
1156+ const currentIsVirtual = vCurrent && vnode_isVirtualVNode ( vCurrent ) ;
1157+ const isSameNode = currentIsVirtual && currentKey === jsxKey && ( checkKey ? ! ! jsxKey : true ) ;
11211158
11221159 if ( isSameNode ) {
11231160 // All is good.
@@ -1136,7 +1173,7 @@ export const vnode_diff = (
11361173 isDev && ( vNewNode as VirtualVNode ) . setProp ( DEBUG_TYPE , type ) ;
11371174 } ;
11381175 // For fragments without a key, always create a new virtual node (ensures rerender semantics)
1139- if ( checkKey && jsxKey === null ) {
1176+ if ( jsxKey === null ) {
11401177 createNew ( ) ;
11411178 return ;
11421179 }
0 commit comments