@sveltejs/kit 3.0.0-next.3 → 3.0.0-next.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/core/adapt/builder.js +18 -4
- package/src/core/adapt/index.js +1 -4
- package/src/core/config/index.js +64 -5
- package/src/core/config/options.js +73 -21
- package/src/core/env.js +35 -4
- package/src/core/postbuild/analyse.js +8 -12
- package/src/core/postbuild/crawl.js +22 -6
- package/src/core/postbuild/prerender.js +40 -23
- package/src/core/sync/create_manifest_data/index.js +23 -5
- package/src/core/sync/write_client_manifest.js +7 -0
- package/src/core/sync/write_server.js +13 -10
- package/src/core/sync/write_tsconfig.js +2 -4
- package/src/exports/index.js +32 -10
- package/src/exports/internal/env.js +1 -1
- package/src/exports/internal/index.js +6 -5
- package/src/exports/node/index.js +57 -6
- package/src/exports/public.d.ts +218 -114
- package/src/exports/vite/build/build_server.js +6 -1
- package/src/exports/vite/dev/index.js +10 -20
- package/src/exports/vite/index.js +306 -222
- package/src/exports/vite/preview/index.js +15 -7
- package/src/exports/vite/utils.js +8 -10
- package/src/runtime/app/env/types.d.ts +1 -1
- package/src/runtime/app/forms.js +22 -5
- package/src/runtime/app/paths/client.js +1 -3
- package/src/runtime/app/paths/public.d.ts +0 -28
- package/src/runtime/app/paths/server.js +7 -3
- package/src/runtime/app/server/remote/form.js +10 -3
- package/src/runtime/app/server/remote/query.js +3 -6
- package/src/runtime/app/server/remote/requested.js +8 -4
- package/src/runtime/app/server/remote/shared.js +5 -7
- package/src/runtime/client/client.js +190 -95
- package/src/runtime/client/fetcher.js +3 -2
- package/src/runtime/client/remote-functions/form.svelte.js +134 -24
- package/src/runtime/client/remote-functions/prerender.svelte.js +8 -2
- package/src/runtime/client/remote-functions/query/index.js +3 -2
- package/src/runtime/client/remote-functions/query/instance.svelte.js +26 -10
- package/src/runtime/client/remote-functions/query-batch.svelte.js +4 -3
- package/src/runtime/client/remote-functions/query-live/instance.svelte.js +26 -9
- package/src/runtime/client/remote-functions/shared.svelte.js +17 -7
- package/src/runtime/client/types.d.ts +9 -1
- package/src/runtime/client/utils.js +1 -1
- package/src/runtime/form-utils.js +84 -16
- package/src/runtime/server/cookie.js +22 -33
- package/src/runtime/server/csrf.js +65 -0
- package/src/runtime/server/data/index.js +7 -7
- package/src/runtime/server/env_module.js +0 -5
- package/src/runtime/server/index.js +1 -1
- package/src/runtime/server/page/actions.js +41 -26
- package/src/runtime/server/page/index.js +2 -1
- package/src/runtime/server/page/render.js +21 -23
- package/src/runtime/server/page/respond_with_error.js +7 -8
- package/src/runtime/server/remote.js +64 -27
- package/src/runtime/server/respond.js +81 -50
- package/src/runtime/server/utils.js +7 -7
- package/src/runtime/telemetry/otel.js +1 -1
- package/src/types/ambient.d.ts +5 -4
- package/src/types/global-private.d.ts +4 -4
- package/src/types/internal.d.ts +8 -10
- package/src/types/private.d.ts +33 -1
- package/src/types/synthetic/$lib.md +1 -1
- package/src/utils/error.js +11 -3
- package/src/utils/shared-iterator.js +5 -0
- package/src/utils/url.js +99 -2
- package/src/version.js +1 -1
- package/types/index.d.ts +340 -186
- package/types/index.d.ts.map +10 -9
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** @import { RemoteFunctionDataNode, ServerNodesResponse, ServerRedirectNode } from 'types' */
|
|
2
|
+
/** @import { NavigationIntent } from './types.js' */
|
|
2
3
|
/** @import { CacheEntry } from './remote-functions/cache.svelte.js' */
|
|
3
4
|
/** @import { Query } from './remote-functions/query/instance.svelte.js' */
|
|
4
5
|
/** @import { LiveQuery } from './remote-functions/query-live/instance.svelte.js' */
|
|
@@ -30,7 +31,7 @@ import {
|
|
|
30
31
|
create_updated_store,
|
|
31
32
|
load_css
|
|
32
33
|
} from './utils.js';
|
|
33
|
-
import { base } from '$app/paths';
|
|
34
|
+
import { base } from '$app/paths/internal/client';
|
|
34
35
|
import * as devalue from 'devalue';
|
|
35
36
|
import {
|
|
36
37
|
HISTORY_INDEX,
|
|
@@ -90,8 +91,6 @@ const snapshots = storage.get(SNAPSHOT_KEY) ?? {};
|
|
|
90
91
|
if (DEV && BROWSER) {
|
|
91
92
|
let warned = false;
|
|
92
93
|
|
|
93
|
-
const current_module_url = import.meta.url.split('?')[0]; // remove query params that vite adds to the URL when it is loaded from node_modules
|
|
94
|
-
|
|
95
94
|
const warn = () => {
|
|
96
95
|
if (warned) return;
|
|
97
96
|
|
|
@@ -100,9 +99,13 @@ if (DEV && BROWSER) {
|
|
|
100
99
|
let stack = new Error().stack?.split('\n');
|
|
101
100
|
if (!stack) return;
|
|
102
101
|
if (!stack[0].includes('https:') && !stack[0].includes('http:')) stack = stack.slice(1); // Chrome includes the error message in the stack
|
|
103
|
-
|
|
104
|
-
//
|
|
105
|
-
|
|
102
|
+
|
|
103
|
+
// skip over `warn` and the place where `warn` was called
|
|
104
|
+
const frame = stack[2];
|
|
105
|
+
|
|
106
|
+
// ignore calls that happen inside dependencies, including SvelteKit.
|
|
107
|
+
// `frame` can be falsy if we came from an anonymous function
|
|
108
|
+
if (frame?.includes('node_modules')) return;
|
|
106
109
|
|
|
107
110
|
warned = true;
|
|
108
111
|
|
|
@@ -163,7 +166,8 @@ function clear_onward_history(current_history_index, current_navigation_index) {
|
|
|
163
166
|
* Returns a `Promise` that never resolves (to prevent any
|
|
164
167
|
* subsequent work, e.g. history manipulation, from happening)
|
|
165
168
|
* @param {URL} url
|
|
166
|
-
* @param {boolean} [replace]
|
|
169
|
+
* @param {boolean} [replace] if `true`, will replace the current `history` entry rather than creating a new one with `pushState`
|
|
170
|
+
* @returns {Promise<any>} a promise that never resolves
|
|
167
171
|
*/
|
|
168
172
|
function native_navigation(url, replace = false) {
|
|
169
173
|
if (replace) {
|
|
@@ -187,7 +191,7 @@ async function update_service_worker() {
|
|
|
187
191
|
}
|
|
188
192
|
}
|
|
189
193
|
|
|
190
|
-
/** @type {import('types').CSRRoute[]} All routes of the app. Only available when
|
|
194
|
+
/** @type {import('types').CSRRoute[]} All routes of the app. Only available when router.resolution=client */
|
|
191
195
|
let routes;
|
|
192
196
|
/** @type {import('types').CSRPageNodeLoader} */
|
|
193
197
|
let default_layout_loader;
|
|
@@ -292,8 +296,17 @@ let current_history_index;
|
|
|
292
296
|
/** @type {number} */
|
|
293
297
|
let current_navigation_index;
|
|
294
298
|
|
|
295
|
-
/** @type {{}} */
|
|
296
|
-
let
|
|
299
|
+
/** @type {{}} Token for the latest navigation. Updated on new navigations */
|
|
300
|
+
let navigation_token;
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* @type {{}}
|
|
304
|
+
* The latest invalidate(All) token. Superseeded by both later invalidate(All)s and navigations.
|
|
305
|
+
* This is separate to navigation_token because an invalidate(All) might be triggered while a navigation
|
|
306
|
+
* is in progress, and we want to be able to finish this navigation (unless the invalidation finishes before
|
|
307
|
+
* it and redirects, in which case we will do the redirect triggered by the invalidation).
|
|
308
|
+
*/
|
|
309
|
+
let invalidation_token;
|
|
297
310
|
|
|
298
311
|
/**
|
|
299
312
|
* A set of tokens which are associated to current preloads.
|
|
@@ -301,6 +314,7 @@ let token;
|
|
|
301
314
|
* If a preload token is in the set and the preload errors, the error
|
|
302
315
|
* handling logic (for example reloading) is skipped.
|
|
303
316
|
*/
|
|
317
|
+
/** @type {Set<{}>} */
|
|
304
318
|
const preload_tokens = new Set();
|
|
305
319
|
|
|
306
320
|
/** @type {Promise<void> | null} */
|
|
@@ -418,7 +432,9 @@ async function _invalidate(include_load_functions = true, reset_page_state = tru
|
|
|
418
432
|
if (!pending_invalidate) return;
|
|
419
433
|
pending_invalidate = null;
|
|
420
434
|
|
|
421
|
-
const
|
|
435
|
+
const token = (invalidation_token = {});
|
|
436
|
+
const nav_token = navigation_token;
|
|
437
|
+
const navigating = is_navigating;
|
|
422
438
|
const intent = await get_navigation_intent(current.url, true);
|
|
423
439
|
|
|
424
440
|
// Clear preload, it might be affected by the invalidation.
|
|
@@ -450,17 +466,25 @@ async function _invalidate(include_load_functions = true, reset_page_state = tru
|
|
|
450
466
|
if (include_load_functions) {
|
|
451
467
|
const prev_state = page.state;
|
|
452
468
|
const navigation_result = intent && (await load_route(intent));
|
|
453
|
-
if (!navigation_result || nav_token !==
|
|
469
|
+
if (!navigation_result || token !== invalidation_token || nav_token !== navigation_token) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
454
472
|
|
|
455
473
|
if (navigation_result.type === 'redirect') {
|
|
456
474
|
return _goto(
|
|
457
475
|
new URL(navigation_result.location, current.url).href,
|
|
458
476
|
{ replaceState: true },
|
|
459
477
|
1,
|
|
460
|
-
|
|
478
|
+
token
|
|
461
479
|
);
|
|
462
480
|
}
|
|
463
481
|
|
|
482
|
+
// A navigation started before the invalidation and ended before it finished. The invalidation did not redirect,
|
|
483
|
+
// hence it likely contains outdated data now, so we ignore it.
|
|
484
|
+
if (navigating && !is_navigating) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
464
488
|
// This is a bit hacky but allows us not having to pass that boolean around, making things harder to reason about
|
|
465
489
|
if (!reset_page_state) {
|
|
466
490
|
navigation_result.props.page.state = prev_state;
|
|
@@ -527,8 +551,10 @@ function persist_state() {
|
|
|
527
551
|
* @param {{ replaceState?: boolean; noScroll?: boolean; keepFocus?: boolean; invalidateAll?: boolean; invalidate?: Array<string | URL | ((url: URL) => boolean)>; state?: Record<string, any> }} options
|
|
528
552
|
* @param {number} redirect_count
|
|
529
553
|
* @param {{}} [nav_token]
|
|
554
|
+
* @param {NavigationIntent | undefined} [intent] navigation intent, when already known by the caller (avoids recomputing it)
|
|
555
|
+
* @returns {Promise<void>}
|
|
530
556
|
*/
|
|
531
|
-
export async function _goto(url, options, redirect_count, nav_token) {
|
|
557
|
+
export async function _goto(url, options, redirect_count, nav_token, intent) {
|
|
532
558
|
/** @type {Set<string>} */
|
|
533
559
|
let query_keys;
|
|
534
560
|
/** @type {Set<string>} */
|
|
@@ -549,12 +575,17 @@ export async function _goto(url, options, redirect_count, nav_token) {
|
|
|
549
575
|
state: options.state,
|
|
550
576
|
redirect_count,
|
|
551
577
|
nav_token,
|
|
578
|
+
intent,
|
|
552
579
|
accept: () => {
|
|
553
580
|
if (options.invalidateAll) {
|
|
554
581
|
force_invalidation = true;
|
|
555
582
|
query_keys = new Set();
|
|
556
583
|
for (const [id, entries] of query_map) {
|
|
557
|
-
for (const payload of entries
|
|
584
|
+
for (const [payload, entry] of entries) {
|
|
585
|
+
// don't refresh yet, as some queries will be unrendered,
|
|
586
|
+
// but clear caches so that newly rendered queries
|
|
587
|
+
// don't use stale data. TODO same for `live_query_map`
|
|
588
|
+
entry.resource?.reset();
|
|
558
589
|
query_keys.add(create_remote_key(id, payload));
|
|
559
590
|
}
|
|
560
591
|
}
|
|
@@ -582,7 +613,7 @@ export async function _goto(url, options, redirect_count, nav_token) {
|
|
|
582
613
|
for (const [id, entries] of query_map) {
|
|
583
614
|
for (const [payload, { resource }] of entries) {
|
|
584
615
|
if (query_keys?.has(create_remote_key(id, payload))) {
|
|
585
|
-
void resource.
|
|
616
|
+
void resource.start();
|
|
586
617
|
}
|
|
587
618
|
}
|
|
588
619
|
}
|
|
@@ -702,7 +733,7 @@ async function initialize(result, target, hydrate) {
|
|
|
702
733
|
transformError: __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__
|
|
703
734
|
? /** @param {unknown} e */ async (e) => {
|
|
704
735
|
const error = await handle_error(e, current.nav);
|
|
705
|
-
rendering_error = { error, status:
|
|
736
|
+
rendering_error = { error, status: error.status };
|
|
706
737
|
page.error = error;
|
|
707
738
|
page.status = rendering_error.status;
|
|
708
739
|
return error;
|
|
@@ -714,8 +745,6 @@ async function initialize(result, target, hydrate) {
|
|
|
714
745
|
// which causes component script blocks to run asynchronously
|
|
715
746
|
void (await Promise.resolve());
|
|
716
747
|
|
|
717
|
-
restore_snapshot(current_navigation_index);
|
|
718
|
-
|
|
719
748
|
if (hydrate) {
|
|
720
749
|
/** @type {import('@sveltejs/kit').AfterNavigate} */
|
|
721
750
|
const navigation = {
|
|
@@ -732,6 +761,8 @@ async function initialize(result, target, hydrate) {
|
|
|
732
761
|
after_navigate_callbacks.forEach((fn) => fn(navigation));
|
|
733
762
|
}
|
|
734
763
|
|
|
764
|
+
restore_snapshot(current_navigation_index);
|
|
765
|
+
|
|
735
766
|
started = true;
|
|
736
767
|
}
|
|
737
768
|
|
|
@@ -742,7 +773,7 @@ async function initialize(result, target, hydrate) {
|
|
|
742
773
|
* params: Record<string, string>;
|
|
743
774
|
* branch: Array<import('./types.js').BranchNode | undefined>;
|
|
744
775
|
* errors?: Array<import('types').CSRPageNodeLoader | undefined>;
|
|
745
|
-
* status
|
|
776
|
+
* status?: number;
|
|
746
777
|
* error: App.Error | null;
|
|
747
778
|
* route: import('types').CSRRoute | null;
|
|
748
779
|
* form?: Record<string, any> | null;
|
|
@@ -859,7 +890,7 @@ async function get_navigation_result_from_branch({
|
|
|
859
890
|
id: route?.id ?? null
|
|
860
891
|
},
|
|
861
892
|
state: {},
|
|
862
|
-
status,
|
|
893
|
+
status: status ?? error?.status ?? 200,
|
|
863
894
|
url: new URL(url),
|
|
864
895
|
form: form ?? null,
|
|
865
896
|
// The whole page store is updated, but this way the object reference stays the same
|
|
@@ -1146,6 +1177,10 @@ function diff_search_params(old_url, new_url) {
|
|
|
1146
1177
|
* @returns {import('./types.js').NavigationFinished}
|
|
1147
1178
|
*/
|
|
1148
1179
|
function preload_error({ error, url, route, params }) {
|
|
1180
|
+
// we skipped loading the error page, so we need to use the current page
|
|
1181
|
+
// store, but we still pass the updated status to the preloadData function
|
|
1182
|
+
const new_page = clone_page(page);
|
|
1183
|
+
new_page.status = error.status;
|
|
1149
1184
|
return {
|
|
1150
1185
|
type: 'loaded',
|
|
1151
1186
|
state: {
|
|
@@ -1156,16 +1191,26 @@ function preload_error({ error, url, route, params }) {
|
|
|
1156
1191
|
branch: []
|
|
1157
1192
|
},
|
|
1158
1193
|
props: {
|
|
1159
|
-
page:
|
|
1194
|
+
page: new_page,
|
|
1160
1195
|
constructors: []
|
|
1161
1196
|
}
|
|
1162
1197
|
};
|
|
1163
1198
|
}
|
|
1164
1199
|
|
|
1165
1200
|
/**
|
|
1166
|
-
* @
|
|
1201
|
+
* @overload
|
|
1202
|
+
* @param {import('./types.js').NavigationIntent} intent
|
|
1203
|
+
* @returns {Promise<import('./types.js').NavigationResult | undefined>}
|
|
1204
|
+
*/
|
|
1205
|
+
/**
|
|
1206
|
+
* @overload
|
|
1207
|
+
* @param {import('./types.js').NavigationIntent & { preload: {} }} intent
|
|
1167
1208
|
* @returns {Promise<import('./types.js').NavigationResult>}
|
|
1168
1209
|
*/
|
|
1210
|
+
/**
|
|
1211
|
+
* @param {import('./types.js').NavigationIntent & { preload?: {} }} intent
|
|
1212
|
+
* @returns {Promise<import('./types.js').NavigationResult | undefined>}
|
|
1213
|
+
*/
|
|
1169
1214
|
async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
1170
1215
|
if (load_cache?.id === id) {
|
|
1171
1216
|
// the preload becomes the real navigation
|
|
@@ -1221,12 +1266,11 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1221
1266
|
} catch (error) {
|
|
1222
1267
|
const handled_error = await handle_error(error, { url, params, route: { id } });
|
|
1223
1268
|
|
|
1224
|
-
if (preload_tokens.has(preload)) {
|
|
1269
|
+
if (preload && preload_tokens.has(preload)) {
|
|
1225
1270
|
return preload_error({ error: handled_error, url, params, route });
|
|
1226
1271
|
}
|
|
1227
1272
|
|
|
1228
1273
|
return load_root_error_page({
|
|
1229
|
-
status: get_status(error),
|
|
1230
1274
|
error: handled_error,
|
|
1231
1275
|
url,
|
|
1232
1276
|
route
|
|
@@ -1269,7 +1313,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1269
1313
|
|
|
1270
1314
|
if (server_data_node?.type === 'error') {
|
|
1271
1315
|
// rethrow and catch below
|
|
1272
|
-
throw server_data_node;
|
|
1316
|
+
throw new HttpError(server_data_node.error.status, server_data_node.error);
|
|
1273
1317
|
}
|
|
1274
1318
|
|
|
1275
1319
|
return load_node({
|
|
@@ -1307,27 +1351,27 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1307
1351
|
if (err instanceof Redirect) {
|
|
1308
1352
|
return {
|
|
1309
1353
|
type: 'redirect',
|
|
1354
|
+
status: err.status,
|
|
1310
1355
|
location: err.location
|
|
1311
1356
|
};
|
|
1312
1357
|
}
|
|
1313
1358
|
|
|
1314
|
-
if (preload_tokens.has(preload)) {
|
|
1359
|
+
if (preload && preload_tokens.has(preload)) {
|
|
1360
|
+
const error = await handle_error(err, { params, url, route: { id: route.id } });
|
|
1315
1361
|
return preload_error({
|
|
1316
|
-
error
|
|
1362
|
+
error,
|
|
1317
1363
|
url,
|
|
1318
1364
|
params,
|
|
1319
1365
|
route
|
|
1320
1366
|
});
|
|
1321
1367
|
}
|
|
1322
1368
|
|
|
1323
|
-
let status = get_status(err);
|
|
1324
1369
|
/** @type {App.Error} */
|
|
1325
1370
|
let error;
|
|
1326
1371
|
|
|
1327
1372
|
if (server_data_nodes?.includes(/** @type {import('types').ServerErrorNode} */ (err))) {
|
|
1328
1373
|
// this is the server error rethrown above, reconstruct but don't invoke
|
|
1329
1374
|
// the client error handler; it should've already been handled on the server
|
|
1330
|
-
status = /** @type {import('types').ServerErrorNode} */ (err).status ?? status;
|
|
1331
1375
|
error = /** @type {import('types').ServerErrorNode} */ (err).error;
|
|
1332
1376
|
} else if (err instanceof HttpError) {
|
|
1333
1377
|
error = err.body;
|
|
@@ -1350,12 +1394,11 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1350
1394
|
params,
|
|
1351
1395
|
branch: branch.slice(0, error_load.idx).concat(error_load.node),
|
|
1352
1396
|
errors,
|
|
1353
|
-
status,
|
|
1354
1397
|
error,
|
|
1355
1398
|
route
|
|
1356
1399
|
});
|
|
1357
1400
|
} else {
|
|
1358
|
-
return await server_fallback(url, { id: route.id }, error
|
|
1401
|
+
return await server_fallback(url, { id: route.id }, error);
|
|
1359
1402
|
}
|
|
1360
1403
|
}
|
|
1361
1404
|
} else {
|
|
@@ -1370,7 +1413,6 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1370
1413
|
params,
|
|
1371
1414
|
branch,
|
|
1372
1415
|
errors,
|
|
1373
|
-
status: 200,
|
|
1374
1416
|
error: null,
|
|
1375
1417
|
route,
|
|
1376
1418
|
// Reset `form` on navigation, but not invalidation
|
|
@@ -1409,14 +1451,13 @@ async function load_nearest_error_page(i, branch, errors) {
|
|
|
1409
1451
|
|
|
1410
1452
|
/**
|
|
1411
1453
|
* @param {{
|
|
1412
|
-
* status: number;
|
|
1413
1454
|
* error: App.Error;
|
|
1414
1455
|
* url: URL;
|
|
1415
1456
|
* route: { id: string | null }
|
|
1416
1457
|
* }} opts
|
|
1417
|
-
* @returns {Promise<import('./types.js').NavigationFinished>}
|
|
1458
|
+
* @returns {Promise<import('./types.js').NavigationFinished | undefined>} returns `undefined` in case of a redirect
|
|
1418
1459
|
*/
|
|
1419
|
-
async function load_root_error_page({
|
|
1460
|
+
async function load_root_error_page({ error, url, route }) {
|
|
1420
1461
|
/** @type {Record<string, string>} */
|
|
1421
1462
|
const params = {}; // error page does not have params
|
|
1422
1463
|
|
|
@@ -1440,11 +1481,15 @@ async function load_root_error_page({ status, error, url, route }) {
|
|
|
1440
1481
|
}
|
|
1441
1482
|
|
|
1442
1483
|
server_data_node = server_data.nodes[0] ?? null;
|
|
1443
|
-
} catch {
|
|
1484
|
+
} catch (e) {
|
|
1444
1485
|
// at this point we have no choice but to fall back to the server, if it wouldn't
|
|
1445
|
-
// bring us right back here, turning this into an endless loop
|
|
1446
|
-
if
|
|
1447
|
-
|
|
1486
|
+
// bring us right back here, turning this into an endless loop.
|
|
1487
|
+
// if __data.json returned 404, the route doesn't exist — don't reload or we loop
|
|
1488
|
+
if (
|
|
1489
|
+
!(e instanceof HttpError && e.status === 404) &&
|
|
1490
|
+
(url.origin !== origin || url.pathname !== location.pathname || hydrated)
|
|
1491
|
+
) {
|
|
1492
|
+
return await native_navigation(url);
|
|
1448
1493
|
}
|
|
1449
1494
|
}
|
|
1450
1495
|
}
|
|
@@ -1473,18 +1518,30 @@ async function load_root_error_page({ status, error, url, route }) {
|
|
|
1473
1518
|
url,
|
|
1474
1519
|
params,
|
|
1475
1520
|
branch: [root_layout, root_error],
|
|
1476
|
-
status,
|
|
1477
1521
|
error,
|
|
1478
1522
|
errors: [],
|
|
1479
1523
|
route: null
|
|
1480
1524
|
});
|
|
1481
1525
|
} catch (error) {
|
|
1526
|
+
// client-side navigation if the root layout loader throws a redirect while
|
|
1527
|
+
// rendering the default error page
|
|
1482
1528
|
if (error instanceof Redirect) {
|
|
1483
|
-
|
|
1484
|
-
return
|
|
1529
|
+
await _goto(new URL(error.location, location.href), {}, 0);
|
|
1530
|
+
return;
|
|
1485
1531
|
}
|
|
1486
1532
|
|
|
1487
|
-
//
|
|
1533
|
+
// otherwise, render the static error page
|
|
1534
|
+
const error_template = await app.get_error_template();
|
|
1535
|
+
const handled = await handle_error(error, { url, params, route });
|
|
1536
|
+
const message = String(handled?.message ?? '')
|
|
1537
|
+
.replace(/&/g, '&')
|
|
1538
|
+
.replace(/</g, '<')
|
|
1539
|
+
.replace(/>/g, '>');
|
|
1540
|
+
const html = error_template({ status: handled.status, message });
|
|
1541
|
+
const parsed = new DOMParser().parseFromString(html, 'text/html');
|
|
1542
|
+
document.documentElement.replaceChild(document.adoptNode(parsed.head), document.head);
|
|
1543
|
+
document.documentElement.replaceChild(document.adoptNode(parsed.body), document.body);
|
|
1544
|
+
|
|
1488
1545
|
throw error;
|
|
1489
1546
|
}
|
|
1490
1547
|
}
|
|
@@ -1669,8 +1726,10 @@ function _before_navigate({ url, type, intent, delta, event, scroll }) {
|
|
|
1669
1726
|
* nav_token?: {};
|
|
1670
1727
|
* accept?: () => void;
|
|
1671
1728
|
* block?: () => void;
|
|
1672
|
-
* event?: Event
|
|
1729
|
+
* event?: Event;
|
|
1730
|
+
* intent?: NavigationIntent | undefined
|
|
1673
1731
|
* }} opts
|
|
1732
|
+
* @returns {Promise<void>}
|
|
1674
1733
|
*/
|
|
1675
1734
|
async function navigate({
|
|
1676
1735
|
type,
|
|
@@ -1684,12 +1743,14 @@ async function navigate({
|
|
|
1684
1743
|
nav_token = {},
|
|
1685
1744
|
accept = noop,
|
|
1686
1745
|
block = noop,
|
|
1687
|
-
event
|
|
1746
|
+
event,
|
|
1747
|
+
intent
|
|
1688
1748
|
}) {
|
|
1689
|
-
const prev_token =
|
|
1690
|
-
|
|
1749
|
+
const prev_token = navigation_token;
|
|
1750
|
+
const prev_invalidation_token = invalidation_token;
|
|
1751
|
+
navigation_token = invalidation_token = nav_token;
|
|
1691
1752
|
|
|
1692
|
-
|
|
1753
|
+
intent ??= await get_navigation_intent(url, false);
|
|
1693
1754
|
const nav =
|
|
1694
1755
|
type === 'enter'
|
|
1695
1756
|
? create_navigation(current, intent, url, type)
|
|
@@ -1705,7 +1766,8 @@ async function navigate({
|
|
|
1705
1766
|
|
|
1706
1767
|
if (!nav) {
|
|
1707
1768
|
block();
|
|
1708
|
-
if (
|
|
1769
|
+
if (navigation_token === nav_token) navigation_token = prev_token;
|
|
1770
|
+
if (invalidation_token === nav_token) invalidation_token = prev_invalidation_token;
|
|
1709
1771
|
return;
|
|
1710
1772
|
}
|
|
1711
1773
|
|
|
@@ -1743,7 +1805,6 @@ async function navigate({
|
|
|
1743
1805
|
route: { id: null }
|
|
1744
1806
|
}
|
|
1745
1807
|
),
|
|
1746
|
-
404,
|
|
1747
1808
|
replace_state
|
|
1748
1809
|
);
|
|
1749
1810
|
} else {
|
|
@@ -1758,7 +1819,6 @@ async function navigate({
|
|
|
1758
1819
|
params: {},
|
|
1759
1820
|
route: { id: null }
|
|
1760
1821
|
}),
|
|
1761
|
-
404,
|
|
1762
1822
|
replace_state
|
|
1763
1823
|
);
|
|
1764
1824
|
}
|
|
@@ -1769,11 +1829,13 @@ async function navigate({
|
|
|
1769
1829
|
url = intent?.url || url;
|
|
1770
1830
|
|
|
1771
1831
|
// abort if user navigated during update
|
|
1772
|
-
if (
|
|
1832
|
+
if (navigation_token !== nav_token) {
|
|
1773
1833
|
nav.reject(new Error('navigation aborted'));
|
|
1774
|
-
return
|
|
1834
|
+
return;
|
|
1775
1835
|
}
|
|
1776
1836
|
|
|
1837
|
+
if (!navigation_result) return;
|
|
1838
|
+
|
|
1777
1839
|
if (navigation_result.type === 'redirect') {
|
|
1778
1840
|
// whatwg fetch spec https://fetch.spec.whatwg.org/#http-redirect-fetch says to error after 20 redirects
|
|
1779
1841
|
if (redirect_count < 20) {
|
|
@@ -1794,7 +1856,6 @@ async function navigate({
|
|
|
1794
1856
|
}
|
|
1795
1857
|
|
|
1796
1858
|
navigation_result = await load_root_error_page({
|
|
1797
|
-
status: 500,
|
|
1798
1859
|
error: await handle_error(new Error('Redirect loop'), {
|
|
1799
1860
|
url,
|
|
1800
1861
|
params: {},
|
|
@@ -1803,12 +1864,14 @@ async function navigate({
|
|
|
1803
1864
|
url,
|
|
1804
1865
|
route: { id: null }
|
|
1805
1866
|
});
|
|
1867
|
+
|
|
1868
|
+
if (!navigation_result) return;
|
|
1806
1869
|
} else if (/** @type {number} */ (navigation_result.props.page.status) >= 400) {
|
|
1807
1870
|
const updated = await stores.updated.check();
|
|
1808
1871
|
if (updated) {
|
|
1809
1872
|
// Before reloading, try to update the service worker if it exists
|
|
1810
1873
|
await update_service_worker();
|
|
1811
|
-
await native_navigation(url, replace_state);
|
|
1874
|
+
return await native_navigation(url, replace_state);
|
|
1812
1875
|
}
|
|
1813
1876
|
}
|
|
1814
1877
|
|
|
@@ -1903,6 +1966,16 @@ async function navigate({
|
|
|
1903
1966
|
navigation_result.props.page.url = url;
|
|
1904
1967
|
}
|
|
1905
1968
|
|
|
1969
|
+
// Remove focus before updating the component tree, so that blur/focusout
|
|
1970
|
+
// handlers fire while the old component's data is still valid (#14575)
|
|
1971
|
+
if (
|
|
1972
|
+
!keepfocus &&
|
|
1973
|
+
document.activeElement instanceof HTMLElement &&
|
|
1974
|
+
document.activeElement !== document.body
|
|
1975
|
+
) {
|
|
1976
|
+
document.activeElement.blur();
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1906
1979
|
const fork = load_cache_fork && (await load_cache_fork);
|
|
1907
1980
|
|
|
1908
1981
|
if (fork) {
|
|
@@ -1934,10 +2007,10 @@ async function navigate({
|
|
|
1934
2007
|
await svelte.tick();
|
|
1935
2008
|
await svelte.tick();
|
|
1936
2009
|
|
|
1937
|
-
if (
|
|
2010
|
+
if (navigation_token !== nav_token) {
|
|
1938
2011
|
// a new navigation happened while we were waiting for the DOM to update, so abort
|
|
1939
2012
|
nav.reject(new Error('navigation aborted'));
|
|
1940
|
-
return
|
|
2013
|
+
return;
|
|
1941
2014
|
}
|
|
1942
2015
|
|
|
1943
2016
|
// Check for async rendering error
|
|
@@ -1981,10 +2054,6 @@ async function navigate({
|
|
|
1981
2054
|
|
|
1982
2055
|
is_navigating = false;
|
|
1983
2056
|
|
|
1984
|
-
if (type === 'popstate') {
|
|
1985
|
-
restore_snapshot(current_navigation_index);
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
2057
|
nav.fulfil(undefined);
|
|
1989
2058
|
|
|
1990
2059
|
// Update to.scroll to the actual scroll position after navigation completed
|
|
@@ -1996,6 +2065,10 @@ async function navigate({
|
|
|
1996
2065
|
fn(/** @type {import('@sveltejs/kit').AfterNavigate} */ (nav.navigation))
|
|
1997
2066
|
);
|
|
1998
2067
|
|
|
2068
|
+
if (type === 'popstate') {
|
|
2069
|
+
restore_snapshot(current_navigation_index);
|
|
2070
|
+
}
|
|
2071
|
+
|
|
1999
2072
|
stores.navigating.set((navigating.current = null));
|
|
2000
2073
|
|
|
2001
2074
|
updating = false;
|
|
@@ -2006,23 +2079,21 @@ async function navigate({
|
|
|
2006
2079
|
* @param {URL} url
|
|
2007
2080
|
* @param {{ id: string | null }} route
|
|
2008
2081
|
* @param {App.Error} error
|
|
2009
|
-
* @param {number} status
|
|
2010
2082
|
* @param {boolean} [replace_state]
|
|
2011
|
-
* @returns {Promise<import('./types.js').NavigationFinished>}
|
|
2083
|
+
* @returns {Promise<import('./types.js').NavigationFinished | undefined>}
|
|
2012
2084
|
*/
|
|
2013
|
-
async function server_fallback(url, route, error,
|
|
2085
|
+
async function server_fallback(url, route, error, replace_state) {
|
|
2014
2086
|
if (url.origin === origin && url.pathname === location.pathname && !hydrated) {
|
|
2015
2087
|
// We would reload the same page we're currently on, which isn't hydrated,
|
|
2016
2088
|
// which means no SSR, which means we would end up in an endless loop
|
|
2017
2089
|
return await load_root_error_page({
|
|
2018
|
-
status,
|
|
2019
2090
|
error,
|
|
2020
2091
|
url,
|
|
2021
2092
|
route
|
|
2022
2093
|
});
|
|
2023
2094
|
}
|
|
2024
2095
|
|
|
2025
|
-
if (DEV && status !== 404) {
|
|
2096
|
+
if (DEV && error.status !== 404) {
|
|
2026
2097
|
console.error(
|
|
2027
2098
|
'An error occurred while loading the page. This will cause a full page reload. (This message will only appear during development.)'
|
|
2028
2099
|
);
|
|
@@ -2161,9 +2232,9 @@ function setup_preload() {
|
|
|
2161
2232
|
/**
|
|
2162
2233
|
* @param {unknown} error
|
|
2163
2234
|
* @param {import('@sveltejs/kit').NavigationEvent} event
|
|
2164
|
-
* @returns {
|
|
2235
|
+
* @returns {Promise<App.Error>}
|
|
2165
2236
|
*/
|
|
2166
|
-
function handle_error(error, event) {
|
|
2237
|
+
export async function handle_error(error, event) {
|
|
2167
2238
|
if (error instanceof HttpError) {
|
|
2168
2239
|
return error.body;
|
|
2169
2240
|
}
|
|
@@ -2175,10 +2246,9 @@ function handle_error(error, event) {
|
|
|
2175
2246
|
|
|
2176
2247
|
const status = get_status(error);
|
|
2177
2248
|
const message = get_message(error);
|
|
2249
|
+
const app_error = (await app.hooks.handleError({ error, event, status, message })) ?? { message };
|
|
2178
2250
|
|
|
2179
|
-
return (
|
|
2180
|
-
app.hooks.handleError({ error, event, status, message }) ?? /** @type {any} */ ({ message })
|
|
2181
|
-
);
|
|
2251
|
+
return { ...app_error, status: get_status(app_error, error) };
|
|
2182
2252
|
}
|
|
2183
2253
|
|
|
2184
2254
|
/**
|
|
@@ -2262,9 +2332,11 @@ export function disableScrollHandling() {
|
|
|
2262
2332
|
* Allows you to navigate programmatically to a given route, with options such as keeping the current element focused.
|
|
2263
2333
|
* Returns a Promise that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) to the specified `url`.
|
|
2264
2334
|
*
|
|
2265
|
-
*
|
|
2335
|
+
* `goto` is intended for navigations to routes that belong to the app.
|
|
2336
|
+
* If the URL does not resolve to a route within the app, the returned promise will reject.
|
|
2337
|
+
* For external URLs, use `window.location = url` to perform a full-page navigation instead of calling `goto(url)`.
|
|
2266
2338
|
*
|
|
2267
|
-
* @param {string | URL} url Where to navigate to. Note that if you've set [`config.
|
|
2339
|
+
* @param {string | URL} url Where to navigate to. Note that if you've set [`config.paths.base`](https://svelte.dev/docs/kit/configuration#paths) and the URL is root-relative, you need to prepend the base path if you want to navigate within the app.
|
|
2268
2340
|
* @param {Object} [opts] Options related to the navigation
|
|
2269
2341
|
* @param {boolean} [opts.replaceState] If `true`, will replace the current `history` entry rather than creating a new one with `pushState`
|
|
2270
2342
|
* @param {boolean} [opts.noScroll] If `true`, the browser will maintain its scroll position rather than scrolling to the top of the page after navigation
|
|
@@ -2274,7 +2346,7 @@ export function disableScrollHandling() {
|
|
|
2274
2346
|
* @param {App.PageState} [opts.state] An optional object that will be available as `page.state`
|
|
2275
2347
|
* @returns {Promise<void>}
|
|
2276
2348
|
*/
|
|
2277
|
-
export function goto(url, opts = {}) {
|
|
2349
|
+
export async function goto(url, opts = {}) {
|
|
2278
2350
|
if (!BROWSER) {
|
|
2279
2351
|
throw new Error('Cannot call goto(...) on the server');
|
|
2280
2352
|
}
|
|
@@ -2282,16 +2354,24 @@ export function goto(url, opts = {}) {
|
|
|
2282
2354
|
url = new URL(resolve_url(url));
|
|
2283
2355
|
|
|
2284
2356
|
if (url.origin !== origin) {
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
: 'goto: invalid URL'
|
|
2290
|
-
)
|
|
2357
|
+
throw new Error(
|
|
2358
|
+
DEV
|
|
2359
|
+
? `Cannot use \`goto\` with an external URL. Use \`window.location = "${url}"\` instead`
|
|
2360
|
+
: 'goto: invalid URL'
|
|
2291
2361
|
);
|
|
2292
2362
|
}
|
|
2293
2363
|
|
|
2294
|
-
|
|
2364
|
+
const intent = await get_navigation_intent(url, false);
|
|
2365
|
+
|
|
2366
|
+
if (!intent) {
|
|
2367
|
+
throw new Error(
|
|
2368
|
+
DEV
|
|
2369
|
+
? `Cannot use \`goto\` with a URL that does not resolve to a route within the app. Use \`window.location = "${url}"\` instead`
|
|
2370
|
+
: 'goto: invalid URL'
|
|
2371
|
+
);
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
return _goto(url, opts, 0, {}, intent);
|
|
2295
2375
|
}
|
|
2296
2376
|
|
|
2297
2377
|
/**
|
|
@@ -2372,7 +2452,7 @@ export function refreshAll({ includeLoadFunctions = true } = {}) {
|
|
|
2372
2452
|
* Returns a Promise that resolves with the result of running the new route's `load` functions once the preload is complete.
|
|
2373
2453
|
*
|
|
2374
2454
|
* @param {string} href Page to preload
|
|
2375
|
-
* @returns {Promise<{ type: 'loaded';
|
|
2455
|
+
* @returns {Promise<({ type: 'loaded'; data: Record<string, any> } | { type: 'redirect'; location: string } | { type: 'error'; error: App.Error }) & { status: number; }>}
|
|
2376
2456
|
*/
|
|
2377
2457
|
export async function preloadData(href) {
|
|
2378
2458
|
if (!BROWSER) {
|
|
@@ -2390,11 +2470,21 @@ export async function preloadData(href) {
|
|
|
2390
2470
|
if (result.type === 'redirect') {
|
|
2391
2471
|
return {
|
|
2392
2472
|
type: result.type,
|
|
2473
|
+
status: result.status,
|
|
2393
2474
|
location: result.location
|
|
2394
2475
|
};
|
|
2395
2476
|
}
|
|
2396
2477
|
|
|
2397
2478
|
const { status, data } = result.props.page ?? page;
|
|
2479
|
+
|
|
2480
|
+
if (result.type === 'loaded' && result.state.error) {
|
|
2481
|
+
return {
|
|
2482
|
+
type: 'error',
|
|
2483
|
+
status,
|
|
2484
|
+
error: result.state.error
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2398
2488
|
return { type: result.type, status, data };
|
|
2399
2489
|
}
|
|
2400
2490
|
|
|
@@ -2415,7 +2505,9 @@ export async function preloadCode(pathname) {
|
|
|
2415
2505
|
throw new Error('Cannot call preloadCode(...) on the server');
|
|
2416
2506
|
}
|
|
2417
2507
|
|
|
2418
|
-
|
|
2508
|
+
// `current.url` is null until the first navigation/hydration completes, so fall back
|
|
2509
|
+
// to `location` to support calling `preloadCode` during initial page load (#13297)
|
|
2510
|
+
const url = new URL(pathname, current.url ?? location.href);
|
|
2419
2511
|
|
|
2420
2512
|
if (DEV) {
|
|
2421
2513
|
if (!pathname.startsWith('/')) {
|
|
@@ -2543,7 +2635,7 @@ export async function applyAction(result) {
|
|
|
2543
2635
|
}
|
|
2544
2636
|
|
|
2545
2637
|
if (result.type === 'error') {
|
|
2546
|
-
await set_nearest_error_page(result.error
|
|
2638
|
+
await set_nearest_error_page(result.error);
|
|
2547
2639
|
} else if (result.type === 'redirect') {
|
|
2548
2640
|
await _goto(result.location, { invalidateAll: true }, 0);
|
|
2549
2641
|
} else {
|
|
@@ -2570,9 +2662,8 @@ export async function applyAction(result) {
|
|
|
2570
2662
|
|
|
2571
2663
|
/**
|
|
2572
2664
|
* @param {App.Error} error
|
|
2573
|
-
* @param {number} status
|
|
2574
2665
|
*/
|
|
2575
|
-
export async function set_nearest_error_page(error
|
|
2666
|
+
export async function set_nearest_error_page(error) {
|
|
2576
2667
|
const url = new URL(location.href);
|
|
2577
2668
|
|
|
2578
2669
|
const { branch, route } = current;
|
|
@@ -2584,7 +2675,6 @@ export async function set_nearest_error_page(error, status = 500) {
|
|
|
2584
2675
|
url,
|
|
2585
2676
|
params: current.params,
|
|
2586
2677
|
branch: branch.slice(0, error_load.idx).concat(error_load.node),
|
|
2587
|
-
status,
|
|
2588
2678
|
error,
|
|
2589
2679
|
// do not set errors, we haven't changed the page so the previous ones are still current
|
|
2590
2680
|
route
|
|
@@ -2819,7 +2909,7 @@ function _start_router() {
|
|
|
2819
2909
|
|
|
2820
2910
|
if (event.state?.[HISTORY_INDEX]) {
|
|
2821
2911
|
const history_index = event.state[HISTORY_INDEX];
|
|
2822
|
-
|
|
2912
|
+
navigation_token = invalidation_token = {};
|
|
2823
2913
|
|
|
2824
2914
|
// if a popstate-driven navigation is cancelled, we need to counteract it
|
|
2825
2915
|
// with history.go, which means we end up back here, hence this check
|
|
@@ -2868,7 +2958,7 @@ function _start_router() {
|
|
|
2868
2958
|
block: () => {
|
|
2869
2959
|
history.go(-delta);
|
|
2870
2960
|
},
|
|
2871
|
-
nav_token:
|
|
2961
|
+
nav_token: navigation_token,
|
|
2872
2962
|
event
|
|
2873
2963
|
});
|
|
2874
2964
|
} else {
|
|
@@ -2937,10 +3027,11 @@ function _start_router() {
|
|
|
2937
3027
|
/**
|
|
2938
3028
|
* @param {HTMLElement} target
|
|
2939
3029
|
* @param {import('./types.js').HydrateOptions} opts
|
|
3030
|
+
* @returns {Promise<void>}
|
|
2940
3031
|
*/
|
|
2941
3032
|
async function _hydrate(
|
|
2942
3033
|
target,
|
|
2943
|
-
{ status
|
|
3034
|
+
{ status, error, node_ids, params, route, server_route, data: server_data_nodes, form }
|
|
2944
3035
|
) {
|
|
2945
3036
|
hydrated = true;
|
|
2946
3037
|
|
|
@@ -3023,13 +3114,13 @@ async function _hydrate(
|
|
|
3023
3114
|
if (error instanceof Redirect) {
|
|
3024
3115
|
// this is a real edge case — `load` would need to return
|
|
3025
3116
|
// a redirect but only in the browser
|
|
3026
|
-
await native_navigation(new URL(error.location, location.href));
|
|
3027
|
-
return;
|
|
3117
|
+
return await native_navigation(new URL(error.location, location.href));
|
|
3028
3118
|
}
|
|
3029
3119
|
|
|
3120
|
+
const handled_error = await handle_error(error, { url, params, route });
|
|
3121
|
+
|
|
3030
3122
|
result = await load_root_error_page({
|
|
3031
|
-
|
|
3032
|
-
error: await handle_error(error, { url, params, route }),
|
|
3123
|
+
error: handled_error,
|
|
3033
3124
|
url,
|
|
3034
3125
|
route
|
|
3035
3126
|
});
|
|
@@ -3038,6 +3129,10 @@ async function _hydrate(
|
|
|
3038
3129
|
hydrate = false;
|
|
3039
3130
|
}
|
|
3040
3131
|
|
|
3132
|
+
// Exit early when we encounter a redirect while loading the root error page.
|
|
3133
|
+
// In this case, `initialize` will be called later on
|
|
3134
|
+
if (!result) return;
|
|
3135
|
+
|
|
3041
3136
|
if (result.props.page) {
|
|
3042
3137
|
result.props.page.state = {};
|
|
3043
3138
|
}
|