1- import { _deserialize } from '@qwik.dev/core/internal' ;
1+ import { _deserialize , isDev } from '@qwik.dev/core/internal' ;
22import type { QData } from '../../middleware/request-handler/handlers/qdata-handler' ;
33import { preloadRouteBundles } from './client-navigate' ;
44import { QACTION_KEY } from './constants' ;
55import type { ClientPageData , RouteActionValue } from './types' ;
66
7+ class ShouldRedirect < T > {
8+ constructor (
9+ public location : string ,
10+ public data : T
11+ ) { }
12+ }
13+
714interface LoaderDataResponse {
815 id : string ;
916 route : string ;
1017}
1118
19+ interface RedirectContext {
20+ promise : Promise < unknown > | undefined ;
21+ }
22+
1223export const loadClientLoaderData = async ( url : URL , loaderId : string , manifestHash : string ) => {
1324 const pagePathname = url . pathname . endsWith ( '/' ) ? url . pathname : url . pathname + '/' ;
14- return fetchLoader ( loaderId , pagePathname , manifestHash ) ;
25+ const abortController = new AbortController ( ) ;
26+ return fetchLoader ( loaderId , pagePathname , manifestHash , abortController , { promise : undefined } ) ;
1527} ;
1628
1729export const loadClientData = async (
@@ -20,6 +32,7 @@ export const loadClientData = async (
2032 opts ?: {
2133 action ?: RouteActionValue ;
2234 loaderIds ?: string [ ] ;
35+ redirectData ?: ShouldRedirect < LoaderDataResponse [ ] > ;
2336 clearCache ?: boolean ;
2437 preloadRouteBundles ?: boolean ;
2538 isPrefetch ?: boolean ;
@@ -34,33 +47,69 @@ export const loadClientData = async (
3447 let resolveFn : ( ) => void | undefined ;
3548 let actionData : unknown ;
3649 if ( opts ?. action ) {
37- const actionResult = await fetchActionData ( opts . action , pagePathname , url . searchParams ) ;
38- actionData = actionResult . data ;
39- resolveFn = ( ) => {
40- opts . action ! . resolve ! ( { status : actionResult . status , result : actionData } ) ;
41- } ;
50+ try {
51+ const actionResult = await fetchActionData ( opts . action , pagePathname , url . searchParams ) ;
52+ actionData = actionResult . data ;
53+ resolveFn = ( ) => {
54+ opts . action ! . resolve ! ( { status : actionResult . status , result : actionData } ) ;
55+ } ;
56+ } catch ( e ) {
57+ if ( e instanceof ShouldRedirect ) {
58+ const newUrl = new URL ( e . location , url ) ;
59+ const newOpts = {
60+ ...opts ,
61+ action : undefined ,
62+ loaderIds : undefined ,
63+ redirectData : e ,
64+ } ;
65+ return loadClientData ( newUrl , manifestHash , newOpts ) ;
66+ } else {
67+ throw e ;
68+ }
69+ }
4270 } else {
4371 let loaderData : LoaderDataResponse [ ] = [ ] ;
44- if ( ! opts ?. loaderIds ) {
45- // we need to load all the loaders
46- // first we need to get the loader urls
47- loaderData = ( await fetchLoaderData ( pagePathname , manifestHash ) ) . loaderData ;
48- } else {
72+ if ( opts && opts . loaderIds ) {
4973 loaderData = opts . loaderIds . map ( ( loaderId ) => {
5074 return {
5175 id : loaderId ,
5276 route : pagePathname ,
5377 } ;
5478 } ) ;
79+ } else if ( opts ?. redirectData ?. data ) {
80+ loaderData = opts . redirectData . data ;
81+ } else {
82+ // we need to load all the loaders
83+ // first we need to get the loader urls
84+ loaderData = ( await fetchLoaderData ( pagePathname , manifestHash ) ) . loaderData ;
5585 }
5686 if ( loaderData . length > 0 ) {
5787 // load specific loaders
58- const loaderPromises = loaderData . map ( ( loader ) =>
59- fetchLoader ( loader . id , loader . route , manifestHash )
60- ) ;
61- const loaderResults = await Promise . all ( loaderPromises ) ;
62- for ( let i = 0 ; i < loaderData . length ; i ++ ) {
63- loaders [ loaderData [ i ] . id ] = loaderResults [ i ] ;
88+ const abortController = new AbortController ( ) ;
89+ const redirectContext : RedirectContext = { promise : undefined } ;
90+ try {
91+ const loaderPromises = loaderData . map ( ( loader ) =>
92+ fetchLoader ( loader . id , loader . route , manifestHash , abortController , redirectContext )
93+ ) ;
94+ const loaderResults = await Promise . all ( loaderPromises ) ;
95+ for ( let i = 0 ; i < loaderData . length ; i ++ ) {
96+ loaders [ loaderData [ i ] . id ] = loaderResults [ i ] ;
97+ }
98+ } catch ( e ) {
99+ if ( e instanceof ShouldRedirect ) {
100+ const newUrl = new URL ( e . location , url ) ;
101+ const newOpts = {
102+ ...opts ,
103+ action : undefined ,
104+ loaderIds : undefined ,
105+ redirectData : e ,
106+ } ;
107+ return loadClientData ( newUrl , manifestHash , newOpts ) ;
108+ } else if ( e instanceof Error && e . name === 'AbortError' ) {
109+ // Expected, do nothing
110+ } else {
111+ throw e ;
112+ }
64113 }
65114 }
66115 }
@@ -118,24 +167,57 @@ export async function fetchLoaderData(
118167) : Promise < { loaderData : LoaderDataResponse [ ] } > {
119168 const url = `${ routePath } q-loader-data.${ manifestHash } .json` ;
120169 const response = await fetch ( url ) ;
170+
171+ if ( ! response . ok ) {
172+ if ( isDev ) {
173+ throw new Error ( `Failed to load loader data for ${ routePath } : ${ response . status } ` ) ;
174+ }
175+ return { loaderData : [ ] } ;
176+ }
121177 return response . json ( ) ;
122178}
123179
124180export async function fetchLoader (
125181 loaderId : string ,
126182 routePath : string ,
127- manifestHash : string
183+ manifestHash : string ,
184+ abortController : AbortController ,
185+ redirectContext : RedirectContext
128186) : Promise < unknown > {
129187 const url = `${ routePath } q-loader-${ loaderId } .${ manifestHash } .json` ;
130188
131- const response = await fetch ( url ) ;
189+ const response = await fetch ( url , {
190+ signal : abortController . signal ,
191+ } ) ;
192+
193+ if ( response . redirected ) {
194+ if ( ! redirectContext . promise ) {
195+ redirectContext . promise = response . json ( ) ;
196+
197+ abortController . abort ( ) ;
198+
199+ const data = await redirectContext . promise ;
200+ // remove the q-loader-XY.json from the url and keep the rest of the url
201+ // the url is like this: https://localhost:3000/q-loader-XY.json
202+ // we need to remove the q-loader-XY.json and keep the rest of the url
203+ // the new url is like this: https://localhost:3000/
204+ const newUrl = new URL ( response . url ) ;
205+ newUrl . pathname = newUrl . pathname . replace ( `q-loader-data.${ manifestHash } .json` , '' ) ;
206+ throw new ShouldRedirect (
207+ newUrl . pathname ,
208+ ( data as { loaderData : LoaderDataResponse [ ] } ) . loaderData
209+ ) ;
210+ }
211+ }
132212 if ( ! response . ok ) {
133- throw new Error ( `Failed to load ${ loaderId } : ${ response . status } ` ) ;
213+ if ( isDev ) {
214+ throw new Error ( `Failed to load ${ loaderId } : ${ response . status } ` ) ;
215+ }
216+ return undefined ;
134217 }
135218
136219 const text = await response . text ( ) ;
137220 const [ data ] = _deserialize ( text , document . documentElement ) as [ Record < string , unknown > ] ;
138-
139221 return data ;
140222}
141223
@@ -159,6 +241,12 @@ export async function fetchActionData(
159241 action . data = undefined ;
160242 const response = await fetch ( url , fetchOptions ) ;
161243
244+ if ( response . redirected ) {
245+ const newUrl = new URL ( response . url ) ;
246+ newUrl . pathname = newUrl . pathname . replace ( 'q-data.json' , '' ) ;
247+ throw new ShouldRedirect ( newUrl . pathname , undefined ) ;
248+ }
249+
162250 const text = await response . text ( ) ;
163251 const [ data ] = _deserialize ( text , document . documentElement ) as [ Record < string , unknown > ] ;
164252
0 commit comments