@sveltejs/kit 3.0.0-next.1 → 3.0.0-next.10
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 +33 -21
- package/src/core/adapt/builder.js +29 -43
- package/src/core/adapt/index.js +1 -4
- package/src/core/config/index.js +132 -71
- package/src/core/config/options.js +294 -244
- package/src/core/config/types.d.ts +1 -1
- package/src/core/env.js +121 -5
- package/src/core/generate_manifest/index.js +12 -15
- package/src/core/postbuild/analyse.js +7 -19
- package/src/core/postbuild/crawl.js +22 -6
- package/src/core/postbuild/prerender.js +42 -23
- package/src/core/sync/create_manifest_data/index.js +53 -50
- package/src/core/sync/sync.js +0 -2
- package/src/core/sync/write_client_manifest.js +8 -15
- package/src/core/sync/write_env.js +2 -1
- package/src/core/sync/write_non_ambient.js +12 -9
- package/src/core/sync/write_server.js +13 -14
- package/src/core/sync/write_tsconfig.js +16 -8
- package/src/core/sync/write_tsconfig_test/package.json +7 -0
- package/src/core/sync/write_types/index.js +28 -24
- package/src/core/utils.js +2 -2
- package/src/exports/env/index.js +12 -0
- package/src/exports/hooks/index.js +3 -9
- package/src/exports/hooks/sequence.js +3 -2
- package/src/exports/index.js +35 -13
- package/src/exports/internal/client.js +5 -0
- package/src/exports/internal/env.js +2 -2
- package/src/exports/internal/index.js +1 -90
- package/src/exports/internal/{event.js → server/event.js} +1 -2
- package/src/exports/internal/server/index.js +33 -0
- package/src/exports/internal/shared.js +89 -0
- package/src/exports/node/index.js +62 -15
- package/src/exports/params.js +63 -0
- package/src/exports/public.d.ts +323 -170
- package/src/exports/url.js +86 -0
- package/src/exports/vite/build/build_server.js +53 -59
- package/src/exports/vite/build/remote.js +18 -11
- package/src/exports/vite/build/utils.js +0 -8
- package/src/exports/vite/dev/index.js +42 -44
- package/src/exports/vite/index.js +810 -593
- package/src/exports/vite/preview/index.js +29 -22
- package/src/exports/vite/static_analysis/index.js +2 -4
- package/src/exports/vite/static_analysis/types.d.ts +14 -0
- package/src/exports/vite/utils.js +21 -36
- package/src/runtime/app/env/internal.js +4 -4
- package/src/runtime/app/env/types.d.ts +1 -1
- package/src/runtime/app/environment/index.js +3 -3
- package/src/runtime/app/forms.js +24 -7
- package/src/runtime/app/paths/client.js +4 -10
- package/src/runtime/app/paths/internal/client.js +4 -2
- package/src/runtime/app/paths/internal/server.js +2 -23
- package/src/runtime/app/paths/public.d.ts +0 -28
- package/src/runtime/app/paths/server.js +9 -5
- package/src/runtime/app/server/remote/command.js +0 -3
- package/src/runtime/app/server/remote/form.js +27 -15
- package/src/runtime/app/server/remote/prerender.js +29 -36
- package/src/runtime/app/server/remote/query.js +101 -99
- package/src/runtime/app/server/remote/requested.js +22 -14
- package/src/runtime/app/server/remote/shared.js +30 -26
- package/src/runtime/app/state/client.js +1 -2
- package/src/runtime/app/stores.js +13 -76
- package/src/runtime/client/bundle.js +1 -1
- package/src/runtime/client/client-entry.js +3 -0
- package/src/runtime/client/client.js +411 -318
- package/src/runtime/client/entry.js +24 -3
- package/src/runtime/client/fetcher.js +3 -2
- package/src/runtime/client/ndjson.js +6 -33
- package/src/runtime/client/payload.js +17 -0
- package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
- package/src/runtime/client/remote-functions/command.svelte.js +7 -32
- package/src/runtime/client/remote-functions/form.svelte.js +183 -112
- package/src/runtime/client/remote-functions/prerender.svelte.js +29 -8
- package/src/runtime/client/remote-functions/query/index.js +7 -14
- package/src/runtime/client/remote-functions/query/instance.svelte.js +63 -18
- package/src/runtime/client/remote-functions/query/proxy.js +3 -3
- package/src/runtime/client/remote-functions/query-batch.svelte.js +60 -68
- package/src/runtime/client/remote-functions/query-live/instance.svelte.js +60 -18
- package/src/runtime/client/remote-functions/query-live/iterator.js +36 -55
- package/src/runtime/client/remote-functions/shared.svelte.js +88 -62
- package/src/runtime/client/sse.js +32 -0
- package/src/runtime/client/state.svelte.js +65 -49
- package/src/runtime/client/stream.js +38 -0
- package/src/runtime/client/types.d.ts +11 -7
- package/src/runtime/client/utils.js +0 -96
- package/src/runtime/components/root.svelte +66 -0
- package/src/runtime/env/dynamic/private.js +7 -0
- package/src/runtime/env/dynamic/public.js +7 -0
- package/src/runtime/env/static/private.js +6 -0
- package/src/runtime/env/static/public.js +6 -0
- package/src/runtime/form-utils.js +100 -22
- package/src/runtime/server/cookie.js +69 -52
- package/src/runtime/server/csrf.js +65 -0
- package/src/runtime/server/data/index.js +14 -18
- package/src/runtime/server/env_module.js +0 -5
- package/src/runtime/server/index.js +2 -2
- package/src/runtime/server/page/actions.js +41 -26
- package/src/runtime/server/page/index.js +9 -15
- package/src/runtime/server/page/load_data.js +1 -1
- package/src/runtime/server/page/render.js +112 -187
- package/src/runtime/server/page/respond_with_error.js +7 -8
- package/src/runtime/server/page/server_routing.js +27 -18
- package/src/runtime/server/remote.js +355 -216
- package/src/runtime/server/respond.js +97 -61
- package/src/runtime/server/utils.js +32 -9
- package/src/runtime/shared.js +83 -13
- package/src/runtime/telemetry/otel.js +1 -1
- package/src/runtime/types.d.ts +8 -0
- package/src/types/ambient.d.ts +10 -5
- package/src/types/global-private.d.ts +15 -22
- package/src/types/internal.d.ts +89 -74
- package/src/types/private.d.ts +33 -1
- package/src/utils/error.js +24 -4
- package/src/utils/import.js +6 -1
- package/src/utils/imports.js +83 -0
- package/src/utils/mime.js +9 -0
- package/src/utils/params.js +66 -0
- package/src/utils/routing.js +90 -44
- package/src/utils/shared-iterator.js +5 -0
- package/src/utils/streaming.js +14 -4
- package/src/utils/url.js +20 -2
- package/src/version.js +1 -1
- package/types/index.d.ts +467 -537
- package/types/index.d.ts.map +22 -39
- package/src/core/sync/write_root.js +0 -148
- package/src/exports/internal/server.js +0 -22
- package/src/types/synthetic/$lib.md +0 -5
- /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
/** @import { ServerNodesResponse, ServerRedirectNode } from 'types' */
|
|
1
|
+
/** @import { RemoteFunctionDataNode, ServerNodesResponse, ServerRedirectNode } from 'types' */
|
|
2
|
+
/** @import { NavigationIntent } from './types.js' */
|
|
3
|
+
/** @import { RenderNode } from '../types.js' */
|
|
2
4
|
/** @import { CacheEntry } from './remote-functions/cache.svelte.js' */
|
|
3
5
|
/** @import { Query } from './remote-functions/query/instance.svelte.js' */
|
|
4
6
|
/** @import { LiveQuery } from './remote-functions/query-live/instance.svelte.js' */
|
|
5
7
|
import { BROWSER, DEV } from 'esm-env';
|
|
6
8
|
import * as svelte from 'svelte';
|
|
7
9
|
import { HttpError, Redirect, SvelteKitError } from '@sveltejs/kit/internal';
|
|
8
|
-
|
|
9
|
-
// Svelte 4 and under don't have `untrack`, so we have to fallback if `untrack` is not exported
|
|
10
|
-
const untrack = svelte.untrack ?? ((value) => value());
|
|
11
|
-
import {
|
|
12
|
-
decode_params,
|
|
13
|
-
decode_pathname,
|
|
14
|
-
strip_hash,
|
|
15
|
-
make_trackable,
|
|
16
|
-
normalize_path
|
|
17
|
-
} from '../../utils/url.js';
|
|
10
|
+
import { decode_pathname, strip_hash, make_trackable, normalize_path } from '../../utils/url.js';
|
|
18
11
|
import { dev_fetch, initial_fetch, lock_fetch, subsequent_fetch, unlock_fetch } from './fetcher.js';
|
|
19
12
|
import { parse, parse_server_route } from './parse.js';
|
|
20
13
|
import * as storage from './session-storage.js';
|
|
@@ -26,11 +19,9 @@ import {
|
|
|
26
19
|
is_external_url,
|
|
27
20
|
origin,
|
|
28
21
|
scroll_state,
|
|
29
|
-
notifiable_store,
|
|
30
|
-
create_updated_store,
|
|
31
22
|
load_css
|
|
32
23
|
} from './utils.js';
|
|
33
|
-
import { base } from '$app/paths';
|
|
24
|
+
import { base } from '$app/paths/internal/client';
|
|
34
25
|
import * as devalue from 'devalue';
|
|
35
26
|
import {
|
|
36
27
|
HISTORY_INDEX,
|
|
@@ -43,7 +34,6 @@ import {
|
|
|
43
34
|
} from './constants.js';
|
|
44
35
|
import { validate_page_exports } from '../../utils/exports.js';
|
|
45
36
|
import { noop } from '../../utils/functions.js';
|
|
46
|
-
import { compact } from '../../utils/array.js';
|
|
47
37
|
import {
|
|
48
38
|
INVALIDATED_PARAM,
|
|
49
39
|
TRAILING_SLASH_PARAM,
|
|
@@ -52,11 +42,15 @@ import {
|
|
|
52
42
|
validate_load_response
|
|
53
43
|
} from '../shared.js';
|
|
54
44
|
import { get_message, get_status } from '../../utils/error.js';
|
|
55
|
-
import {
|
|
56
|
-
import {
|
|
45
|
+
import { page, update, navigating, updated } from './state.svelte.js';
|
|
46
|
+
import { payload } from './payload.js';
|
|
57
47
|
import { add_data_suffix, add_resolution_suffix } from '../pathname.js';
|
|
58
48
|
import { noop_span } from '../telemetry/noop.js';
|
|
59
49
|
import { read_ndjson } from './ndjson.js';
|
|
50
|
+
import RootModern from '../components/root.svelte';
|
|
51
|
+
import { asClassComponent } from 'svelte/legacy';
|
|
52
|
+
|
|
53
|
+
const Root = asClassComponent(RootModern);
|
|
60
54
|
|
|
61
55
|
export { load_css };
|
|
62
56
|
const ICON_REL_ATTRIBUTES = new Set(['icon', 'shortcut icon', 'apple-touch-icon']);
|
|
@@ -71,6 +65,17 @@ let errored = false;
|
|
|
71
65
|
*/
|
|
72
66
|
let rendering_error = null;
|
|
73
67
|
|
|
68
|
+
/**
|
|
69
|
+
* `reset` functions for `<svelte:boundary>`s in the generated root that have
|
|
70
|
+
* failed. A failed boundary stays failed until `reset()` is called — prop
|
|
71
|
+
* updates alone don't re-render its content — so without resetting, a client
|
|
72
|
+
* navigation away from a render error would leave the stale `+error.svelte`
|
|
73
|
+
* mounted. The boundary's `onerror` populates this array; `navigate` drains it
|
|
74
|
+
* after applying the new props. See sveltejs/kit#15694.
|
|
75
|
+
* @type {Array<(() => void) | undefined>}
|
|
76
|
+
*/
|
|
77
|
+
const resetters = [];
|
|
78
|
+
|
|
74
79
|
// We track the scroll position associated with each history entry in sessionStorage,
|
|
75
80
|
// rather than on history.state itself, because when navigation is driven by
|
|
76
81
|
// popstate it's too late to update the scroll position associated with the
|
|
@@ -87,11 +92,14 @@ const scroll_positions = storage.get(SCROLL_KEY) ?? {};
|
|
|
87
92
|
*/
|
|
88
93
|
const snapshots = storage.get(SNAPSHOT_KEY) ?? {};
|
|
89
94
|
|
|
95
|
+
/**
|
|
96
|
+
* @deprecated this is a temporary measure to avoid a regression, replace with nested `RenderNode` classes
|
|
97
|
+
*/
|
|
98
|
+
let current_tree = /** @type {RenderNode} */ ({});
|
|
99
|
+
|
|
90
100
|
if (DEV && BROWSER) {
|
|
91
101
|
let warned = false;
|
|
92
102
|
|
|
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
103
|
const warn = () => {
|
|
96
104
|
if (warned) return;
|
|
97
105
|
|
|
@@ -100,9 +108,13 @@ if (DEV && BROWSER) {
|
|
|
100
108
|
let stack = new Error().stack?.split('\n');
|
|
101
109
|
if (!stack) return;
|
|
102
110
|
if (!stack[0].includes('https:') && !stack[0].includes('http:')) stack = stack.slice(1); // Chrome includes the error message in the stack
|
|
103
|
-
|
|
104
|
-
//
|
|
105
|
-
|
|
111
|
+
|
|
112
|
+
// skip over `warn` and the place where `warn` was called
|
|
113
|
+
const frame = stack[2];
|
|
114
|
+
|
|
115
|
+
// ignore calls that happen inside dependencies, including SvelteKit.
|
|
116
|
+
// `frame` can be falsy if we came from an anonymous function
|
|
117
|
+
if (frame?.includes('node_modules')) return;
|
|
106
118
|
|
|
107
119
|
warned = true;
|
|
108
120
|
|
|
@@ -124,15 +136,6 @@ if (DEV && BROWSER) {
|
|
|
124
136
|
};
|
|
125
137
|
}
|
|
126
138
|
|
|
127
|
-
export const stores = {
|
|
128
|
-
url: /* @__PURE__ */ notifiable_store({}),
|
|
129
|
-
page: /* @__PURE__ */ notifiable_store({}),
|
|
130
|
-
navigating: /* @__PURE__ */ writable(
|
|
131
|
-
/** @type {import('@sveltejs/kit').Navigation | null} */ (null)
|
|
132
|
-
),
|
|
133
|
-
updated: /* @__PURE__ */ create_updated_store()
|
|
134
|
-
};
|
|
135
|
-
|
|
136
139
|
/** @param {number} index */
|
|
137
140
|
function update_scroll_positions(index) {
|
|
138
141
|
scroll_positions[index] = scroll_state();
|
|
@@ -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;
|
|
@@ -202,18 +206,20 @@ let target;
|
|
|
202
206
|
export let app;
|
|
203
207
|
|
|
204
208
|
/**
|
|
205
|
-
* Data that was serialized during SSR for queries/forms/commands
|
|
206
|
-
*
|
|
207
|
-
*
|
|
209
|
+
* Data that was serialized during SSR for queries/forms/commands, stored as
|
|
210
|
+
* `{ v }` (value) or `{ e }` (error) nodes so that failed states survive hydration.
|
|
211
|
+
* Entries are deleted as they are consumed (when the corresponding resource is created).
|
|
212
|
+
* @type {Record<string, RemoteFunctionDataNode>}
|
|
208
213
|
*/
|
|
209
|
-
export
|
|
214
|
+
export const query_responses = {};
|
|
210
215
|
|
|
211
216
|
/**
|
|
212
|
-
* Data that was serialized during SSR for prerender functions
|
|
217
|
+
* Data that was serialized during SSR for prerender functions, stored as
|
|
218
|
+
* `{ v }` (value) or `{ e }` (error) nodes.
|
|
213
219
|
* This persists across client-side navigations.
|
|
214
|
-
* @type {Record<string,
|
|
220
|
+
* @type {Record<string, RemoteFunctionDataNode>}
|
|
215
221
|
*/
|
|
216
|
-
export
|
|
222
|
+
export const prerender_responses = {};
|
|
217
223
|
|
|
218
224
|
/** @type {Array<((url: URL) => boolean)>} */
|
|
219
225
|
const invalidated = [];
|
|
@@ -290,8 +296,17 @@ let current_history_index;
|
|
|
290
296
|
/** @type {number} */
|
|
291
297
|
let current_navigation_index;
|
|
292
298
|
|
|
293
|
-
/** @type {{}} */
|
|
294
|
-
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;
|
|
295
310
|
|
|
296
311
|
/**
|
|
297
312
|
* A set of tokens which are associated to current preloads.
|
|
@@ -299,6 +314,7 @@ let token;
|
|
|
299
314
|
* If a preload token is in the set and the preload errors, the error
|
|
300
315
|
* handling logic (for example reloading) is skipped.
|
|
301
316
|
*/
|
|
317
|
+
/** @type {Set<{}>} */
|
|
302
318
|
const preload_tokens = new Set();
|
|
303
319
|
|
|
304
320
|
/** @type {Promise<void> | null} */
|
|
@@ -328,9 +344,15 @@ export async function start(_app, _target, hydrate) {
|
|
|
328
344
|
);
|
|
329
345
|
}
|
|
330
346
|
|
|
331
|
-
if (
|
|
332
|
-
|
|
333
|
-
|
|
347
|
+
if (payload.data) {
|
|
348
|
+
const { q = {}, p = {}, l = {}, f = {} } = payload.data;
|
|
349
|
+
|
|
350
|
+
// store the whole nodes — error records seed the corresponding
|
|
351
|
+
// resources in a failed state when they are created during hydration
|
|
352
|
+
for (const k in q) query_responses[k] = q[k];
|
|
353
|
+
for (const k in l) query_responses[k] = l[k];
|
|
354
|
+
for (const k in f) query_responses[k] = f[k];
|
|
355
|
+
for (const k in p) prerender_responses[k] = p[k];
|
|
334
356
|
}
|
|
335
357
|
|
|
336
358
|
// detect basic auth credentials in the current URL
|
|
@@ -402,15 +424,17 @@ export async function start(_app, _target, hydrate) {
|
|
|
402
424
|
_start_router();
|
|
403
425
|
}
|
|
404
426
|
|
|
405
|
-
async function _invalidate(
|
|
427
|
+
async function _invalidate(reset_page_state = true) {
|
|
406
428
|
// Accept all invalidations as they come, don't swallow any while another invalidation
|
|
407
429
|
// is running because subsequent invalidations may make earlier ones outdated,
|
|
408
430
|
// but batch multiple synchronous invalidations.
|
|
409
431
|
await (pending_invalidate ||= Promise.resolve());
|
|
410
|
-
if (
|
|
432
|
+
if (pending_invalidate === null) return;
|
|
411
433
|
pending_invalidate = null;
|
|
412
434
|
|
|
413
|
-
const
|
|
435
|
+
const token = (invalidation_token = {});
|
|
436
|
+
const nav_token = navigation_token;
|
|
437
|
+
const navigating = is_navigating;
|
|
414
438
|
const intent = await get_navigation_intent(current.url, true);
|
|
415
439
|
|
|
416
440
|
// Clear preload, it might be affected by the invalidation.
|
|
@@ -439,32 +463,37 @@ async function _invalidate(include_load_functions = true, reset_page_state = tru
|
|
|
439
463
|
}
|
|
440
464
|
}
|
|
441
465
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
466
|
+
const prev_state = page.state;
|
|
467
|
+
const navigation_result = intent && (await load_route(intent));
|
|
468
|
+
if (!navigation_result || token !== invalidation_token || nav_token !== navigation_token) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
446
471
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
472
|
+
if (navigation_result.type === 'redirect') {
|
|
473
|
+
return _goto(
|
|
474
|
+
new URL(navigation_result.location, current.url).href,
|
|
475
|
+
{ replaceState: true },
|
|
476
|
+
1,
|
|
477
|
+
token
|
|
478
|
+
);
|
|
479
|
+
}
|
|
455
480
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
update(navigation_result.props.page);
|
|
461
|
-
current = { ...navigation_result.state, nav: current.nav };
|
|
462
|
-
reset_invalidation();
|
|
463
|
-
root.$set(navigation_result.props);
|
|
464
|
-
} else {
|
|
465
|
-
reset_invalidation();
|
|
481
|
+
// A navigation started before the invalidation and ended before it finished. The invalidation did not redirect,
|
|
482
|
+
// hence it likely contains outdated data now, so we ignore it.
|
|
483
|
+
if (navigating && !is_navigating) {
|
|
484
|
+
return;
|
|
466
485
|
}
|
|
467
486
|
|
|
487
|
+
// Preserve `page.state` when invalidating without resetting it (e.g. `refresh`/`refreshAll`)
|
|
488
|
+
if (!reset_page_state) {
|
|
489
|
+
navigation_result.props.page.state = prev_state;
|
|
490
|
+
}
|
|
491
|
+
update(navigation_result.props.page);
|
|
492
|
+
current_tree = navigation_result.props.tree;
|
|
493
|
+
current = { ...navigation_result.state, nav: current.nav };
|
|
494
|
+
reset_invalidation();
|
|
495
|
+
root.$set(navigation_result.props);
|
|
496
|
+
|
|
468
497
|
// only wait for promises that are connected to queries that still exist
|
|
469
498
|
/** @type {Promise<any>[]} */
|
|
470
499
|
const promises = [];
|
|
@@ -516,19 +545,21 @@ function persist_state() {
|
|
|
516
545
|
|
|
517
546
|
/**
|
|
518
547
|
* @param {string | URL} url
|
|
519
|
-
* @param {{ replaceState?: boolean; noScroll?: boolean; keepFocus?: boolean;
|
|
548
|
+
* @param {{ replaceState?: boolean; noScroll?: boolean; keepFocus?: boolean; refreshAll?: boolean; invalidate?: Array<string | URL | ((url: URL) => boolean)>; state?: Record<string, any> }} options
|
|
520
549
|
* @param {number} redirect_count
|
|
521
550
|
* @param {{}} [nav_token]
|
|
551
|
+
* @param {NavigationIntent | undefined} [intent] navigation intent, when already known by the caller (avoids recomputing it)
|
|
552
|
+
* @returns {Promise<void>}
|
|
522
553
|
*/
|
|
523
|
-
export async function _goto(url, options, redirect_count, nav_token) {
|
|
554
|
+
export async function _goto(url, options, redirect_count, nav_token, intent) {
|
|
524
555
|
/** @type {Set<string>} */
|
|
525
556
|
let query_keys;
|
|
526
557
|
/** @type {Set<string>} */
|
|
527
558
|
let live_query_keys;
|
|
528
559
|
|
|
529
|
-
// Clear preload cache when
|
|
560
|
+
// Clear preload cache when refreshAll is true to ensure fresh data
|
|
530
561
|
// after form submissions or explicit invalidations
|
|
531
|
-
if (options.
|
|
562
|
+
if (options.refreshAll) {
|
|
532
563
|
discard_load_cache();
|
|
533
564
|
}
|
|
534
565
|
|
|
@@ -541,12 +572,17 @@ export async function _goto(url, options, redirect_count, nav_token) {
|
|
|
541
572
|
state: options.state,
|
|
542
573
|
redirect_count,
|
|
543
574
|
nav_token,
|
|
575
|
+
intent,
|
|
544
576
|
accept: () => {
|
|
545
|
-
if (options.
|
|
577
|
+
if (options.refreshAll) {
|
|
546
578
|
force_invalidation = true;
|
|
547
579
|
query_keys = new Set();
|
|
548
580
|
for (const [id, entries] of query_map) {
|
|
549
|
-
for (const payload of entries
|
|
581
|
+
for (const [payload, entry] of entries) {
|
|
582
|
+
// don't refresh yet, as some queries will be unrendered,
|
|
583
|
+
// but clear caches so that newly rendered queries
|
|
584
|
+
// don't use stale data. TODO same for `live_query_map`
|
|
585
|
+
entry.resource?.reset();
|
|
550
586
|
query_keys.add(create_remote_key(id, payload));
|
|
551
587
|
}
|
|
552
588
|
}
|
|
@@ -564,7 +600,7 @@ export async function _goto(url, options, redirect_count, nav_token) {
|
|
|
564
600
|
}
|
|
565
601
|
});
|
|
566
602
|
|
|
567
|
-
if (options.
|
|
603
|
+
if (options.refreshAll) {
|
|
568
604
|
// TODO the ticks shouldn't be necessary, something inside Svelte itself is buggy
|
|
569
605
|
// when a query in a layout that still exists after page change is refreshed earlier than this
|
|
570
606
|
void svelte
|
|
@@ -574,7 +610,7 @@ export async function _goto(url, options, redirect_count, nav_token) {
|
|
|
574
610
|
for (const [id, entries] of query_map) {
|
|
575
611
|
for (const [payload, { resource }] of entries) {
|
|
576
612
|
if (query_keys?.has(create_remote_key(id, payload))) {
|
|
577
|
-
void resource.
|
|
613
|
+
void resource.start();
|
|
578
614
|
}
|
|
579
615
|
}
|
|
580
616
|
}
|
|
@@ -603,17 +639,14 @@ async function _preload_data(intent) {
|
|
|
603
639
|
load_cache = {
|
|
604
640
|
id: intent.id,
|
|
605
641
|
token: preload,
|
|
606
|
-
promise: load_route({ ...intent, preload }).
|
|
642
|
+
promise: load_route({ ...intent, preload }).finally(() => {
|
|
607
643
|
preload_tokens.delete(preload);
|
|
608
|
-
if (result.type === 'loaded' && result.state.error) {
|
|
609
|
-
// Don't cache errors, because they might be transient
|
|
610
|
-
discard_load_cache();
|
|
611
|
-
}
|
|
612
|
-
return result;
|
|
613
644
|
}),
|
|
614
645
|
fork: null
|
|
615
646
|
};
|
|
616
647
|
|
|
648
|
+
load_cache.promise.catch(discard_load_cache);
|
|
649
|
+
|
|
617
650
|
if (__SVELTEKIT_FORK_PRELOADS__ && svelte.fork) {
|
|
618
651
|
const lc = load_cache;
|
|
619
652
|
|
|
@@ -625,6 +658,7 @@ async function _preload_data(intent) {
|
|
|
625
658
|
return svelte.fork(() => {
|
|
626
659
|
root.$set(result.props);
|
|
627
660
|
update(result.props.page);
|
|
661
|
+
current_tree = result.props.tree;
|
|
628
662
|
});
|
|
629
663
|
} catch {
|
|
630
664
|
// if it errors, it's because the experimental flag isn't enabled in Svelte
|
|
@@ -683,31 +717,28 @@ async function initialize(result, target, hydrate) {
|
|
|
683
717
|
}
|
|
684
718
|
|
|
685
719
|
update(/** @type {import('@sveltejs/kit').Page} */ (result.props.page));
|
|
720
|
+
current_tree = result.props.tree;
|
|
686
721
|
|
|
687
|
-
|
|
722
|
+
// TODO: use mount()
|
|
723
|
+
root = new Root({
|
|
688
724
|
target,
|
|
689
|
-
props: { ...result.props,
|
|
725
|
+
props: { ...result.props, components, resetters },
|
|
690
726
|
hydrate,
|
|
691
|
-
//
|
|
727
|
+
// Svelte 5 specific: asynchronously instantiate the component, i.e. don't call flushSync
|
|
692
728
|
sync: false,
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
return error;
|
|
701
|
-
}
|
|
702
|
-
: undefined
|
|
729
|
+
transformError: /** @param {unknown} e */ async (e) => {
|
|
730
|
+
const error = await handle_error(e, current.nav);
|
|
731
|
+
rendering_error = { error, status: error.status };
|
|
732
|
+
page.error = error;
|
|
733
|
+
page.status = rendering_error.status;
|
|
734
|
+
return error;
|
|
735
|
+
}
|
|
703
736
|
});
|
|
704
737
|
|
|
705
738
|
// Wait for a microtask in case svelte experimental async is enabled,
|
|
706
739
|
// which causes component script blocks to run asynchronously
|
|
707
740
|
void (await Promise.resolve());
|
|
708
741
|
|
|
709
|
-
restore_snapshot(current_navigation_index);
|
|
710
|
-
|
|
711
742
|
if (hydrate) {
|
|
712
743
|
/** @type {import('@sveltejs/kit').AfterNavigate} */
|
|
713
744
|
const navigation = {
|
|
@@ -724,6 +755,8 @@ async function initialize(result, target, hydrate) {
|
|
|
724
755
|
after_navigate_callbacks.forEach((fn) => fn(navigation));
|
|
725
756
|
}
|
|
726
757
|
|
|
758
|
+
restore_snapshot(current_navigation_index);
|
|
759
|
+
|
|
727
760
|
started = true;
|
|
728
761
|
}
|
|
729
762
|
|
|
@@ -734,7 +767,7 @@ async function initialize(result, target, hydrate) {
|
|
|
734
767
|
* params: Record<string, string>;
|
|
735
768
|
* branch: Array<import('./types.js').BranchNode | undefined>;
|
|
736
769
|
* errors?: Array<import('types').CSRPageNodeLoader | undefined>;
|
|
737
|
-
* status
|
|
770
|
+
* status?: number;
|
|
738
771
|
* error: App.Error | null;
|
|
739
772
|
* route: import('types').CSRRoute | null;
|
|
740
773
|
* form?: Record<string, any> | null;
|
|
@@ -778,35 +811,12 @@ async function get_navigation_result_from_branch({
|
|
|
778
811
|
route
|
|
779
812
|
},
|
|
780
813
|
props: {
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
page: clone_page(page)
|
|
814
|
+
page,
|
|
815
|
+
tree: /** @type {RenderNode} */ ({})
|
|
784
816
|
}
|
|
785
817
|
};
|
|
786
818
|
|
|
787
|
-
if (
|
|
788
|
-
let last_idx = -1;
|
|
789
|
-
result.props.errors = await Promise.all(
|
|
790
|
-
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
791
|
-
branch
|
|
792
|
-
.map((b, i) => {
|
|
793
|
-
if (i === 0) return undefined; // root layout wraps root error component, not the other way around
|
|
794
|
-
if (!b) return null;
|
|
795
|
-
|
|
796
|
-
i--;
|
|
797
|
-
// Find the closest error component up to the previous branch
|
|
798
|
-
while (i > last_idx + 1 && !errors[i]) i -= 1;
|
|
799
|
-
last_idx = i;
|
|
800
|
-
return errors[i]?.()
|
|
801
|
-
.then((e) => e.component)
|
|
802
|
-
.catch(() => undefined);
|
|
803
|
-
})
|
|
804
|
-
// filter out indexes where there was no branch, but keep indexes where there was a branch but no error component
|
|
805
|
-
.filter((e) => e !== null)
|
|
806
|
-
);
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
if (error && __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__) {
|
|
819
|
+
if (error) {
|
|
810
820
|
result.props.error = error;
|
|
811
821
|
}
|
|
812
822
|
|
|
@@ -817,23 +827,44 @@ async function get_navigation_result_from_branch({
|
|
|
817
827
|
let data = {};
|
|
818
828
|
let data_changed = !page;
|
|
819
829
|
|
|
820
|
-
let
|
|
830
|
+
let current_node = result.props.tree;
|
|
831
|
+
|
|
832
|
+
/** @type {RenderNode | undefined} */
|
|
833
|
+
let previous_node = current_tree;
|
|
821
834
|
|
|
822
|
-
for (let i = 0; i <
|
|
835
|
+
for (let i = 0; i < branch.length; i += 1) {
|
|
823
836
|
const node = branch[i];
|
|
824
837
|
const prev = current.branch[i];
|
|
825
838
|
|
|
826
|
-
if (node?.data !== prev?.data) data_changed = true;
|
|
827
839
|
if (!node) continue;
|
|
828
840
|
|
|
829
|
-
|
|
841
|
+
const error_loader = errors?.slice(0, i + 1).findLast((x) => x) ?? default_error_loader;
|
|
830
842
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
843
|
+
current_node.error = (await error_loader()).component;
|
|
844
|
+
current_node.component = node.node.component;
|
|
845
|
+
|
|
846
|
+
if (
|
|
847
|
+
// if an ancestor node in this path changed, `data_changed` is already true and the
|
|
848
|
+
// accumulated `data` differs from the previous render, so we must re-merge
|
|
849
|
+
data_changed ||
|
|
850
|
+
!previous_node ||
|
|
851
|
+
node?.data !== prev?.data
|
|
852
|
+
) {
|
|
853
|
+
current_node.data = { ...data, ...node.data };
|
|
854
|
+
data_changed = true;
|
|
855
|
+
} else {
|
|
856
|
+
// use existing object — prevents effects re-running unnecessarily
|
|
857
|
+
current_node.data = previous_node.data;
|
|
834
858
|
}
|
|
835
859
|
|
|
836
|
-
|
|
860
|
+
data = current_node.data;
|
|
861
|
+
|
|
862
|
+
if (i < branch.length - 1) {
|
|
863
|
+
current_node.child = /** @type {import('../types.js').RenderNode} */ ({});
|
|
864
|
+
current_node = current_node.child;
|
|
865
|
+
|
|
866
|
+
previous_node = previous_node?.child;
|
|
867
|
+
}
|
|
837
868
|
}
|
|
838
869
|
|
|
839
870
|
const page_changed =
|
|
@@ -851,11 +882,10 @@ async function get_navigation_result_from_branch({
|
|
|
851
882
|
id: route?.id ?? null
|
|
852
883
|
},
|
|
853
884
|
state: {},
|
|
854
|
-
status,
|
|
885
|
+
status: status ?? error?.status ?? 200,
|
|
855
886
|
url: new URL(url),
|
|
856
887
|
form: form ?? null,
|
|
857
|
-
|
|
858
|
-
data: data_changed ? data : page.data
|
|
888
|
+
data
|
|
859
889
|
};
|
|
860
890
|
}
|
|
861
891
|
|
|
@@ -1134,30 +1164,19 @@ function diff_search_params(old_url, new_url) {
|
|
|
1134
1164
|
}
|
|
1135
1165
|
|
|
1136
1166
|
/**
|
|
1137
|
-
* @
|
|
1138
|
-
* @
|
|
1167
|
+
* @overload
|
|
1168
|
+
* @param {import('./types.js').NavigationIntent} intent
|
|
1169
|
+
* @returns {Promise<import('./types.js').NavigationResult | undefined>}
|
|
1139
1170
|
*/
|
|
1140
|
-
function preload_error({ error, url, route, params }) {
|
|
1141
|
-
return {
|
|
1142
|
-
type: 'loaded',
|
|
1143
|
-
state: {
|
|
1144
|
-
error,
|
|
1145
|
-
url,
|
|
1146
|
-
route,
|
|
1147
|
-
params,
|
|
1148
|
-
branch: []
|
|
1149
|
-
},
|
|
1150
|
-
props: {
|
|
1151
|
-
page: clone_page(page),
|
|
1152
|
-
constructors: []
|
|
1153
|
-
}
|
|
1154
|
-
};
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
1171
|
/**
|
|
1158
|
-
* @
|
|
1172
|
+
* @overload
|
|
1173
|
+
* @param {import('./types.js').NavigationIntent & { preload: {} }} intent
|
|
1159
1174
|
* @returns {Promise<import('./types.js').NavigationResult>}
|
|
1160
1175
|
*/
|
|
1176
|
+
/**
|
|
1177
|
+
* @param {import('./types.js').NavigationIntent & { preload?: {} }} intent
|
|
1178
|
+
* @returns {Promise<import('./types.js').NavigationResult | undefined>}
|
|
1179
|
+
*/
|
|
1161
1180
|
async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
1162
1181
|
if (load_cache?.id === id) {
|
|
1163
1182
|
// the preload becomes the real navigation
|
|
@@ -1213,12 +1232,11 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1213
1232
|
} catch (error) {
|
|
1214
1233
|
const handled_error = await handle_error(error, { url, params, route: { id } });
|
|
1215
1234
|
|
|
1216
|
-
if (preload_tokens.has(preload)) {
|
|
1217
|
-
|
|
1235
|
+
if (preload && preload_tokens.has(preload)) {
|
|
1236
|
+
throw handled_error;
|
|
1218
1237
|
}
|
|
1219
1238
|
|
|
1220
1239
|
return load_root_error_page({
|
|
1221
|
-
status: get_status(error),
|
|
1222
1240
|
error: handled_error,
|
|
1223
1241
|
url,
|
|
1224
1242
|
route
|
|
@@ -1261,7 +1279,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1261
1279
|
|
|
1262
1280
|
if (server_data_node?.type === 'error') {
|
|
1263
1281
|
// rethrow and catch below
|
|
1264
|
-
throw server_data_node;
|
|
1282
|
+
throw new HttpError(server_data_node.error.status, server_data_node.error);
|
|
1265
1283
|
}
|
|
1266
1284
|
|
|
1267
1285
|
return load_node({
|
|
@@ -1299,34 +1317,27 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1299
1317
|
if (err instanceof Redirect) {
|
|
1300
1318
|
return {
|
|
1301
1319
|
type: 'redirect',
|
|
1320
|
+
status: err.status,
|
|
1302
1321
|
location: err.location
|
|
1303
1322
|
};
|
|
1304
1323
|
}
|
|
1305
1324
|
|
|
1306
|
-
if (preload_tokens.has(preload)) {
|
|
1307
|
-
|
|
1308
|
-
error: await handle_error(err, { params, url, route: { id: route.id } }),
|
|
1309
|
-
url,
|
|
1310
|
-
params,
|
|
1311
|
-
route
|
|
1312
|
-
});
|
|
1325
|
+
if (preload && preload_tokens.has(preload)) {
|
|
1326
|
+
throw await handle_error(err, { params, url, route: { id: route.id } });
|
|
1313
1327
|
}
|
|
1314
1328
|
|
|
1315
|
-
let status = get_status(err);
|
|
1316
1329
|
/** @type {App.Error} */
|
|
1317
1330
|
let error;
|
|
1318
1331
|
|
|
1319
1332
|
if (server_data_nodes?.includes(/** @type {import('types').ServerErrorNode} */ (err))) {
|
|
1320
1333
|
// this is the server error rethrown above, reconstruct but don't invoke
|
|
1321
1334
|
// the client error handler; it should've already been handled on the server
|
|
1322
|
-
status = /** @type {import('types').ServerErrorNode} */ (err).status ?? status;
|
|
1323
1335
|
error = /** @type {import('types').ServerErrorNode} */ (err).error;
|
|
1324
1336
|
} else if (err instanceof HttpError) {
|
|
1325
1337
|
error = err.body;
|
|
1326
1338
|
} else {
|
|
1327
1339
|
// Referenced node could have been removed due to redeploy, check
|
|
1328
|
-
|
|
1329
|
-
if (updated) {
|
|
1340
|
+
if (await updated.check()) {
|
|
1330
1341
|
// Before reloading, try to update the service worker if it exists
|
|
1331
1342
|
await update_service_worker();
|
|
1332
1343
|
return await native_navigation(url);
|
|
@@ -1342,12 +1353,11 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1342
1353
|
params,
|
|
1343
1354
|
branch: branch.slice(0, error_load.idx).concat(error_load.node),
|
|
1344
1355
|
errors,
|
|
1345
|
-
status,
|
|
1346
1356
|
error,
|
|
1347
1357
|
route
|
|
1348
1358
|
});
|
|
1349
1359
|
} else {
|
|
1350
|
-
return await server_fallback(url, { id: route.id }, error
|
|
1360
|
+
return await server_fallback(url, { id: route.id }, error);
|
|
1351
1361
|
}
|
|
1352
1362
|
}
|
|
1353
1363
|
} else {
|
|
@@ -1362,7 +1372,6 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1362
1372
|
params,
|
|
1363
1373
|
branch,
|
|
1364
1374
|
errors,
|
|
1365
|
-
status: 200,
|
|
1366
1375
|
error: null,
|
|
1367
1376
|
route,
|
|
1368
1377
|
// Reset `form` on navigation, but not invalidation
|
|
@@ -1401,14 +1410,13 @@ async function load_nearest_error_page(i, branch, errors) {
|
|
|
1401
1410
|
|
|
1402
1411
|
/**
|
|
1403
1412
|
* @param {{
|
|
1404
|
-
* status: number;
|
|
1405
1413
|
* error: App.Error;
|
|
1406
1414
|
* url: URL;
|
|
1407
1415
|
* route: { id: string | null }
|
|
1408
1416
|
* }} opts
|
|
1409
|
-
* @returns {Promise<import('./types.js').NavigationFinished>}
|
|
1417
|
+
* @returns {Promise<import('./types.js').NavigationFinished | undefined>} returns `undefined` in case of a redirect
|
|
1410
1418
|
*/
|
|
1411
|
-
async function load_root_error_page({
|
|
1419
|
+
async function load_root_error_page({ error, url, route }) {
|
|
1412
1420
|
/** @type {Record<string, string>} */
|
|
1413
1421
|
const params = {}; // error page does not have params
|
|
1414
1422
|
|
|
@@ -1432,11 +1440,15 @@ async function load_root_error_page({ status, error, url, route }) {
|
|
|
1432
1440
|
}
|
|
1433
1441
|
|
|
1434
1442
|
server_data_node = server_data.nodes[0] ?? null;
|
|
1435
|
-
} catch {
|
|
1443
|
+
} catch (e) {
|
|
1436
1444
|
// at this point we have no choice but to fall back to the server, if it wouldn't
|
|
1437
|
-
// bring us right back here, turning this into an endless loop
|
|
1438
|
-
if
|
|
1439
|
-
|
|
1445
|
+
// bring us right back here, turning this into an endless loop.
|
|
1446
|
+
// if __data.json returned 404, the route doesn't exist — don't reload or we loop
|
|
1447
|
+
if (
|
|
1448
|
+
!(e instanceof HttpError && e.status === 404) &&
|
|
1449
|
+
(url.origin !== origin || url.pathname !== location.pathname || hydrated)
|
|
1450
|
+
) {
|
|
1451
|
+
return await native_navigation(url);
|
|
1440
1452
|
}
|
|
1441
1453
|
}
|
|
1442
1454
|
}
|
|
@@ -1465,18 +1477,30 @@ async function load_root_error_page({ status, error, url, route }) {
|
|
|
1465
1477
|
url,
|
|
1466
1478
|
params,
|
|
1467
1479
|
branch: [root_layout, root_error],
|
|
1468
|
-
status,
|
|
1469
1480
|
error,
|
|
1470
1481
|
errors: [],
|
|
1471
1482
|
route: null
|
|
1472
1483
|
});
|
|
1473
1484
|
} catch (error) {
|
|
1485
|
+
// client-side navigation if the root layout loader throws a redirect while
|
|
1486
|
+
// rendering the default error page
|
|
1474
1487
|
if (error instanceof Redirect) {
|
|
1475
|
-
|
|
1476
|
-
return
|
|
1488
|
+
await _goto(new URL(error.location, location.href), {}, 0);
|
|
1489
|
+
return;
|
|
1477
1490
|
}
|
|
1478
1491
|
|
|
1479
|
-
//
|
|
1492
|
+
// otherwise, render the static error page
|
|
1493
|
+
const error_template = await app.get_error_template();
|
|
1494
|
+
const handled = await handle_error(error, { url, params, route });
|
|
1495
|
+
const message = String(handled?.message ?? '')
|
|
1496
|
+
.replace(/&/g, '&')
|
|
1497
|
+
.replace(/</g, '<')
|
|
1498
|
+
.replace(/>/g, '>');
|
|
1499
|
+
const html = error_template({ status: handled.status, message });
|
|
1500
|
+
const parsed = new DOMParser().parseFromString(html, 'text/html');
|
|
1501
|
+
document.documentElement.replaceChild(document.adoptNode(parsed.head), document.head);
|
|
1502
|
+
document.documentElement.replaceChild(document.adoptNode(parsed.body), document.body);
|
|
1503
|
+
|
|
1480
1504
|
throw error;
|
|
1481
1505
|
}
|
|
1482
1506
|
}
|
|
@@ -1566,7 +1590,7 @@ export async function get_navigation_intent(url, invalidating) {
|
|
|
1566
1590
|
id: get_page_key(url),
|
|
1567
1591
|
invalidating,
|
|
1568
1592
|
route,
|
|
1569
|
-
params
|
|
1593
|
+
params,
|
|
1570
1594
|
url
|
|
1571
1595
|
};
|
|
1572
1596
|
}
|
|
@@ -1661,8 +1685,10 @@ function _before_navigate({ url, type, intent, delta, event, scroll }) {
|
|
|
1661
1685
|
* nav_token?: {};
|
|
1662
1686
|
* accept?: () => void;
|
|
1663
1687
|
* block?: () => void;
|
|
1664
|
-
* event?: Event
|
|
1688
|
+
* event?: Event;
|
|
1689
|
+
* intent?: NavigationIntent | undefined
|
|
1665
1690
|
* }} opts
|
|
1691
|
+
* @returns {Promise<void>}
|
|
1666
1692
|
*/
|
|
1667
1693
|
async function navigate({
|
|
1668
1694
|
type,
|
|
@@ -1676,12 +1702,14 @@ async function navigate({
|
|
|
1676
1702
|
nav_token = {},
|
|
1677
1703
|
accept = noop,
|
|
1678
1704
|
block = noop,
|
|
1679
|
-
event
|
|
1705
|
+
event,
|
|
1706
|
+
intent
|
|
1680
1707
|
}) {
|
|
1681
|
-
const prev_token =
|
|
1682
|
-
|
|
1708
|
+
const prev_token = navigation_token;
|
|
1709
|
+
const prev_invalidation_token = invalidation_token;
|
|
1710
|
+
navigation_token = invalidation_token = nav_token;
|
|
1683
1711
|
|
|
1684
|
-
|
|
1712
|
+
intent ??= await get_navigation_intent(url, false);
|
|
1685
1713
|
const nav =
|
|
1686
1714
|
type === 'enter'
|
|
1687
1715
|
? create_navigation(current, intent, url, type)
|
|
@@ -1697,7 +1725,8 @@ async function navigate({
|
|
|
1697
1725
|
|
|
1698
1726
|
if (!nav) {
|
|
1699
1727
|
block();
|
|
1700
|
-
if (
|
|
1728
|
+
if (navigation_token === nav_token) navigation_token = prev_token;
|
|
1729
|
+
if (invalidation_token === nav_token) invalidation_token = prev_invalidation_token;
|
|
1701
1730
|
return;
|
|
1702
1731
|
}
|
|
1703
1732
|
|
|
@@ -1710,7 +1739,7 @@ async function navigate({
|
|
|
1710
1739
|
is_navigating = true;
|
|
1711
1740
|
|
|
1712
1741
|
if (started && nav.navigation.type !== 'enter') {
|
|
1713
|
-
|
|
1742
|
+
navigating.current = nav.navigation;
|
|
1714
1743
|
}
|
|
1715
1744
|
|
|
1716
1745
|
let navigation_result = intent && (await load_route(intent));
|
|
@@ -1735,7 +1764,6 @@ async function navigate({
|
|
|
1735
1764
|
route: { id: null }
|
|
1736
1765
|
}
|
|
1737
1766
|
),
|
|
1738
|
-
404,
|
|
1739
1767
|
replace_state
|
|
1740
1768
|
);
|
|
1741
1769
|
} else {
|
|
@@ -1750,7 +1778,6 @@ async function navigate({
|
|
|
1750
1778
|
params: {},
|
|
1751
1779
|
route: { id: null }
|
|
1752
1780
|
}),
|
|
1753
|
-
404,
|
|
1754
1781
|
replace_state
|
|
1755
1782
|
);
|
|
1756
1783
|
}
|
|
@@ -1761,11 +1788,13 @@ async function navigate({
|
|
|
1761
1788
|
url = intent?.url || url;
|
|
1762
1789
|
|
|
1763
1790
|
// abort if user navigated during update
|
|
1764
|
-
if (
|
|
1791
|
+
if (navigation_token !== nav_token) {
|
|
1765
1792
|
nav.reject(new Error('navigation aborted'));
|
|
1766
|
-
return
|
|
1793
|
+
return;
|
|
1767
1794
|
}
|
|
1768
1795
|
|
|
1796
|
+
if (!navigation_result) return;
|
|
1797
|
+
|
|
1769
1798
|
if (navigation_result.type === 'redirect') {
|
|
1770
1799
|
// whatwg fetch spec https://fetch.spec.whatwg.org/#http-redirect-fetch says to error after 20 redirects
|
|
1771
1800
|
if (redirect_count < 20) {
|
|
@@ -1786,7 +1815,6 @@ async function navigate({
|
|
|
1786
1815
|
}
|
|
1787
1816
|
|
|
1788
1817
|
navigation_result = await load_root_error_page({
|
|
1789
|
-
status: 500,
|
|
1790
1818
|
error: await handle_error(new Error('Redirect loop'), {
|
|
1791
1819
|
url,
|
|
1792
1820
|
params: {},
|
|
@@ -1795,12 +1823,13 @@ async function navigate({
|
|
|
1795
1823
|
url,
|
|
1796
1824
|
route: { id: null }
|
|
1797
1825
|
});
|
|
1826
|
+
|
|
1827
|
+
if (!navigation_result) return;
|
|
1798
1828
|
} else if (/** @type {number} */ (navigation_result.props.page.status) >= 400) {
|
|
1799
|
-
|
|
1800
|
-
if (updated) {
|
|
1829
|
+
if (await updated.check()) {
|
|
1801
1830
|
// Before reloading, try to update the service worker if it exists
|
|
1802
1831
|
await update_service_worker();
|
|
1803
|
-
await native_navigation(url, replace_state);
|
|
1832
|
+
return await native_navigation(url, replace_state);
|
|
1804
1833
|
}
|
|
1805
1834
|
}
|
|
1806
1835
|
|
|
@@ -1895,13 +1924,38 @@ async function navigate({
|
|
|
1895
1924
|
navigation_result.props.page.url = url;
|
|
1896
1925
|
}
|
|
1897
1926
|
|
|
1927
|
+
// Remove focus before updating the component tree, so that blur/focusout
|
|
1928
|
+
// handlers fire while the old component's data is still valid (#14575)
|
|
1929
|
+
if (
|
|
1930
|
+
!keepfocus &&
|
|
1931
|
+
document.activeElement instanceof HTMLElement &&
|
|
1932
|
+
document.activeElement !== document.body
|
|
1933
|
+
) {
|
|
1934
|
+
document.activeElement.blur();
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1898
1937
|
const fork = load_cache_fork && (await load_cache_fork);
|
|
1899
1938
|
|
|
1900
1939
|
if (fork) {
|
|
1901
1940
|
commit_promise = fork.commit();
|
|
1941
|
+
// `fork.commit()` applies the preloaded state synchronously before the
|
|
1942
|
+
// first `await`, so reset any previously-failed boundaries now so the
|
|
1943
|
+
// stale `+error.svelte` is torn down. See sveltejs/kit#15694.
|
|
1944
|
+
for (const reset of resetters) {
|
|
1945
|
+
reset?.();
|
|
1946
|
+
}
|
|
1947
|
+
resetters.length = 0;
|
|
1902
1948
|
} else {
|
|
1903
1949
|
rendering_error = null; // TODO this can break with forks, rethink for SvelteKit 3 where we can assume Svelte 5
|
|
1904
1950
|
root.$set(navigation_result.props);
|
|
1951
|
+
current_tree = navigation_result.props.tree;
|
|
1952
|
+
// Reset any boundaries that failed on a previous navigation now that the
|
|
1953
|
+
// new props are applied, otherwise the stale `+error.svelte` stays
|
|
1954
|
+
// mounted above the new route's content. See sveltejs/kit#15694.
|
|
1955
|
+
for (const reset of resetters) {
|
|
1956
|
+
reset?.();
|
|
1957
|
+
}
|
|
1958
|
+
resetters.length = 0;
|
|
1905
1959
|
// Check for sync rendering error
|
|
1906
1960
|
if (rendering_error) {
|
|
1907
1961
|
Object.assign(navigation_result.props.page, rendering_error);
|
|
@@ -1918,18 +1972,12 @@ async function navigate({
|
|
|
1918
1972
|
|
|
1919
1973
|
const { activeElement } = document;
|
|
1920
1974
|
|
|
1921
|
-
await commit_promise;
|
|
1922
|
-
|
|
1923
|
-
// TODO 3.0 remote — the double tick is probably necessary because
|
|
1924
|
-
// of some store shenanigans. `settled()` and `f.commit()`
|
|
1925
|
-
// should resolve after DOM updates in newer versions
|
|
1926
|
-
await svelte.tick();
|
|
1927
|
-
await svelte.tick();
|
|
1975
|
+
await (commit_promise ?? svelte.tick());
|
|
1928
1976
|
|
|
1929
|
-
if (
|
|
1977
|
+
if (navigation_token !== nav_token) {
|
|
1930
1978
|
// a new navigation happened while we were waiting for the DOM to update, so abort
|
|
1931
1979
|
nav.reject(new Error('navigation aborted'));
|
|
1932
|
-
return
|
|
1980
|
+
return;
|
|
1933
1981
|
}
|
|
1934
1982
|
|
|
1935
1983
|
// Check for async rendering error
|
|
@@ -1973,10 +2021,6 @@ async function navigate({
|
|
|
1973
2021
|
|
|
1974
2022
|
is_navigating = false;
|
|
1975
2023
|
|
|
1976
|
-
if (type === 'popstate') {
|
|
1977
|
-
restore_snapshot(current_navigation_index);
|
|
1978
|
-
}
|
|
1979
|
-
|
|
1980
2024
|
nav.fulfil(undefined);
|
|
1981
2025
|
|
|
1982
2026
|
// Update to.scroll to the actual scroll position after navigation completed
|
|
@@ -1988,7 +2032,11 @@ async function navigate({
|
|
|
1988
2032
|
fn(/** @type {import('@sveltejs/kit').AfterNavigate} */ (nav.navigation))
|
|
1989
2033
|
);
|
|
1990
2034
|
|
|
1991
|
-
|
|
2035
|
+
if (type === 'popstate') {
|
|
2036
|
+
restore_snapshot(current_navigation_index);
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
navigating.current = null;
|
|
1992
2040
|
|
|
1993
2041
|
updating = false;
|
|
1994
2042
|
}
|
|
@@ -1998,23 +2046,21 @@ async function navigate({
|
|
|
1998
2046
|
* @param {URL} url
|
|
1999
2047
|
* @param {{ id: string | null }} route
|
|
2000
2048
|
* @param {App.Error} error
|
|
2001
|
-
* @param {number} status
|
|
2002
2049
|
* @param {boolean} [replace_state]
|
|
2003
|
-
* @returns {Promise<import('./types.js').NavigationFinished>}
|
|
2050
|
+
* @returns {Promise<import('./types.js').NavigationFinished | undefined>}
|
|
2004
2051
|
*/
|
|
2005
|
-
async function server_fallback(url, route, error,
|
|
2052
|
+
async function server_fallback(url, route, error, replace_state) {
|
|
2006
2053
|
if (url.origin === origin && url.pathname === location.pathname && !hydrated) {
|
|
2007
2054
|
// We would reload the same page we're currently on, which isn't hydrated,
|
|
2008
2055
|
// which means no SSR, which means we would end up in an endless loop
|
|
2009
2056
|
return await load_root_error_page({
|
|
2010
|
-
status,
|
|
2011
2057
|
error,
|
|
2012
2058
|
url,
|
|
2013
2059
|
route
|
|
2014
2060
|
});
|
|
2015
2061
|
}
|
|
2016
2062
|
|
|
2017
|
-
if (DEV && status !== 404) {
|
|
2063
|
+
if (DEV && error.status !== 404) {
|
|
2018
2064
|
console.error(
|
|
2019
2065
|
'An error occurred while loading the page. This will cause a full page reload. (This message will only appear during development.)'
|
|
2020
2066
|
);
|
|
@@ -2043,22 +2089,63 @@ let current_a = { element: undefined, href: undefined };
|
|
|
2043
2089
|
function setup_preload() {
|
|
2044
2090
|
/** @type {NodeJS.Timeout} */
|
|
2045
2091
|
let mousemove_timeout;
|
|
2092
|
+
/** @type {HTMLAnchorElement | SVGAElement | undefined} */
|
|
2093
|
+
let hovered_a;
|
|
2046
2094
|
/** @type {PreloadDataPriority} */
|
|
2047
2095
|
let current_priority;
|
|
2048
2096
|
|
|
2049
|
-
|
|
2050
|
-
|
|
2097
|
+
function clear_hover_preload() {
|
|
2098
|
+
clearTimeout(mousemove_timeout);
|
|
2099
|
+
hovered_a?.removeEventListener('mousemove', start_hover_preload);
|
|
2100
|
+
hovered_a?.removeEventListener('mouseleave', clear_hover_preload);
|
|
2101
|
+
hovered_a = undefined;
|
|
2102
|
+
}
|
|
2051
2103
|
|
|
2104
|
+
function start_hover_preload() {
|
|
2052
2105
|
clearTimeout(mousemove_timeout);
|
|
2053
2106
|
mousemove_timeout = setTimeout(() => {
|
|
2054
|
-
|
|
2107
|
+
if (!hovered_a) return;
|
|
2108
|
+
void preload(hovered_a, PRELOAD_PRIORITIES.hover);
|
|
2055
2109
|
}, 20);
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
// Use mouseover initially instead of mousemove to avoid cluttering the event queue
|
|
2113
|
+
container.addEventListener('mouseover', (event) => {
|
|
2114
|
+
if (!(event.target instanceof Element)) return;
|
|
2115
|
+
|
|
2116
|
+
const a = find_anchor(event.target, container);
|
|
2117
|
+
if (!a || a === hovered_a) return;
|
|
2118
|
+
|
|
2119
|
+
clear_hover_preload();
|
|
2120
|
+
hovered_a = a;
|
|
2121
|
+
|
|
2122
|
+
const options = get_router_options(a);
|
|
2123
|
+
|
|
2124
|
+
if (
|
|
2125
|
+
options.preload_code < PRELOAD_PRIORITIES.hover &&
|
|
2126
|
+
options.preload_data < PRELOAD_PRIORITIES.hover
|
|
2127
|
+
) {
|
|
2128
|
+
// don't add event listeners if no preloading will happen
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
// Instead of just preloading right away, we start a mousemove listener to implement
|
|
2133
|
+
// "mouse comes to a rest" behavior. This avoid false positives when you just move
|
|
2134
|
+
// your mouse across the screen and happen to pass over a link.
|
|
2135
|
+
a.addEventListener('mousemove', start_hover_preload);
|
|
2136
|
+
a.addEventListener('mouseleave', clear_hover_preload, { once: true });
|
|
2137
|
+
|
|
2138
|
+
start_hover_preload();
|
|
2056
2139
|
});
|
|
2057
2140
|
|
|
2058
2141
|
/** @param {Event} event */
|
|
2059
2142
|
function tap(event) {
|
|
2060
2143
|
if (event.defaultPrevented) return;
|
|
2061
|
-
|
|
2144
|
+
|
|
2145
|
+
const a = find_anchor(/** @type {Element} */ (event.composedPath()[0]), container);
|
|
2146
|
+
if (!a) return;
|
|
2147
|
+
|
|
2148
|
+
void preload(a, PRELOAD_PRIORITIES.tap);
|
|
2062
2149
|
}
|
|
2063
2150
|
|
|
2064
2151
|
container.addEventListener('mousedown', tap);
|
|
@@ -2077,12 +2164,10 @@ function setup_preload() {
|
|
|
2077
2164
|
);
|
|
2078
2165
|
|
|
2079
2166
|
/**
|
|
2080
|
-
* @param {
|
|
2167
|
+
* @param {HTMLAnchorElement | SVGAElement} a
|
|
2081
2168
|
* @param {PreloadDataPriority} priority
|
|
2082
2169
|
*/
|
|
2083
|
-
async function preload(
|
|
2084
|
-
const a = find_anchor(element, container);
|
|
2085
|
-
|
|
2170
|
+
async function preload(a, priority) {
|
|
2086
2171
|
// we don't want to preload data again if the user has already hovered/tapped
|
|
2087
2172
|
const interacted =
|
|
2088
2173
|
a === current_a.element && a?.href === current_a.href && priority >= current_priority;
|
|
@@ -2106,15 +2191,13 @@ function setup_preload() {
|
|
|
2106
2191
|
if (!intent) return;
|
|
2107
2192
|
|
|
2108
2193
|
if (DEV) {
|
|
2109
|
-
void _preload_data(intent).
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
);
|
|
2117
|
-
}
|
|
2194
|
+
void _preload_data(intent).catch((error) => {
|
|
2195
|
+
console.warn(
|
|
2196
|
+
`Preloading data for ${intent.url.pathname} failed with the following error: ${error.message}\n` +
|
|
2197
|
+
'If this error is transient, you can ignore it. Otherwise, consider disabling preloading for this route. ' +
|
|
2198
|
+
'This route was preloaded due to a data-sveltekit-preload-data attribute. ' +
|
|
2199
|
+
'See https://svelte.dev/docs/kit/link-options for more info'
|
|
2200
|
+
);
|
|
2118
2201
|
});
|
|
2119
2202
|
} else {
|
|
2120
2203
|
void _preload_data(intent);
|
|
@@ -2153,9 +2236,9 @@ function setup_preload() {
|
|
|
2153
2236
|
/**
|
|
2154
2237
|
* @param {unknown} error
|
|
2155
2238
|
* @param {import('@sveltejs/kit').NavigationEvent} event
|
|
2156
|
-
* @returns {
|
|
2239
|
+
* @returns {Promise<App.Error>}
|
|
2157
2240
|
*/
|
|
2158
|
-
function handle_error(error, event) {
|
|
2241
|
+
export async function handle_error(error, event) {
|
|
2159
2242
|
if (error instanceof HttpError) {
|
|
2160
2243
|
return error.body;
|
|
2161
2244
|
}
|
|
@@ -2167,10 +2250,9 @@ function handle_error(error, event) {
|
|
|
2167
2250
|
|
|
2168
2251
|
const status = get_status(error);
|
|
2169
2252
|
const message = get_message(error);
|
|
2253
|
+
const app_error = (await app.hooks.handleError({ error, event, status, message })) ?? { message };
|
|
2170
2254
|
|
|
2171
|
-
return (
|
|
2172
|
-
app.hooks.handleError({ error, event, status, message }) ?? /** @type {any} */ ({ message })
|
|
2173
|
-
);
|
|
2255
|
+
return { ...app_error, status: get_status(app_error, error) };
|
|
2174
2256
|
}
|
|
2175
2257
|
|
|
2176
2258
|
/**
|
|
@@ -2179,7 +2261,7 @@ function handle_error(error, event) {
|
|
|
2179
2261
|
* @param {T} callback
|
|
2180
2262
|
*/
|
|
2181
2263
|
function add_navigation_callback(callbacks, callback) {
|
|
2182
|
-
onMount(() => {
|
|
2264
|
+
svelte.onMount(() => {
|
|
2183
2265
|
callbacks.add(callback);
|
|
2184
2266
|
|
|
2185
2267
|
return () => {
|
|
@@ -2250,23 +2332,28 @@ export function disableScrollHandling() {
|
|
|
2250
2332
|
}
|
|
2251
2333
|
}
|
|
2252
2334
|
|
|
2335
|
+
let warned_on_invalidate_all = false;
|
|
2336
|
+
|
|
2253
2337
|
/**
|
|
2254
2338
|
* Allows you to navigate programmatically to a given route, with options such as keeping the current element focused.
|
|
2255
2339
|
* Returns a Promise that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) to the specified `url`.
|
|
2256
2340
|
*
|
|
2257
|
-
*
|
|
2341
|
+
* `goto` is intended for navigations to routes that belong to the app.
|
|
2342
|
+
* If the URL does not resolve to a route within the app, the returned promise will reject.
|
|
2343
|
+
* For external URLs, use `window.location = url` to perform a full-page navigation instead of calling `goto(url)`.
|
|
2258
2344
|
*
|
|
2259
|
-
* @param {string | URL} url Where to navigate to. Note that if you've set [`config.
|
|
2345
|
+
* @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.
|
|
2260
2346
|
* @param {Object} [opts] Options related to the navigation
|
|
2261
2347
|
* @param {boolean} [opts.replaceState] If `true`, will replace the current `history` entry rather than creating a new one with `pushState`
|
|
2262
2348
|
* @param {boolean} [opts.noScroll] If `true`, the browser will maintain its scroll position rather than scrolling to the top of the page after navigation
|
|
2263
2349
|
* @param {boolean} [opts.keepFocus] If `true`, the currently focused element will retain focus after navigation. Otherwise, focus will be reset to the body
|
|
2264
|
-
* @param {boolean} [opts.
|
|
2350
|
+
* @param {boolean} [opts.refreshAll] If `true`, all `load` functions and queries of the page will be rerun. See https://svelte.dev/docs/kit/load#rerunning-load-functions for more info on invalidation.
|
|
2265
2351
|
* @param {Array<string | URL | ((url: URL) => boolean)>} [opts.invalidate] Causes any load functions to re-run if they depend on one of the urls
|
|
2352
|
+
* @param {boolean} [opts.invalidateAll] Deprecated in favour of opts.refreshAll.
|
|
2266
2353
|
* @param {App.PageState} [opts.state] An optional object that will be available as `page.state`
|
|
2267
2354
|
* @returns {Promise<void>}
|
|
2268
2355
|
*/
|
|
2269
|
-
export function goto(url, opts = {}) {
|
|
2356
|
+
export async function goto(url, opts = {}) {
|
|
2270
2357
|
if (!BROWSER) {
|
|
2271
2358
|
throw new Error('Cannot call goto(...) on the server');
|
|
2272
2359
|
}
|
|
@@ -2274,16 +2361,32 @@ export function goto(url, opts = {}) {
|
|
|
2274
2361
|
url = new URL(resolve_url(url));
|
|
2275
2362
|
|
|
2276
2363
|
if (url.origin !== origin) {
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
: 'goto: invalid URL'
|
|
2282
|
-
)
|
|
2364
|
+
throw new Error(
|
|
2365
|
+
DEV
|
|
2366
|
+
? `Cannot use \`goto\` with an external URL. Use \`window.location = "${url}"\` instead`
|
|
2367
|
+
: 'goto: invalid URL'
|
|
2283
2368
|
);
|
|
2284
2369
|
}
|
|
2285
2370
|
|
|
2286
|
-
|
|
2371
|
+
const intent = await get_navigation_intent(url, false);
|
|
2372
|
+
|
|
2373
|
+
if (!intent) {
|
|
2374
|
+
throw new Error(
|
|
2375
|
+
DEV
|
|
2376
|
+
? `Cannot use \`goto\` with a URL that does not resolve to a route within the app. Use \`window.location = "${url}"\` instead`
|
|
2377
|
+
: 'goto: invalid URL'
|
|
2378
|
+
);
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
if (DEV && 'invalidateAll' in opts && !warned_on_invalidate_all) {
|
|
2382
|
+
warned_on_invalidate_all = true;
|
|
2383
|
+
console.warn(
|
|
2384
|
+
`The \`goto(..., { invalidateAll: ${opts.invalidateAll} })\` option has been deprecated in favour of \`refreshAll\``
|
|
2385
|
+
);
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
opts.refreshAll = opts.refreshAll ?? opts.invalidateAll;
|
|
2389
|
+
return _goto(url, opts, 0, {}, intent);
|
|
2287
2390
|
}
|
|
2288
2391
|
|
|
2289
2392
|
/**
|
|
@@ -2302,16 +2405,17 @@ export function goto(url, opts = {}) {
|
|
|
2302
2405
|
* invalidate((url) => url.pathname === '/path');
|
|
2303
2406
|
* ```
|
|
2304
2407
|
* @param {string | URL | ((url: URL) => boolean)} resource The invalidated URL
|
|
2408
|
+
* @param {boolean} [keepState] If `true`, the current `page.state` will be preserved. Otherwise, it will be reset to an empty object. `false` by default.
|
|
2305
2409
|
* @returns {Promise<void>}
|
|
2306
2410
|
*/
|
|
2307
|
-
export function invalidate(resource) {
|
|
2411
|
+
export function invalidate(resource, keepState = false) {
|
|
2308
2412
|
if (!BROWSER) {
|
|
2309
2413
|
throw new Error('Cannot call invalidate(...) on the server');
|
|
2310
2414
|
}
|
|
2311
2415
|
|
|
2312
2416
|
push_invalidated(resource);
|
|
2313
2417
|
|
|
2314
|
-
return _invalidate();
|
|
2418
|
+
return _invalidate(!keepState);
|
|
2315
2419
|
}
|
|
2316
2420
|
|
|
2317
2421
|
/**
|
|
@@ -2328,6 +2432,10 @@ function push_invalidated(resource) {
|
|
|
2328
2432
|
|
|
2329
2433
|
/**
|
|
2330
2434
|
* Causes all `load` and `query` functions belonging to the currently active page to re-run. Returns a `Promise` that resolves when the page is subsequently updated.
|
|
2435
|
+
*
|
|
2436
|
+
* Note that this resets `page.state` to an empty object. If you want to preserve `page.state` (for example when using [shallow routing](https://svelte.dev/docs/kit/shallow-routing)), use `refreshAll` instead.
|
|
2437
|
+
*
|
|
2438
|
+
* @deprecated Use [`refreshAll`](https://svelte.dev/docs/kit/$app-navigation#refreshAll) instead. Unlike `invalidateAll`, `refreshAll` does not reset `page.state`.
|
|
2331
2439
|
* @returns {Promise<void>}
|
|
2332
2440
|
*/
|
|
2333
2441
|
export function invalidateAll() {
|
|
@@ -2340,18 +2448,17 @@ export function invalidateAll() {
|
|
|
2340
2448
|
}
|
|
2341
2449
|
|
|
2342
2450
|
/**
|
|
2343
|
-
* Causes all currently active remote functions to refresh, and all `load` functions belonging to the currently active page to re-run
|
|
2451
|
+
* Causes all currently active remote functions to refresh, and all `load` functions belonging to the currently active page to re-run.
|
|
2344
2452
|
* Returns a `Promise` that resolves when the page is subsequently updated.
|
|
2345
|
-
* @param {{ includeLoadFunctions?: boolean }} [options]
|
|
2346
2453
|
* @returns {Promise<void>}
|
|
2347
2454
|
*/
|
|
2348
|
-
export function refreshAll(
|
|
2455
|
+
export function refreshAll() {
|
|
2349
2456
|
if (!BROWSER) {
|
|
2350
2457
|
throw new Error('Cannot call refreshAll() on the server');
|
|
2351
2458
|
}
|
|
2352
2459
|
|
|
2353
2460
|
force_invalidation = true;
|
|
2354
|
-
return _invalidate(
|
|
2461
|
+
return _invalidate(false);
|
|
2355
2462
|
}
|
|
2356
2463
|
|
|
2357
2464
|
/**
|
|
@@ -2364,7 +2471,7 @@ export function refreshAll({ includeLoadFunctions = true } = {}) {
|
|
|
2364
2471
|
* Returns a Promise that resolves with the result of running the new route's `load` functions once the preload is complete.
|
|
2365
2472
|
*
|
|
2366
2473
|
* @param {string} href Page to preload
|
|
2367
|
-
* @returns {Promise<{ type: 'loaded';
|
|
2474
|
+
* @returns {Promise<({ type: 'loaded'; data: Record<string, any> } | { type: 'redirect'; location: string } | { type: 'error'; error: App.Error }) & { status: number; }>}
|
|
2368
2475
|
*/
|
|
2369
2476
|
export async function preloadData(href) {
|
|
2370
2477
|
if (!BROWSER) {
|
|
@@ -2378,15 +2485,32 @@ export async function preloadData(href) {
|
|
|
2378
2485
|
throw new Error(`Attempted to preload a URL that does not belong to this app: ${url}`);
|
|
2379
2486
|
}
|
|
2380
2487
|
|
|
2381
|
-
|
|
2488
|
+
/** @type {Awaited<ReturnType<typeof _preload_data>>} */
|
|
2489
|
+
let result;
|
|
2490
|
+
|
|
2491
|
+
try {
|
|
2492
|
+
result = await _preload_data(intent);
|
|
2493
|
+
} catch (error) {
|
|
2494
|
+
// `load_route` throws the handled error (an `App.Error` with a `status`)
|
|
2495
|
+
// when a preload fails, so surface it in the documented `{ type: 'error' }` shape
|
|
2496
|
+
const handled = /** @type {App.Error & { status?: number }} */ (error);
|
|
2497
|
+
return {
|
|
2498
|
+
type: 'error',
|
|
2499
|
+
status: handled?.status ?? 500,
|
|
2500
|
+
error: handled
|
|
2501
|
+
};
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2382
2504
|
if (result.type === 'redirect') {
|
|
2383
2505
|
return {
|
|
2384
2506
|
type: result.type,
|
|
2507
|
+
status: result.status,
|
|
2385
2508
|
location: result.location
|
|
2386
2509
|
};
|
|
2387
2510
|
}
|
|
2388
2511
|
|
|
2389
2512
|
const { status, data } = result.props.page ?? page;
|
|
2513
|
+
|
|
2390
2514
|
return { type: result.type, status, data };
|
|
2391
2515
|
}
|
|
2392
2516
|
|
|
@@ -2407,7 +2531,9 @@ export async function preloadCode(pathname) {
|
|
|
2407
2531
|
throw new Error('Cannot call preloadCode(...) on the server');
|
|
2408
2532
|
}
|
|
2409
2533
|
|
|
2410
|
-
|
|
2534
|
+
// `current.url` is null until the first navigation/hydration completes, so fall back
|
|
2535
|
+
// to `location` to support calling `preloadCode` during initial page load (#13297)
|
|
2536
|
+
const url = new URL(pathname, current.url ?? location.href);
|
|
2411
2537
|
|
|
2412
2538
|
if (DEV) {
|
|
2413
2539
|
if (!pathname.startsWith('/')) {
|
|
@@ -2472,10 +2598,6 @@ export function pushState(url, state) {
|
|
|
2472
2598
|
has_navigated = true;
|
|
2473
2599
|
|
|
2474
2600
|
page.state = state;
|
|
2475
|
-
root.$set({
|
|
2476
|
-
// we need to assign a new page object so that subscribers are correctly notified
|
|
2477
|
-
page: untrack(() => clone_page(page))
|
|
2478
|
-
});
|
|
2479
2601
|
|
|
2480
2602
|
clear_onward_history(current_history_index, current_navigation_index);
|
|
2481
2603
|
}
|
|
@@ -2516,9 +2638,6 @@ export function replaceState(url, state) {
|
|
|
2516
2638
|
history.replaceState(opts, '', resolve_url(url));
|
|
2517
2639
|
|
|
2518
2640
|
page.state = state;
|
|
2519
|
-
root.$set({
|
|
2520
|
-
page: untrack(() => clone_page(page))
|
|
2521
|
-
});
|
|
2522
2641
|
}
|
|
2523
2642
|
|
|
2524
2643
|
/**
|
|
@@ -2535,9 +2654,9 @@ export async function applyAction(result) {
|
|
|
2535
2654
|
}
|
|
2536
2655
|
|
|
2537
2656
|
if (result.type === 'error') {
|
|
2538
|
-
await set_nearest_error_page(result.error
|
|
2657
|
+
await set_nearest_error_page(result.error);
|
|
2539
2658
|
} else if (result.type === 'redirect') {
|
|
2540
|
-
await _goto(result.location, {
|
|
2659
|
+
await _goto(result.location, { refreshAll: true }, 0);
|
|
2541
2660
|
} else {
|
|
2542
2661
|
page.form = result.data;
|
|
2543
2662
|
page.status = result.status;
|
|
@@ -2546,25 +2665,23 @@ export async function applyAction(result) {
|
|
|
2546
2665
|
root.$set({
|
|
2547
2666
|
// this brings Svelte's view of the world in line with SvelteKit's
|
|
2548
2667
|
// after use:enhance reset the form....
|
|
2549
|
-
form: null
|
|
2550
|
-
page: clone_page(page)
|
|
2668
|
+
form: null
|
|
2551
2669
|
});
|
|
2552
2670
|
|
|
2553
2671
|
// ...so that setting the `form` prop takes effect and isn't ignored
|
|
2554
|
-
await tick();
|
|
2672
|
+
await svelte.tick();
|
|
2555
2673
|
root.$set({ form: result.data });
|
|
2556
2674
|
|
|
2557
2675
|
if (result.type === 'success') {
|
|
2558
|
-
reset_focus(page.url);
|
|
2676
|
+
reset_focus(/** @type {URL} */ (page.url));
|
|
2559
2677
|
}
|
|
2560
2678
|
}
|
|
2561
2679
|
}
|
|
2562
2680
|
|
|
2563
2681
|
/**
|
|
2564
2682
|
* @param {App.Error} error
|
|
2565
|
-
* @param {number} status
|
|
2566
2683
|
*/
|
|
2567
|
-
export async function set_nearest_error_page(error
|
|
2684
|
+
export async function set_nearest_error_page(error) {
|
|
2568
2685
|
const url = new URL(location.href);
|
|
2569
2686
|
|
|
2570
2687
|
const { branch, route } = current;
|
|
@@ -2576,7 +2693,6 @@ export async function set_nearest_error_page(error, status = 500) {
|
|
|
2576
2693
|
url,
|
|
2577
2694
|
params: current.params,
|
|
2578
2695
|
branch: branch.slice(0, error_load.idx).concat(error_load.node),
|
|
2579
|
-
status,
|
|
2580
2696
|
error,
|
|
2581
2697
|
// do not set errors, we haven't changed the page so the previous ones are still current
|
|
2582
2698
|
route
|
|
@@ -2585,9 +2701,10 @@ export async function set_nearest_error_page(error, status = 500) {
|
|
|
2585
2701
|
current = { ...navigation_result.state, nav: current.nav };
|
|
2586
2702
|
|
|
2587
2703
|
root.$set(navigation_result.props);
|
|
2704
|
+
current_tree = navigation_result.props.tree;
|
|
2588
2705
|
update(navigation_result.props.page);
|
|
2589
2706
|
|
|
2590
|
-
void tick().then(() => reset_focus(current.url));
|
|
2707
|
+
void svelte.tick().then(() => reset_focus(current.url));
|
|
2591
2708
|
}
|
|
2592
2709
|
}
|
|
2593
2710
|
|
|
@@ -2811,7 +2928,7 @@ function _start_router() {
|
|
|
2811
2928
|
|
|
2812
2929
|
if (event.state?.[HISTORY_INDEX]) {
|
|
2813
2930
|
const history_index = event.state[HISTORY_INDEX];
|
|
2814
|
-
|
|
2931
|
+
navigation_token = invalidation_token = {};
|
|
2815
2932
|
|
|
2816
2933
|
// if a popstate-driven navigation is cancelled, we need to counteract it
|
|
2817
2934
|
// with history.go, which means we end up back here, hence this check
|
|
@@ -2860,7 +2977,7 @@ function _start_router() {
|
|
|
2860
2977
|
block: () => {
|
|
2861
2978
|
history.go(-delta);
|
|
2862
2979
|
},
|
|
2863
|
-
nav_token:
|
|
2980
|
+
nav_token: navigation_token,
|
|
2864
2981
|
event
|
|
2865
2982
|
});
|
|
2866
2983
|
} else {
|
|
@@ -2912,7 +3029,7 @@ function _start_router() {
|
|
|
2912
3029
|
// the navigation away from it was successful.
|
|
2913
3030
|
// Info about bfcache here: https://web.dev/bfcache
|
|
2914
3031
|
if (event.persisted) {
|
|
2915
|
-
|
|
3032
|
+
navigating.current = null;
|
|
2916
3033
|
}
|
|
2917
3034
|
});
|
|
2918
3035
|
|
|
@@ -2921,18 +3038,17 @@ function _start_router() {
|
|
|
2921
3038
|
*/
|
|
2922
3039
|
function update_url(url) {
|
|
2923
3040
|
current.url = page.url = url;
|
|
2924
|
-
stores.page.set(clone_page(page));
|
|
2925
|
-
stores.page.notify();
|
|
2926
3041
|
}
|
|
2927
3042
|
}
|
|
2928
3043
|
|
|
2929
3044
|
/**
|
|
2930
3045
|
* @param {HTMLElement} target
|
|
2931
3046
|
* @param {import('./types.js').HydrateOptions} opts
|
|
3047
|
+
* @returns {Promise<void>}
|
|
2932
3048
|
*/
|
|
2933
3049
|
async function _hydrate(
|
|
2934
3050
|
target,
|
|
2935
|
-
{ status
|
|
3051
|
+
{ status, error, node_ids, params, route, server_route, data: server_data_nodes, form }
|
|
2936
3052
|
) {
|
|
2937
3053
|
hydrated = true;
|
|
2938
3054
|
|
|
@@ -3015,23 +3131,25 @@ async function _hydrate(
|
|
|
3015
3131
|
if (error instanceof Redirect) {
|
|
3016
3132
|
// this is a real edge case — `load` would need to return
|
|
3017
3133
|
// a redirect but only in the browser
|
|
3018
|
-
await native_navigation(new URL(error.location, location.href));
|
|
3019
|
-
return;
|
|
3134
|
+
return await native_navigation(new URL(error.location, location.href));
|
|
3020
3135
|
}
|
|
3021
3136
|
|
|
3137
|
+
const handled_error = await handle_error(error, { url, params, route });
|
|
3138
|
+
|
|
3022
3139
|
result = await load_root_error_page({
|
|
3023
|
-
|
|
3024
|
-
error: await handle_error(error, { url, params, route }),
|
|
3140
|
+
error: handled_error,
|
|
3025
3141
|
url,
|
|
3026
3142
|
route
|
|
3027
3143
|
});
|
|
3028
3144
|
|
|
3029
3145
|
target.textContent = '';
|
|
3030
3146
|
hydrate = false;
|
|
3031
|
-
} finally {
|
|
3032
|
-
query_responses = {};
|
|
3033
3147
|
}
|
|
3034
3148
|
|
|
3149
|
+
// Exit early when we encounter a redirect while loading the root error page.
|
|
3150
|
+
// In this case, `initialize` will be called later on
|
|
3151
|
+
if (!result) return;
|
|
3152
|
+
|
|
3035
3153
|
if (result.props.page) {
|
|
3036
3154
|
result.props.page.state = {};
|
|
3037
3155
|
}
|
|
@@ -3208,9 +3326,6 @@ function reset_focus(url, scroll = true) {
|
|
|
3208
3326
|
const tabindex = root.getAttribute('tabindex');
|
|
3209
3327
|
|
|
3210
3328
|
root.tabIndex = -1;
|
|
3211
|
-
// TODO: remove this when we switch to TypeScript 6
|
|
3212
|
-
// @ts-ignore options.focusVisible is only typed in TypeScript 6
|
|
3213
|
-
// See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#browser_compatibility
|
|
3214
3329
|
root.focus({ preventScroll: true, focusVisible: false });
|
|
3215
3330
|
|
|
3216
3331
|
// restore `tabindex` as to prevent `root` from stealing input from elements
|
|
@@ -3313,28 +3428,6 @@ function create_navigation(current, intent, url, type, target_scroll = null) {
|
|
|
3313
3428
|
};
|
|
3314
3429
|
}
|
|
3315
3430
|
|
|
3316
|
-
/**
|
|
3317
|
-
* TODO: remove this in 3.0 when the page store is also removed
|
|
3318
|
-
*
|
|
3319
|
-
* We need to assign a new page object so that subscribers are correctly notified.
|
|
3320
|
-
* However, spreading `{ ...page }` returns an empty object so we manually
|
|
3321
|
-
* assign to each property instead.
|
|
3322
|
-
*
|
|
3323
|
-
* @param {import('@sveltejs/kit').Page} page
|
|
3324
|
-
*/
|
|
3325
|
-
function clone_page(page) {
|
|
3326
|
-
return {
|
|
3327
|
-
data: page.data,
|
|
3328
|
-
error: page.error,
|
|
3329
|
-
form: page.form,
|
|
3330
|
-
params: page.params,
|
|
3331
|
-
route: page.route,
|
|
3332
|
-
state: page.state,
|
|
3333
|
-
status: page.status,
|
|
3334
|
-
url: page.url
|
|
3335
|
-
};
|
|
3336
|
-
}
|
|
3337
|
-
|
|
3338
3431
|
/**
|
|
3339
3432
|
* @param {URL} url
|
|
3340
3433
|
* @returns {URL}
|