@sveltejs/kit 3.0.0-next.4 → 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 +184 -93
- 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 +9 -2
- 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,6 +575,7 @@ 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;
|
|
@@ -706,7 +733,7 @@ async function initialize(result, target, hydrate) {
|
|
|
706
733
|
transformError: __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__
|
|
707
734
|
? /** @param {unknown} e */ async (e) => {
|
|
708
735
|
const error = await handle_error(e, current.nav);
|
|
709
|
-
rendering_error = { error, status:
|
|
736
|
+
rendering_error = { error, status: error.status };
|
|
710
737
|
page.error = error;
|
|
711
738
|
page.status = rendering_error.status;
|
|
712
739
|
return error;
|
|
@@ -718,8 +745,6 @@ async function initialize(result, target, hydrate) {
|
|
|
718
745
|
// which causes component script blocks to run asynchronously
|
|
719
746
|
void (await Promise.resolve());
|
|
720
747
|
|
|
721
|
-
restore_snapshot(current_navigation_index);
|
|
722
|
-
|
|
723
748
|
if (hydrate) {
|
|
724
749
|
/** @type {import('@sveltejs/kit').AfterNavigate} */
|
|
725
750
|
const navigation = {
|
|
@@ -736,6 +761,8 @@ async function initialize(result, target, hydrate) {
|
|
|
736
761
|
after_navigate_callbacks.forEach((fn) => fn(navigation));
|
|
737
762
|
}
|
|
738
763
|
|
|
764
|
+
restore_snapshot(current_navigation_index);
|
|
765
|
+
|
|
739
766
|
started = true;
|
|
740
767
|
}
|
|
741
768
|
|
|
@@ -746,7 +773,7 @@ async function initialize(result, target, hydrate) {
|
|
|
746
773
|
* params: Record<string, string>;
|
|
747
774
|
* branch: Array<import('./types.js').BranchNode | undefined>;
|
|
748
775
|
* errors?: Array<import('types').CSRPageNodeLoader | undefined>;
|
|
749
|
-
* status
|
|
776
|
+
* status?: number;
|
|
750
777
|
* error: App.Error | null;
|
|
751
778
|
* route: import('types').CSRRoute | null;
|
|
752
779
|
* form?: Record<string, any> | null;
|
|
@@ -863,7 +890,7 @@ async function get_navigation_result_from_branch({
|
|
|
863
890
|
id: route?.id ?? null
|
|
864
891
|
},
|
|
865
892
|
state: {},
|
|
866
|
-
status,
|
|
893
|
+
status: status ?? error?.status ?? 200,
|
|
867
894
|
url: new URL(url),
|
|
868
895
|
form: form ?? null,
|
|
869
896
|
// The whole page store is updated, but this way the object reference stays the same
|
|
@@ -1150,6 +1177,10 @@ function diff_search_params(old_url, new_url) {
|
|
|
1150
1177
|
* @returns {import('./types.js').NavigationFinished}
|
|
1151
1178
|
*/
|
|
1152
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;
|
|
1153
1184
|
return {
|
|
1154
1185
|
type: 'loaded',
|
|
1155
1186
|
state: {
|
|
@@ -1160,16 +1191,26 @@ function preload_error({ error, url, route, params }) {
|
|
|
1160
1191
|
branch: []
|
|
1161
1192
|
},
|
|
1162
1193
|
props: {
|
|
1163
|
-
page:
|
|
1194
|
+
page: new_page,
|
|
1164
1195
|
constructors: []
|
|
1165
1196
|
}
|
|
1166
1197
|
};
|
|
1167
1198
|
}
|
|
1168
1199
|
|
|
1169
1200
|
/**
|
|
1170
|
-
* @
|
|
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
|
|
1171
1208
|
* @returns {Promise<import('./types.js').NavigationResult>}
|
|
1172
1209
|
*/
|
|
1210
|
+
/**
|
|
1211
|
+
* @param {import('./types.js').NavigationIntent & { preload?: {} }} intent
|
|
1212
|
+
* @returns {Promise<import('./types.js').NavigationResult | undefined>}
|
|
1213
|
+
*/
|
|
1173
1214
|
async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
1174
1215
|
if (load_cache?.id === id) {
|
|
1175
1216
|
// the preload becomes the real navigation
|
|
@@ -1225,12 +1266,11 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1225
1266
|
} catch (error) {
|
|
1226
1267
|
const handled_error = await handle_error(error, { url, params, route: { id } });
|
|
1227
1268
|
|
|
1228
|
-
if (preload_tokens.has(preload)) {
|
|
1269
|
+
if (preload && preload_tokens.has(preload)) {
|
|
1229
1270
|
return preload_error({ error: handled_error, url, params, route });
|
|
1230
1271
|
}
|
|
1231
1272
|
|
|
1232
1273
|
return load_root_error_page({
|
|
1233
|
-
status: get_status(error),
|
|
1234
1274
|
error: handled_error,
|
|
1235
1275
|
url,
|
|
1236
1276
|
route
|
|
@@ -1273,7 +1313,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1273
1313
|
|
|
1274
1314
|
if (server_data_node?.type === 'error') {
|
|
1275
1315
|
// rethrow and catch below
|
|
1276
|
-
throw server_data_node;
|
|
1316
|
+
throw new HttpError(server_data_node.error.status, server_data_node.error);
|
|
1277
1317
|
}
|
|
1278
1318
|
|
|
1279
1319
|
return load_node({
|
|
@@ -1311,27 +1351,27 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1311
1351
|
if (err instanceof Redirect) {
|
|
1312
1352
|
return {
|
|
1313
1353
|
type: 'redirect',
|
|
1354
|
+
status: err.status,
|
|
1314
1355
|
location: err.location
|
|
1315
1356
|
};
|
|
1316
1357
|
}
|
|
1317
1358
|
|
|
1318
|
-
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 } });
|
|
1319
1361
|
return preload_error({
|
|
1320
|
-
error
|
|
1362
|
+
error,
|
|
1321
1363
|
url,
|
|
1322
1364
|
params,
|
|
1323
1365
|
route
|
|
1324
1366
|
});
|
|
1325
1367
|
}
|
|
1326
1368
|
|
|
1327
|
-
let status = get_status(err);
|
|
1328
1369
|
/** @type {App.Error} */
|
|
1329
1370
|
let error;
|
|
1330
1371
|
|
|
1331
1372
|
if (server_data_nodes?.includes(/** @type {import('types').ServerErrorNode} */ (err))) {
|
|
1332
1373
|
// this is the server error rethrown above, reconstruct but don't invoke
|
|
1333
1374
|
// the client error handler; it should've already been handled on the server
|
|
1334
|
-
status = /** @type {import('types').ServerErrorNode} */ (err).status ?? status;
|
|
1335
1375
|
error = /** @type {import('types').ServerErrorNode} */ (err).error;
|
|
1336
1376
|
} else if (err instanceof HttpError) {
|
|
1337
1377
|
error = err.body;
|
|
@@ -1354,12 +1394,11 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1354
1394
|
params,
|
|
1355
1395
|
branch: branch.slice(0, error_load.idx).concat(error_load.node),
|
|
1356
1396
|
errors,
|
|
1357
|
-
status,
|
|
1358
1397
|
error,
|
|
1359
1398
|
route
|
|
1360
1399
|
});
|
|
1361
1400
|
} else {
|
|
1362
|
-
return await server_fallback(url, { id: route.id }, error
|
|
1401
|
+
return await server_fallback(url, { id: route.id }, error);
|
|
1363
1402
|
}
|
|
1364
1403
|
}
|
|
1365
1404
|
} else {
|
|
@@ -1374,7 +1413,6 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1374
1413
|
params,
|
|
1375
1414
|
branch,
|
|
1376
1415
|
errors,
|
|
1377
|
-
status: 200,
|
|
1378
1416
|
error: null,
|
|
1379
1417
|
route,
|
|
1380
1418
|
// Reset `form` on navigation, but not invalidation
|
|
@@ -1413,14 +1451,13 @@ async function load_nearest_error_page(i, branch, errors) {
|
|
|
1413
1451
|
|
|
1414
1452
|
/**
|
|
1415
1453
|
* @param {{
|
|
1416
|
-
* status: number;
|
|
1417
1454
|
* error: App.Error;
|
|
1418
1455
|
* url: URL;
|
|
1419
1456
|
* route: { id: string | null }
|
|
1420
1457
|
* }} opts
|
|
1421
|
-
* @returns {Promise<import('./types.js').NavigationFinished>}
|
|
1458
|
+
* @returns {Promise<import('./types.js').NavigationFinished | undefined>} returns `undefined` in case of a redirect
|
|
1422
1459
|
*/
|
|
1423
|
-
async function load_root_error_page({
|
|
1460
|
+
async function load_root_error_page({ error, url, route }) {
|
|
1424
1461
|
/** @type {Record<string, string>} */
|
|
1425
1462
|
const params = {}; // error page does not have params
|
|
1426
1463
|
|
|
@@ -1444,11 +1481,15 @@ async function load_root_error_page({ status, error, url, route }) {
|
|
|
1444
1481
|
}
|
|
1445
1482
|
|
|
1446
1483
|
server_data_node = server_data.nodes[0] ?? null;
|
|
1447
|
-
} catch {
|
|
1484
|
+
} catch (e) {
|
|
1448
1485
|
// at this point we have no choice but to fall back to the server, if it wouldn't
|
|
1449
|
-
// bring us right back here, turning this into an endless loop
|
|
1450
|
-
if
|
|
1451
|
-
|
|
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);
|
|
1452
1493
|
}
|
|
1453
1494
|
}
|
|
1454
1495
|
}
|
|
@@ -1477,18 +1518,30 @@ async function load_root_error_page({ status, error, url, route }) {
|
|
|
1477
1518
|
url,
|
|
1478
1519
|
params,
|
|
1479
1520
|
branch: [root_layout, root_error],
|
|
1480
|
-
status,
|
|
1481
1521
|
error,
|
|
1482
1522
|
errors: [],
|
|
1483
1523
|
route: null
|
|
1484
1524
|
});
|
|
1485
1525
|
} catch (error) {
|
|
1526
|
+
// client-side navigation if the root layout loader throws a redirect while
|
|
1527
|
+
// rendering the default error page
|
|
1486
1528
|
if (error instanceof Redirect) {
|
|
1487
|
-
|
|
1488
|
-
return
|
|
1529
|
+
await _goto(new URL(error.location, location.href), {}, 0);
|
|
1530
|
+
return;
|
|
1489
1531
|
}
|
|
1490
1532
|
|
|
1491
|
-
//
|
|
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
|
+
|
|
1492
1545
|
throw error;
|
|
1493
1546
|
}
|
|
1494
1547
|
}
|
|
@@ -1673,8 +1726,10 @@ function _before_navigate({ url, type, intent, delta, event, scroll }) {
|
|
|
1673
1726
|
* nav_token?: {};
|
|
1674
1727
|
* accept?: () => void;
|
|
1675
1728
|
* block?: () => void;
|
|
1676
|
-
* event?: Event
|
|
1729
|
+
* event?: Event;
|
|
1730
|
+
* intent?: NavigationIntent | undefined
|
|
1677
1731
|
* }} opts
|
|
1732
|
+
* @returns {Promise<void>}
|
|
1678
1733
|
*/
|
|
1679
1734
|
async function navigate({
|
|
1680
1735
|
type,
|
|
@@ -1688,12 +1743,14 @@ async function navigate({
|
|
|
1688
1743
|
nav_token = {},
|
|
1689
1744
|
accept = noop,
|
|
1690
1745
|
block = noop,
|
|
1691
|
-
event
|
|
1746
|
+
event,
|
|
1747
|
+
intent
|
|
1692
1748
|
}) {
|
|
1693
|
-
const prev_token =
|
|
1694
|
-
|
|
1749
|
+
const prev_token = navigation_token;
|
|
1750
|
+
const prev_invalidation_token = invalidation_token;
|
|
1751
|
+
navigation_token = invalidation_token = nav_token;
|
|
1695
1752
|
|
|
1696
|
-
|
|
1753
|
+
intent ??= await get_navigation_intent(url, false);
|
|
1697
1754
|
const nav =
|
|
1698
1755
|
type === 'enter'
|
|
1699
1756
|
? create_navigation(current, intent, url, type)
|
|
@@ -1709,7 +1766,8 @@ async function navigate({
|
|
|
1709
1766
|
|
|
1710
1767
|
if (!nav) {
|
|
1711
1768
|
block();
|
|
1712
|
-
if (
|
|
1769
|
+
if (navigation_token === nav_token) navigation_token = prev_token;
|
|
1770
|
+
if (invalidation_token === nav_token) invalidation_token = prev_invalidation_token;
|
|
1713
1771
|
return;
|
|
1714
1772
|
}
|
|
1715
1773
|
|
|
@@ -1747,7 +1805,6 @@ async function navigate({
|
|
|
1747
1805
|
route: { id: null }
|
|
1748
1806
|
}
|
|
1749
1807
|
),
|
|
1750
|
-
404,
|
|
1751
1808
|
replace_state
|
|
1752
1809
|
);
|
|
1753
1810
|
} else {
|
|
@@ -1762,7 +1819,6 @@ async function navigate({
|
|
|
1762
1819
|
params: {},
|
|
1763
1820
|
route: { id: null }
|
|
1764
1821
|
}),
|
|
1765
|
-
404,
|
|
1766
1822
|
replace_state
|
|
1767
1823
|
);
|
|
1768
1824
|
}
|
|
@@ -1773,11 +1829,13 @@ async function navigate({
|
|
|
1773
1829
|
url = intent?.url || url;
|
|
1774
1830
|
|
|
1775
1831
|
// abort if user navigated during update
|
|
1776
|
-
if (
|
|
1832
|
+
if (navigation_token !== nav_token) {
|
|
1777
1833
|
nav.reject(new Error('navigation aborted'));
|
|
1778
|
-
return
|
|
1834
|
+
return;
|
|
1779
1835
|
}
|
|
1780
1836
|
|
|
1837
|
+
if (!navigation_result) return;
|
|
1838
|
+
|
|
1781
1839
|
if (navigation_result.type === 'redirect') {
|
|
1782
1840
|
// whatwg fetch spec https://fetch.spec.whatwg.org/#http-redirect-fetch says to error after 20 redirects
|
|
1783
1841
|
if (redirect_count < 20) {
|
|
@@ -1798,7 +1856,6 @@ async function navigate({
|
|
|
1798
1856
|
}
|
|
1799
1857
|
|
|
1800
1858
|
navigation_result = await load_root_error_page({
|
|
1801
|
-
status: 500,
|
|
1802
1859
|
error: await handle_error(new Error('Redirect loop'), {
|
|
1803
1860
|
url,
|
|
1804
1861
|
params: {},
|
|
@@ -1807,12 +1864,14 @@ async function navigate({
|
|
|
1807
1864
|
url,
|
|
1808
1865
|
route: { id: null }
|
|
1809
1866
|
});
|
|
1867
|
+
|
|
1868
|
+
if (!navigation_result) return;
|
|
1810
1869
|
} else if (/** @type {number} */ (navigation_result.props.page.status) >= 400) {
|
|
1811
1870
|
const updated = await stores.updated.check();
|
|
1812
1871
|
if (updated) {
|
|
1813
1872
|
// Before reloading, try to update the service worker if it exists
|
|
1814
1873
|
await update_service_worker();
|
|
1815
|
-
await native_navigation(url, replace_state);
|
|
1874
|
+
return await native_navigation(url, replace_state);
|
|
1816
1875
|
}
|
|
1817
1876
|
}
|
|
1818
1877
|
|
|
@@ -1907,6 +1966,16 @@ async function navigate({
|
|
|
1907
1966
|
navigation_result.props.page.url = url;
|
|
1908
1967
|
}
|
|
1909
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
|
+
|
|
1910
1979
|
const fork = load_cache_fork && (await load_cache_fork);
|
|
1911
1980
|
|
|
1912
1981
|
if (fork) {
|
|
@@ -1938,10 +2007,10 @@ async function navigate({
|
|
|
1938
2007
|
await svelte.tick();
|
|
1939
2008
|
await svelte.tick();
|
|
1940
2009
|
|
|
1941
|
-
if (
|
|
2010
|
+
if (navigation_token !== nav_token) {
|
|
1942
2011
|
// a new navigation happened while we were waiting for the DOM to update, so abort
|
|
1943
2012
|
nav.reject(new Error('navigation aborted'));
|
|
1944
|
-
return
|
|
2013
|
+
return;
|
|
1945
2014
|
}
|
|
1946
2015
|
|
|
1947
2016
|
// Check for async rendering error
|
|
@@ -1985,10 +2054,6 @@ async function navigate({
|
|
|
1985
2054
|
|
|
1986
2055
|
is_navigating = false;
|
|
1987
2056
|
|
|
1988
|
-
if (type === 'popstate') {
|
|
1989
|
-
restore_snapshot(current_navigation_index);
|
|
1990
|
-
}
|
|
1991
|
-
|
|
1992
2057
|
nav.fulfil(undefined);
|
|
1993
2058
|
|
|
1994
2059
|
// Update to.scroll to the actual scroll position after navigation completed
|
|
@@ -2000,6 +2065,10 @@ async function navigate({
|
|
|
2000
2065
|
fn(/** @type {import('@sveltejs/kit').AfterNavigate} */ (nav.navigation))
|
|
2001
2066
|
);
|
|
2002
2067
|
|
|
2068
|
+
if (type === 'popstate') {
|
|
2069
|
+
restore_snapshot(current_navigation_index);
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2003
2072
|
stores.navigating.set((navigating.current = null));
|
|
2004
2073
|
|
|
2005
2074
|
updating = false;
|
|
@@ -2010,23 +2079,21 @@ async function navigate({
|
|
|
2010
2079
|
* @param {URL} url
|
|
2011
2080
|
* @param {{ id: string | null }} route
|
|
2012
2081
|
* @param {App.Error} error
|
|
2013
|
-
* @param {number} status
|
|
2014
2082
|
* @param {boolean} [replace_state]
|
|
2015
|
-
* @returns {Promise<import('./types.js').NavigationFinished>}
|
|
2083
|
+
* @returns {Promise<import('./types.js').NavigationFinished | undefined>}
|
|
2016
2084
|
*/
|
|
2017
|
-
async function server_fallback(url, route, error,
|
|
2085
|
+
async function server_fallback(url, route, error, replace_state) {
|
|
2018
2086
|
if (url.origin === origin && url.pathname === location.pathname && !hydrated) {
|
|
2019
2087
|
// We would reload the same page we're currently on, which isn't hydrated,
|
|
2020
2088
|
// which means no SSR, which means we would end up in an endless loop
|
|
2021
2089
|
return await load_root_error_page({
|
|
2022
|
-
status,
|
|
2023
2090
|
error,
|
|
2024
2091
|
url,
|
|
2025
2092
|
route
|
|
2026
2093
|
});
|
|
2027
2094
|
}
|
|
2028
2095
|
|
|
2029
|
-
if (DEV && status !== 404) {
|
|
2096
|
+
if (DEV && error.status !== 404) {
|
|
2030
2097
|
console.error(
|
|
2031
2098
|
'An error occurred while loading the page. This will cause a full page reload. (This message will only appear during development.)'
|
|
2032
2099
|
);
|
|
@@ -2165,9 +2232,9 @@ function setup_preload() {
|
|
|
2165
2232
|
/**
|
|
2166
2233
|
* @param {unknown} error
|
|
2167
2234
|
* @param {import('@sveltejs/kit').NavigationEvent} event
|
|
2168
|
-
* @returns {
|
|
2235
|
+
* @returns {Promise<App.Error>}
|
|
2169
2236
|
*/
|
|
2170
|
-
function handle_error(error, event) {
|
|
2237
|
+
export async function handle_error(error, event) {
|
|
2171
2238
|
if (error instanceof HttpError) {
|
|
2172
2239
|
return error.body;
|
|
2173
2240
|
}
|
|
@@ -2179,10 +2246,9 @@ function handle_error(error, event) {
|
|
|
2179
2246
|
|
|
2180
2247
|
const status = get_status(error);
|
|
2181
2248
|
const message = get_message(error);
|
|
2249
|
+
const app_error = (await app.hooks.handleError({ error, event, status, message })) ?? { message };
|
|
2182
2250
|
|
|
2183
|
-
return (
|
|
2184
|
-
app.hooks.handleError({ error, event, status, message }) ?? /** @type {any} */ ({ message })
|
|
2185
|
-
);
|
|
2251
|
+
return { ...app_error, status: get_status(app_error, error) };
|
|
2186
2252
|
}
|
|
2187
2253
|
|
|
2188
2254
|
/**
|
|
@@ -2266,9 +2332,11 @@ export function disableScrollHandling() {
|
|
|
2266
2332
|
* Allows you to navigate programmatically to a given route, with options such as keeping the current element focused.
|
|
2267
2333
|
* Returns a Promise that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) to the specified `url`.
|
|
2268
2334
|
*
|
|
2269
|
-
*
|
|
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)`.
|
|
2270
2338
|
*
|
|
2271
|
-
* @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.
|
|
2272
2340
|
* @param {Object} [opts] Options related to the navigation
|
|
2273
2341
|
* @param {boolean} [opts.replaceState] If `true`, will replace the current `history` entry rather than creating a new one with `pushState`
|
|
2274
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
|
|
@@ -2278,7 +2346,7 @@ export function disableScrollHandling() {
|
|
|
2278
2346
|
* @param {App.PageState} [opts.state] An optional object that will be available as `page.state`
|
|
2279
2347
|
* @returns {Promise<void>}
|
|
2280
2348
|
*/
|
|
2281
|
-
export function goto(url, opts = {}) {
|
|
2349
|
+
export async function goto(url, opts = {}) {
|
|
2282
2350
|
if (!BROWSER) {
|
|
2283
2351
|
throw new Error('Cannot call goto(...) on the server');
|
|
2284
2352
|
}
|
|
@@ -2286,16 +2354,24 @@ export function goto(url, opts = {}) {
|
|
|
2286
2354
|
url = new URL(resolve_url(url));
|
|
2287
2355
|
|
|
2288
2356
|
if (url.origin !== origin) {
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
: 'goto: invalid URL'
|
|
2294
|
-
)
|
|
2357
|
+
throw new Error(
|
|
2358
|
+
DEV
|
|
2359
|
+
? `Cannot use \`goto\` with an external URL. Use \`window.location = "${url}"\` instead`
|
|
2360
|
+
: 'goto: invalid URL'
|
|
2295
2361
|
);
|
|
2296
2362
|
}
|
|
2297
2363
|
|
|
2298
|
-
|
|
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);
|
|
2299
2375
|
}
|
|
2300
2376
|
|
|
2301
2377
|
/**
|
|
@@ -2376,7 +2452,7 @@ export function refreshAll({ includeLoadFunctions = true } = {}) {
|
|
|
2376
2452
|
* Returns a Promise that resolves with the result of running the new route's `load` functions once the preload is complete.
|
|
2377
2453
|
*
|
|
2378
2454
|
* @param {string} href Page to preload
|
|
2379
|
-
* @returns {Promise<{ type: 'loaded';
|
|
2455
|
+
* @returns {Promise<({ type: 'loaded'; data: Record<string, any> } | { type: 'redirect'; location: string } | { type: 'error'; error: App.Error }) & { status: number; }>}
|
|
2380
2456
|
*/
|
|
2381
2457
|
export async function preloadData(href) {
|
|
2382
2458
|
if (!BROWSER) {
|
|
@@ -2394,11 +2470,21 @@ export async function preloadData(href) {
|
|
|
2394
2470
|
if (result.type === 'redirect') {
|
|
2395
2471
|
return {
|
|
2396
2472
|
type: result.type,
|
|
2473
|
+
status: result.status,
|
|
2397
2474
|
location: result.location
|
|
2398
2475
|
};
|
|
2399
2476
|
}
|
|
2400
2477
|
|
|
2401
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
|
+
|
|
2402
2488
|
return { type: result.type, status, data };
|
|
2403
2489
|
}
|
|
2404
2490
|
|
|
@@ -2419,7 +2505,9 @@ export async function preloadCode(pathname) {
|
|
|
2419
2505
|
throw new Error('Cannot call preloadCode(...) on the server');
|
|
2420
2506
|
}
|
|
2421
2507
|
|
|
2422
|
-
|
|
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);
|
|
2423
2511
|
|
|
2424
2512
|
if (DEV) {
|
|
2425
2513
|
if (!pathname.startsWith('/')) {
|
|
@@ -2547,7 +2635,7 @@ export async function applyAction(result) {
|
|
|
2547
2635
|
}
|
|
2548
2636
|
|
|
2549
2637
|
if (result.type === 'error') {
|
|
2550
|
-
await set_nearest_error_page(result.error
|
|
2638
|
+
await set_nearest_error_page(result.error);
|
|
2551
2639
|
} else if (result.type === 'redirect') {
|
|
2552
2640
|
await _goto(result.location, { invalidateAll: true }, 0);
|
|
2553
2641
|
} else {
|
|
@@ -2574,9 +2662,8 @@ export async function applyAction(result) {
|
|
|
2574
2662
|
|
|
2575
2663
|
/**
|
|
2576
2664
|
* @param {App.Error} error
|
|
2577
|
-
* @param {number} status
|
|
2578
2665
|
*/
|
|
2579
|
-
export async function set_nearest_error_page(error
|
|
2666
|
+
export async function set_nearest_error_page(error) {
|
|
2580
2667
|
const url = new URL(location.href);
|
|
2581
2668
|
|
|
2582
2669
|
const { branch, route } = current;
|
|
@@ -2588,7 +2675,6 @@ export async function set_nearest_error_page(error, status = 500) {
|
|
|
2588
2675
|
url,
|
|
2589
2676
|
params: current.params,
|
|
2590
2677
|
branch: branch.slice(0, error_load.idx).concat(error_load.node),
|
|
2591
|
-
status,
|
|
2592
2678
|
error,
|
|
2593
2679
|
// do not set errors, we haven't changed the page so the previous ones are still current
|
|
2594
2680
|
route
|
|
@@ -2823,7 +2909,7 @@ function _start_router() {
|
|
|
2823
2909
|
|
|
2824
2910
|
if (event.state?.[HISTORY_INDEX]) {
|
|
2825
2911
|
const history_index = event.state[HISTORY_INDEX];
|
|
2826
|
-
|
|
2912
|
+
navigation_token = invalidation_token = {};
|
|
2827
2913
|
|
|
2828
2914
|
// if a popstate-driven navigation is cancelled, we need to counteract it
|
|
2829
2915
|
// with history.go, which means we end up back here, hence this check
|
|
@@ -2872,7 +2958,7 @@ function _start_router() {
|
|
|
2872
2958
|
block: () => {
|
|
2873
2959
|
history.go(-delta);
|
|
2874
2960
|
},
|
|
2875
|
-
nav_token:
|
|
2961
|
+
nav_token: navigation_token,
|
|
2876
2962
|
event
|
|
2877
2963
|
});
|
|
2878
2964
|
} else {
|
|
@@ -2941,10 +3027,11 @@ function _start_router() {
|
|
|
2941
3027
|
/**
|
|
2942
3028
|
* @param {HTMLElement} target
|
|
2943
3029
|
* @param {import('./types.js').HydrateOptions} opts
|
|
3030
|
+
* @returns {Promise<void>}
|
|
2944
3031
|
*/
|
|
2945
3032
|
async function _hydrate(
|
|
2946
3033
|
target,
|
|
2947
|
-
{ status
|
|
3034
|
+
{ status, error, node_ids, params, route, server_route, data: server_data_nodes, form }
|
|
2948
3035
|
) {
|
|
2949
3036
|
hydrated = true;
|
|
2950
3037
|
|
|
@@ -3027,13 +3114,13 @@ async function _hydrate(
|
|
|
3027
3114
|
if (error instanceof Redirect) {
|
|
3028
3115
|
// this is a real edge case — `load` would need to return
|
|
3029
3116
|
// a redirect but only in the browser
|
|
3030
|
-
await native_navigation(new URL(error.location, location.href));
|
|
3031
|
-
return;
|
|
3117
|
+
return await native_navigation(new URL(error.location, location.href));
|
|
3032
3118
|
}
|
|
3033
3119
|
|
|
3120
|
+
const handled_error = await handle_error(error, { url, params, route });
|
|
3121
|
+
|
|
3034
3122
|
result = await load_root_error_page({
|
|
3035
|
-
|
|
3036
|
-
error: await handle_error(error, { url, params, route }),
|
|
3123
|
+
error: handled_error,
|
|
3037
3124
|
url,
|
|
3038
3125
|
route
|
|
3039
3126
|
});
|
|
@@ -3042,6 +3129,10 @@ async function _hydrate(
|
|
|
3042
3129
|
hydrate = false;
|
|
3043
3130
|
}
|
|
3044
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
|
+
|
|
3045
3136
|
if (result.props.page) {
|
|
3046
3137
|
result.props.page.state = {};
|
|
3047
3138
|
}
|