@@ -162,7 +162,18 @@ export function create_client(app, target) {
162
162
// was then triggered and is still running while the invalidation kicks in,
163
163
// at which point the invalidation should take over and "win".
164
164
load_cache = null ;
165
- await update ( intent , url , [ ] ) ;
165
+
166
+ const nav_token = ( token = { } ) ;
167
+ const navigation_result = intent && ( await load_route ( intent ) ) ;
168
+ if ( nav_token !== token ) return ;
169
+
170
+ if ( navigation_result ) {
171
+ if ( navigation_result . type === 'redirect' ) {
172
+ return goto ( new URL ( navigation_result . location , url ) . href , { } , [ url . pathname ] , nav_token ) ;
173
+ } else {
174
+ root . $set ( navigation_result . props ) ;
175
+ }
176
+ }
166
177
}
167
178
168
179
/** @param {number } index */
@@ -256,183 +267,6 @@ export function create_client(app, target) {
256
267
await Promise . all ( promises ) ;
257
268
}
258
269
259
- /**
260
- * Returns `true` if update completes, `false` if it is aborted
261
- * @param {import('./types').NavigationIntent | undefined } intent
262
- * @param {URL } url
263
- * @param {string[] } redirect_chain
264
- * @param {number } [previous_history_index]
265
- * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean, details: { replaceState: boolean, state: any } | null} } [opts]
266
- * @param {{} } [nav_token] To distinguish between different navigation events and determine the latest. Needed for example for redirects to keep the original token
267
- * @param {() => void } [callback]
268
- */
269
- async function update (
270
- intent ,
271
- url ,
272
- redirect_chain ,
273
- previous_history_index ,
274
- opts ,
275
- nav_token = { } ,
276
- callback
277
- ) {
278
- token = nav_token ;
279
- let navigation_result = intent && ( await load_route ( intent ) ) ;
280
-
281
- if ( ! navigation_result ) {
282
- if ( is_external_url ( url , base ) ) {
283
- return await native_navigation ( url ) ;
284
- }
285
- navigation_result = await server_fallback (
286
- url ,
287
- { id : null } ,
288
- await handle_error ( new Error ( `Not found: ${ url . pathname } ` ) , {
289
- url,
290
- params : { } ,
291
- route : { id : null }
292
- } ) ,
293
- 404
294
- ) ;
295
- }
296
-
297
- // if this is an internal navigation intent, use the normalized
298
- // URL for the rest of the function
299
- url = intent ?. url || url ;
300
-
301
- // abort if user navigated during update
302
- if ( token !== nav_token ) return false ;
303
-
304
- if ( navigation_result . type === 'redirect' ) {
305
- if ( redirect_chain . length > 10 || redirect_chain . includes ( url . pathname ) ) {
306
- navigation_result = await load_root_error_page ( {
307
- status : 500 ,
308
- error : await handle_error ( new Error ( 'Redirect loop' ) , {
309
- url,
310
- params : { } ,
311
- route : { id : null }
312
- } ) ,
313
- url,
314
- route : { id : null }
315
- } ) ;
316
- } else {
317
- goto (
318
- new URL ( navigation_result . location , url ) . href ,
319
- { } ,
320
- [ ...redirect_chain , url . pathname ] ,
321
- nav_token
322
- ) ;
323
- return false ;
324
- }
325
- } else if ( /** @type {number } */ ( navigation_result . props . page ?. status ) >= 400 ) {
326
- const updated = await stores . updated . check ( ) ;
327
- if ( updated ) {
328
- await native_navigation ( url ) ;
329
- }
330
- }
331
-
332
- // reset invalidation only after a finished navigation. If there are redirects or
333
- // additional invalidations, they should get the same invalidation treatment
334
- invalidated . length = 0 ;
335
- force_invalidation = false ;
336
-
337
- updating = true ;
338
-
339
- // `previous_history_index` will be undefined for invalidation
340
- if ( previous_history_index ) {
341
- update_scroll_positions ( previous_history_index ) ;
342
- capture_snapshot ( previous_history_index ) ;
343
- }
344
-
345
- // ensure the url pathname matches the page's trailing slash option
346
- if (
347
- navigation_result . props . page ?. url &&
348
- navigation_result . props . page . url . pathname !== url . pathname
349
- ) {
350
- url . pathname = navigation_result . props . page ?. url . pathname ;
351
- }
352
-
353
- if ( opts && opts . details ) {
354
- const { details } = opts ;
355
- const change = details . replaceState ? 0 : 1 ;
356
- details . state [ INDEX_KEY ] = current_history_index += change ;
357
- history [ details . replaceState ? 'replaceState' : 'pushState' ] ( details . state , '' , url ) ;
358
-
359
- if ( ! details . replaceState ) {
360
- // if we navigated back, then pushed a new state, we can
361
- // release memory by pruning the scroll/snapshot lookup
362
- let i = current_history_index + 1 ;
363
- while ( snapshots [ i ] || scroll_positions [ i ] ) {
364
- delete snapshots [ i ] ;
365
- delete scroll_positions [ i ] ;
366
- i += 1 ;
367
- }
368
- }
369
- }
370
-
371
- // reset preload synchronously after the history state has been set to avoid race conditions
372
- load_cache = null ;
373
-
374
- if ( started ) {
375
- current = navigation_result . state ;
376
-
377
- // reset url before updating page store
378
- if ( navigation_result . props . page ) {
379
- navigation_result . props . page . url = url ;
380
- }
381
-
382
- root . $set ( navigation_result . props ) ;
383
- } else {
384
- initialize ( navigation_result ) ;
385
- }
386
-
387
- // opts must be passed if we're navigating
388
- if ( opts ) {
389
- const { scroll, keepfocus } = opts ;
390
- const { activeElement } = document ;
391
-
392
- // need to render the DOM before we can scroll to the rendered elements and do focus management
393
- await tick ( ) ;
394
-
395
- // we reset scroll before dealing with focus, to avoid a flash of unscrolled content
396
- if ( autoscroll ) {
397
- const deep_linked =
398
- url . hash && document . getElementById ( decodeURIComponent ( url . hash . slice ( 1 ) ) ) ;
399
- if ( scroll ) {
400
- scrollTo ( scroll . x , scroll . y ) ;
401
- } else if ( deep_linked ) {
402
- // Here we use `scrollIntoView` on the element instead of `scrollTo`
403
- // because it natively supports the `scroll-margin` and `scroll-behavior`
404
- // CSS properties.
405
- deep_linked . scrollIntoView ( ) ;
406
- } else {
407
- scrollTo ( 0 , 0 ) ;
408
- }
409
- }
410
-
411
- const changed_focus =
412
- // reset focus only if any manual focus management didn't override it
413
- document . activeElement !== activeElement &&
414
- // also refocus when activeElement is body already because the
415
- // focus event might not have been fired on it yet
416
- document . activeElement !== document . body ;
417
-
418
- if ( ! keepfocus && ! changed_focus ) {
419
- await reset_focus ( ) ;
420
- }
421
- } else {
422
- await tick ( ) ;
423
- }
424
-
425
- autoscroll = true ;
426
-
427
- if ( navigation_result . props . page ) {
428
- page = navigation_result . props . page ;
429
- }
430
-
431
- if ( callback ) callback ( ) ;
432
-
433
- updating = false ;
434
- }
435
-
436
270
/** @param {import('./types').NavigationFinished } result */
437
271
function initialize ( result ) {
438
272
if ( DEV && document . querySelector ( 'vite-error-overlay' ) ) return ;
@@ -1131,7 +965,7 @@ export function create_client(app, target) {
1131
965
details,
1132
966
type,
1133
967
delta,
1134
- nav_token,
968
+ nav_token = { } ,
1135
969
accepted,
1136
970
blocked
1137
971
} ) {
@@ -1154,25 +988,156 @@ export function create_client(app, target) {
1154
988
stores . navigating . set ( navigation ) ;
1155
989
}
1156
990
1157
- await update (
1158
- intent ,
1159
- url ,
1160
- redirect_chain ,
1161
- previous_history_index ,
1162
- {
1163
- scroll,
1164
- keepfocus,
1165
- details
1166
- } ,
1167
- nav_token ,
1168
- ( ) => {
1169
- navigating = false ;
1170
- callbacks . after_navigate . forEach ( ( fn ) =>
1171
- fn ( /** @type {import('types').AfterNavigate } */ ( navigation ) )
991
+ token = nav_token ;
992
+ let navigation_result = intent && ( await load_route ( intent ) ) ;
993
+
994
+ if ( ! navigation_result ) {
995
+ if ( is_external_url ( url , base ) ) {
996
+ return await native_navigation ( url ) ;
997
+ }
998
+ navigation_result = await server_fallback (
999
+ url ,
1000
+ { id : null } ,
1001
+ await handle_error ( new Error ( `Not found: ${ url . pathname } ` ) , {
1002
+ url,
1003
+ params : { } ,
1004
+ route : { id : null }
1005
+ } ) ,
1006
+ 404
1007
+ ) ;
1008
+ }
1009
+
1010
+ // if this is an internal navigation intent, use the normalized
1011
+ // URL for the rest of the function
1012
+ url = intent ?. url || url ;
1013
+
1014
+ // abort if user navigated during update
1015
+ if ( token !== nav_token ) return false ;
1016
+
1017
+ if ( navigation_result . type === 'redirect' ) {
1018
+ if ( redirect_chain . length > 10 || redirect_chain . includes ( url . pathname ) ) {
1019
+ navigation_result = await load_root_error_page ( {
1020
+ status : 500 ,
1021
+ error : await handle_error ( new Error ( 'Redirect loop' ) , {
1022
+ url,
1023
+ params : { } ,
1024
+ route : { id : null }
1025
+ } ) ,
1026
+ url,
1027
+ route : { id : null }
1028
+ } ) ;
1029
+ } else {
1030
+ goto (
1031
+ new URL ( navigation_result . location , url ) . href ,
1032
+ { } ,
1033
+ [ ...redirect_chain , url . pathname ] ,
1034
+ nav_token
1172
1035
) ;
1173
- stores . navigating . set ( null ) ;
1036
+ return false ;
1037
+ }
1038
+ } else if ( /** @type {number } */ ( navigation_result . props . page ?. status ) >= 400 ) {
1039
+ const updated = await stores . updated . check ( ) ;
1040
+ if ( updated ) {
1041
+ await native_navigation ( url ) ;
1174
1042
}
1043
+ }
1044
+
1045
+ // reset invalidation only after a finished navigation. If there are redirects or
1046
+ // additional invalidations, they should get the same invalidation treatment
1047
+ invalidated . length = 0 ;
1048
+ force_invalidation = false ;
1049
+
1050
+ updating = true ;
1051
+
1052
+ update_scroll_positions ( previous_history_index ) ;
1053
+ capture_snapshot ( previous_history_index ) ;
1054
+
1055
+ // ensure the url pathname matches the page's trailing slash option
1056
+ if (
1057
+ navigation_result . props . page ?. url &&
1058
+ navigation_result . props . page . url . pathname !== url . pathname
1059
+ ) {
1060
+ url . pathname = navigation_result . props . page ?. url . pathname ;
1061
+ }
1062
+
1063
+ if ( details ) {
1064
+ const change = details . replaceState ? 0 : 1 ;
1065
+ details . state [ INDEX_KEY ] = current_history_index += change ;
1066
+ history [ details . replaceState ? 'replaceState' : 'pushState' ] ( details . state , '' , url ) ;
1067
+
1068
+ if ( ! details . replaceState ) {
1069
+ // if we navigated back, then pushed a new state, we can
1070
+ // release memory by pruning the scroll/snapshot lookup
1071
+ let i = current_history_index + 1 ;
1072
+ while ( snapshots [ i ] || scroll_positions [ i ] ) {
1073
+ delete snapshots [ i ] ;
1074
+ delete scroll_positions [ i ] ;
1075
+ i += 1 ;
1076
+ }
1077
+ }
1078
+ }
1079
+
1080
+ // reset preload synchronously after the history state has been set to avoid race conditions
1081
+ load_cache = null ;
1082
+
1083
+ if ( started ) {
1084
+ current = navigation_result . state ;
1085
+
1086
+ // reset url before updating page store
1087
+ if ( navigation_result . props . page ) {
1088
+ navigation_result . props . page . url = url ;
1089
+ }
1090
+
1091
+ root . $set ( navigation_result . props ) ;
1092
+ } else {
1093
+ initialize ( navigation_result ) ;
1094
+ }
1095
+
1096
+ const { activeElement } = document ;
1097
+
1098
+ // need to render the DOM before we can scroll to the rendered elements and do focus management
1099
+ await tick ( ) ;
1100
+
1101
+ // we reset scroll before dealing with focus, to avoid a flash of unscrolled content
1102
+ if ( autoscroll ) {
1103
+ const deep_linked =
1104
+ url . hash && document . getElementById ( decodeURIComponent ( url . hash . slice ( 1 ) ) ) ;
1105
+ if ( scroll ) {
1106
+ scrollTo ( scroll . x , scroll . y ) ;
1107
+ } else if ( deep_linked ) {
1108
+ // Here we use `scrollIntoView` on the element instead of `scrollTo`
1109
+ // because it natively supports the `scroll-margin` and `scroll-behavior`
1110
+ // CSS properties.
1111
+ deep_linked . scrollIntoView ( ) ;
1112
+ } else {
1113
+ scrollTo ( 0 , 0 ) ;
1114
+ }
1115
+ }
1116
+
1117
+ const changed_focus =
1118
+ // reset focus only if any manual focus management didn't override it
1119
+ document . activeElement !== activeElement &&
1120
+ // also refocus when activeElement is body already because the
1121
+ // focus event might not have been fired on it yet
1122
+ document . activeElement !== document . body ;
1123
+
1124
+ if ( ! keepfocus && ! changed_focus ) {
1125
+ await reset_focus ( ) ;
1126
+ }
1127
+
1128
+ autoscroll = true ;
1129
+
1130
+ if ( navigation_result . props . page ) {
1131
+ page = navigation_result . props . page ;
1132
+ }
1133
+
1134
+ navigating = false ;
1135
+ callbacks . after_navigate . forEach ( ( fn ) =>
1136
+ fn ( /** @type {import('types').AfterNavigate } */ ( navigation ) )
1175
1137
) ;
1138
+ stores . navigating . set ( null ) ;
1139
+
1140
+ updating = false ;
1176
1141
}
1177
1142
1178
1143
/**
0 commit comments