@sveltejs/kit 2.0.8 → 2.1.1
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 +1 -1
- package/src/exports/public.d.ts +1 -0
- package/src/exports/vite/dev/index.js +1 -1
- package/src/exports/vite/index.js +3 -3
- package/src/runtime/app/forms.js +3 -17
- package/src/runtime/app/navigation.js +13 -142
- package/src/runtime/app/stores.js +5 -5
- package/src/runtime/client/client.js +1801 -1617
- package/src/runtime/client/entry.js +3 -0
- package/src/runtime/client/fetcher.js +5 -4
- package/src/runtime/client/types.d.ts +0 -41
- package/src/utils/routing.js +3 -6
- package/src/utils/url.js +5 -3
- package/src/version.js +1 -1
- package/types/index.d.ts +56 -48
- package/types/index.d.ts.map +6 -6
- package/src/runtime/client/singletons.js +0 -59
- package/src/runtime/client/start.js +0 -28
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEV } from 'esm-env';
|
|
1
|
+
import { BROWSER, DEV } from 'esm-env';
|
|
2
2
|
import { onMount, tick } from 'svelte';
|
|
3
3
|
import {
|
|
4
4
|
add_data_suffix,
|
|
@@ -24,9 +24,10 @@ import {
|
|
|
24
24
|
get_router_options,
|
|
25
25
|
is_external_url,
|
|
26
26
|
origin,
|
|
27
|
-
scroll_state
|
|
27
|
+
scroll_state,
|
|
28
|
+
notifiable_store,
|
|
29
|
+
create_updated_store
|
|
28
30
|
} from './utils.js';
|
|
29
|
-
|
|
30
31
|
import { base } from '__sveltekit/paths';
|
|
31
32
|
import * as devalue from 'devalue';
|
|
32
33
|
import {
|
|
@@ -42,39 +43,31 @@ import { validate_page_exports } from '../../utils/exports.js';
|
|
|
42
43
|
import { compact } from '../../utils/array.js';
|
|
43
44
|
import { HttpError, Redirect, SvelteKitError } from '../control.js';
|
|
44
45
|
import { INVALIDATED_PARAM, TRAILING_SLASH_PARAM, validate_depends } from '../shared.js';
|
|
45
|
-
import { stores } from './singletons.js';
|
|
46
46
|
import { get_message, get_status } from '../../utils/error.js';
|
|
47
|
+
import { writable } from 'svelte/store';
|
|
47
48
|
|
|
48
49
|
let errored = false;
|
|
49
50
|
|
|
50
|
-
/** @typedef {{ x: number; y: number }} ScrollPosition */
|
|
51
|
-
|
|
52
51
|
// We track the scroll position associated with each history entry in sessionStorage,
|
|
53
52
|
// rather than on history.state itself, because when navigation is driven by
|
|
54
53
|
// popstate it's too late to update the scroll position associated with the
|
|
55
54
|
// state we're navigating from
|
|
56
55
|
/**
|
|
57
56
|
* history index -> { x, y }
|
|
58
|
-
* @type {Record<number,
|
|
57
|
+
* @type {Record<number, { x: number; y: number }>}
|
|
59
58
|
*/
|
|
60
59
|
const scroll_positions = storage.get(SCROLL_KEY) ?? {};
|
|
61
60
|
|
|
62
|
-
/**
|
|
63
|
-
* history index -> any
|
|
64
|
-
* @type {Record<string, Record<string, any>>}
|
|
65
|
-
*/
|
|
66
|
-
const states = storage.get(STATES_KEY, devalue.parse) ?? {};
|
|
67
|
-
|
|
68
61
|
/**
|
|
69
62
|
* navigation index -> any
|
|
70
63
|
* @type {Record<string, any[]>}
|
|
71
64
|
*/
|
|
72
65
|
const snapshots = storage.get(SNAPSHOT_KEY) ?? {};
|
|
73
66
|
|
|
74
|
-
const original_push_state = history.pushState;
|
|
75
|
-
const original_replace_state = history.replaceState;
|
|
67
|
+
const original_push_state = BROWSER ? history.pushState : () => {};
|
|
68
|
+
const original_replace_state = BROWSER ? history.replaceState : () => {};
|
|
76
69
|
|
|
77
|
-
if (DEV) {
|
|
70
|
+
if (DEV && BROWSER) {
|
|
78
71
|
let warned = false;
|
|
79
72
|
|
|
80
73
|
const warn = () => {
|
|
@@ -97,6 +90,15 @@ if (DEV) {
|
|
|
97
90
|
};
|
|
98
91
|
}
|
|
99
92
|
|
|
93
|
+
export const stores = {
|
|
94
|
+
url: /* @__PURE__ */ notifiable_store({}),
|
|
95
|
+
page: /* @__PURE__ */ notifiable_store({}),
|
|
96
|
+
navigating: /* @__PURE__ */ writable(
|
|
97
|
+
/** @type {import('@sveltejs/kit').Navigation | null} */ (null)
|
|
98
|
+
),
|
|
99
|
+
updated: /* @__PURE__ */ create_updated_store()
|
|
100
|
+
};
|
|
101
|
+
|
|
100
102
|
/** @param {number} index */
|
|
101
103
|
function update_scroll_positions(index) {
|
|
102
104
|
scroll_positions[index] = scroll_state();
|
|
@@ -135,78 +137,106 @@ function native_navigation(url) {
|
|
|
135
137
|
|
|
136
138
|
function noop() {}
|
|
137
139
|
|
|
140
|
+
/** @type {import('types').CSRRoute[]} */
|
|
141
|
+
let routes;
|
|
142
|
+
/** @type {import('types').CSRPageNodeLoader} */
|
|
143
|
+
let default_layout_loader;
|
|
144
|
+
/** @type {import('types').CSRPageNodeLoader} */
|
|
145
|
+
let default_error_loader;
|
|
146
|
+
/** @type {HTMLElement} */
|
|
147
|
+
let container;
|
|
148
|
+
/** @type {HTMLElement} */
|
|
149
|
+
let target;
|
|
150
|
+
/** @type {import('./types.js').SvelteKitApp} */
|
|
151
|
+
let app;
|
|
152
|
+
|
|
153
|
+
/** @type {Array<((url: URL) => boolean)>} */
|
|
154
|
+
const invalidated = [];
|
|
155
|
+
|
|
138
156
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
157
|
+
* An array of the `+layout.svelte` and `+page.svelte` component instances
|
|
158
|
+
* that currently live on the page — used for capturing and restoring snapshots.
|
|
159
|
+
* It's updated/manipulated through `bind:this` in `Root.svelte`.
|
|
160
|
+
* @type {import('svelte').SvelteComponent[]}
|
|
142
161
|
*/
|
|
143
|
-
|
|
144
|
-
const routes = parse(app);
|
|
162
|
+
const components = [];
|
|
145
163
|
|
|
146
|
-
|
|
147
|
-
|
|
164
|
+
/** @type {{id: string, promise: Promise<import('./types.js').NavigationResult>} | null} */
|
|
165
|
+
let load_cache = null;
|
|
148
166
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
default_layout_loader();
|
|
152
|
-
default_error_loader();
|
|
167
|
+
/** @type {Array<(navigation: import('@sveltejs/kit').BeforeNavigate) => void>} */
|
|
168
|
+
const before_navigate_callbacks = [];
|
|
153
169
|
|
|
154
|
-
|
|
170
|
+
/** @type {Array<(navigation: import('@sveltejs/kit').OnNavigate) => import('types').MaybePromise<(() => void) | void>>} */
|
|
171
|
+
const on_navigate_callbacks = [];
|
|
155
172
|
|
|
156
|
-
|
|
157
|
-
|
|
173
|
+
/** @type {Array<(navigation: import('@sveltejs/kit').AfterNavigate) => void>} */
|
|
174
|
+
let after_navigate_callbacks = [];
|
|
158
175
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
176
|
+
/** @type {import('./types.js').NavigationState} */
|
|
177
|
+
let current = {
|
|
178
|
+
branch: [],
|
|
179
|
+
error: null,
|
|
180
|
+
// @ts-ignore - we need the initial value to be null
|
|
181
|
+
url: null
|
|
182
|
+
};
|
|
166
183
|
|
|
167
|
-
|
|
168
|
-
|
|
184
|
+
/** this being true means we SSR'd */
|
|
185
|
+
let hydrated = false;
|
|
186
|
+
let started = false;
|
|
187
|
+
let autoscroll = true;
|
|
188
|
+
let updating = false;
|
|
189
|
+
let navigating = false;
|
|
190
|
+
let hash_navigating = false;
|
|
191
|
+
/** True as soon as there happened one client-side navigation (excluding the SvelteKit-initialized initial one when in SPA mode) */
|
|
192
|
+
let has_navigated = false;
|
|
169
193
|
|
|
170
|
-
|
|
171
|
-
/** @type {Array<(navigation: import('@sveltejs/kit').BeforeNavigate) => void>} */
|
|
172
|
-
before_navigate: [],
|
|
194
|
+
let force_invalidation = false;
|
|
173
195
|
|
|
174
|
-
|
|
175
|
-
|
|
196
|
+
/** @type {import('svelte').SvelteComponent} */
|
|
197
|
+
let root;
|
|
176
198
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
};
|
|
199
|
+
/** @type {number} keeping track of the history index in order to prevent popstate navigation events if needed */
|
|
200
|
+
let current_history_index;
|
|
180
201
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
202
|
+
/** @type {number} */
|
|
203
|
+
let current_navigation_index;
|
|
204
|
+
|
|
205
|
+
/** @type {import('@sveltejs/kit').Page} */
|
|
206
|
+
let page;
|
|
207
|
+
|
|
208
|
+
/** @type {{}} */
|
|
209
|
+
let token;
|
|
188
210
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
let started = false;
|
|
192
|
-
let autoscroll = true;
|
|
193
|
-
let updating = false;
|
|
194
|
-
let navigating = false;
|
|
195
|
-
let hash_navigating = false;
|
|
196
|
-
/** True as soon as there happened one client-side navigation (excluding the SvelteKit-initialized initial one when in SPA mode) */
|
|
197
|
-
let has_navigated = false;
|
|
211
|
+
/** @type {Promise<void> | null} */
|
|
212
|
+
let pending_invalidate;
|
|
198
213
|
|
|
199
|
-
|
|
214
|
+
/**
|
|
215
|
+
* @param {import('./types.js').SvelteKitApp} _app
|
|
216
|
+
* @param {HTMLElement} _target
|
|
217
|
+
* @param {Parameters<typeof _hydrate>[1]} [hydrate]
|
|
218
|
+
*/
|
|
219
|
+
export async function start(_app, _target, hydrate) {
|
|
220
|
+
if (DEV && _target === document.body) {
|
|
221
|
+
console.warn(
|
|
222
|
+
'Placing %sveltekit.body% directly inside <body> is not recommended, as your app may break for users who have certain browser extensions installed.\n\nConsider wrapping it in an element:\n\n<div style="display: contents">\n %sveltekit.body%\n</div>'
|
|
223
|
+
);
|
|
224
|
+
}
|
|
200
225
|
|
|
201
|
-
|
|
202
|
-
|
|
226
|
+
app = _app;
|
|
227
|
+
routes = parse(_app);
|
|
228
|
+
container = __SVELTEKIT_EMBEDDED__ ? _target : document.documentElement;
|
|
229
|
+
target = _target;
|
|
203
230
|
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
231
|
+
// we import the root layout/error nodes eagerly, so that
|
|
232
|
+
// connectivity errors after initialisation don't nuke the app
|
|
233
|
+
default_layout_loader = _app.nodes[0];
|
|
234
|
+
default_error_loader = _app.nodes[1];
|
|
235
|
+
default_layout_loader();
|
|
236
|
+
default_error_loader();
|
|
207
237
|
|
|
208
|
-
|
|
209
|
-
|
|
238
|
+
current_history_index = history.state?.[HISTORY_INDEX];
|
|
239
|
+
current_navigation_index = history.state?.[NAVIGATION_INDEX];
|
|
210
240
|
|
|
211
241
|
if (!current_history_index) {
|
|
212
242
|
// we use Date.now() as an offset so that cross-document navigations
|
|
@@ -234,1876 +264,2030 @@ export function create_client(app, target) {
|
|
|
234
264
|
scrollTo(scroll.x, scroll.y);
|
|
235
265
|
}
|
|
236
266
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
267
|
+
if (hydrate) {
|
|
268
|
+
await _hydrate(target, hydrate);
|
|
269
|
+
} else {
|
|
270
|
+
goto(location.href, { replaceState: true });
|
|
271
|
+
}
|
|
242
272
|
|
|
243
|
-
|
|
244
|
-
|
|
273
|
+
_start_router();
|
|
274
|
+
}
|
|
245
275
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
276
|
+
async function _invalidate() {
|
|
277
|
+
// Accept all invalidations as they come, don't swallow any while another invalidation
|
|
278
|
+
// is running because subsequent invalidations may make earlier ones outdated,
|
|
279
|
+
// but batch multiple synchronous invalidations.
|
|
280
|
+
await (pending_invalidate ||= Promise.resolve());
|
|
281
|
+
if (!pending_invalidate) return;
|
|
282
|
+
pending_invalidate = null;
|
|
253
283
|
|
|
254
|
-
|
|
284
|
+
const intent = get_navigation_intent(current.url, true);
|
|
255
285
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
286
|
+
// Clear preload, it might be affected by the invalidation.
|
|
287
|
+
// Also solves an edge case where a preload is triggered, the navigation for it
|
|
288
|
+
// was then triggered and is still running while the invalidation kicks in,
|
|
289
|
+
// at which point the invalidation should take over and "win".
|
|
290
|
+
load_cache = null;
|
|
261
291
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
292
|
+
const nav_token = (token = {});
|
|
293
|
+
const navigation_result = intent && (await load_route(intent));
|
|
294
|
+
if (nav_token !== token) return;
|
|
265
295
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
root.$set(navigation_result.props);
|
|
296
|
+
if (navigation_result) {
|
|
297
|
+
if (navigation_result.type === 'redirect') {
|
|
298
|
+
await _goto(new URL(navigation_result.location, current.url).href, {}, 1, nav_token);
|
|
299
|
+
} else {
|
|
300
|
+
if (navigation_result.props.page !== undefined) {
|
|
301
|
+
page = navigation_result.props.page;
|
|
274
302
|
}
|
|
303
|
+
root.$set(navigation_result.props);
|
|
275
304
|
}
|
|
276
|
-
|
|
277
|
-
invalidated.length = 0;
|
|
278
305
|
}
|
|
279
306
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (components.some((c) => c?.snapshot)) {
|
|
283
|
-
snapshots[index] = components.map((c) => c?.snapshot?.capture());
|
|
284
|
-
}
|
|
285
|
-
}
|
|
307
|
+
invalidated.length = 0;
|
|
308
|
+
}
|
|
286
309
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
});
|
|
310
|
+
/** @param {number} index */
|
|
311
|
+
function capture_snapshot(index) {
|
|
312
|
+
if (components.some((c) => c?.snapshot)) {
|
|
313
|
+
snapshots[index] = components.map((c) => c?.snapshot?.capture());
|
|
292
314
|
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** @param {number} index */
|
|
318
|
+
function restore_snapshot(index) {
|
|
319
|
+
snapshots[index]?.forEach((value, i) => {
|
|
320
|
+
components[i]?.snapshot?.restore(value);
|
|
321
|
+
});
|
|
322
|
+
}
|
|
293
323
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
324
|
+
function persist_state() {
|
|
325
|
+
update_scroll_positions(current_history_index);
|
|
326
|
+
storage.set(SCROLL_KEY, scroll_positions);
|
|
297
327
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
328
|
+
capture_snapshot(current_navigation_index);
|
|
329
|
+
storage.set(SNAPSHOT_KEY, snapshots);
|
|
330
|
+
}
|
|
302
331
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
}
|
|
332
|
+
/**
|
|
333
|
+
* @param {string | URL} url
|
|
334
|
+
* @param {{ replaceState?: boolean; noScroll?: boolean; keepFocus?: boolean; invalidateAll?: boolean; state?: Record<string, any> }} options
|
|
335
|
+
* @param {number} redirect_count
|
|
336
|
+
* @param {{}} [nav_token]
|
|
337
|
+
*/
|
|
338
|
+
async function _goto(url, options, redirect_count, nav_token) {
|
|
339
|
+
return navigate({
|
|
340
|
+
type: 'goto',
|
|
341
|
+
url: resolve_url(url),
|
|
342
|
+
keepfocus: options.keepFocus,
|
|
343
|
+
noscroll: options.noScroll,
|
|
344
|
+
replace_state: options.replaceState,
|
|
345
|
+
state: options.state,
|
|
346
|
+
redirect_count,
|
|
347
|
+
nav_token,
|
|
348
|
+
accept: () => {
|
|
349
|
+
if (options.invalidateAll) {
|
|
350
|
+
force_invalidation = true;
|
|
323
351
|
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}
|
|
326
355
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
356
|
+
/** @param {import('./types.js').NavigationIntent} intent */
|
|
357
|
+
async function _preload_data(intent) {
|
|
358
|
+
load_cache = {
|
|
359
|
+
id: intent.id,
|
|
360
|
+
promise: load_route(intent).then((result) => {
|
|
361
|
+
if (result.type === 'loaded' && result.state.error) {
|
|
362
|
+
// Don't cache errors, because they might be transient
|
|
363
|
+
load_cache = null;
|
|
364
|
+
}
|
|
365
|
+
return result;
|
|
366
|
+
})
|
|
367
|
+
};
|
|
339
368
|
|
|
340
|
-
|
|
341
|
-
|
|
369
|
+
return load_cache.promise;
|
|
370
|
+
}
|
|
342
371
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
372
|
+
/** @param {string} pathname */
|
|
373
|
+
async function _preload_code(pathname) {
|
|
374
|
+
const route = routes.find((route) => route.exec(get_url_path(pathname)));
|
|
346
375
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
376
|
+
if (route) {
|
|
377
|
+
await Promise.all([...route.layouts, route.leaf].map((load) => load?.[1]()));
|
|
350
378
|
}
|
|
379
|
+
}
|
|
351
380
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
381
|
+
/**
|
|
382
|
+
* @param {import('./types.js').NavigationFinished} result
|
|
383
|
+
* @param {HTMLElement} target
|
|
384
|
+
*/
|
|
385
|
+
function initialize(result, target) {
|
|
386
|
+
if (DEV && result.state.error && document.querySelector('vite-error-overlay')) return;
|
|
355
387
|
|
|
356
|
-
|
|
388
|
+
current = result.state;
|
|
357
389
|
|
|
358
|
-
|
|
359
|
-
|
|
390
|
+
const style = document.querySelector('style[data-sveltekit]');
|
|
391
|
+
if (style) style.remove();
|
|
360
392
|
|
|
361
|
-
|
|
393
|
+
page = /** @type {import('@sveltejs/kit').Page} */ (result.props.page);
|
|
362
394
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
395
|
+
root = new app.root({
|
|
396
|
+
target,
|
|
397
|
+
props: { ...result.props, stores, components },
|
|
398
|
+
hydrate: true
|
|
399
|
+
});
|
|
368
400
|
|
|
369
|
-
|
|
401
|
+
restore_snapshot(current_navigation_index);
|
|
370
402
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
callbacks.after_navigate.forEach((fn) => fn(navigation));
|
|
403
|
+
/** @type {import('@sveltejs/kit').AfterNavigate} */
|
|
404
|
+
const navigation = {
|
|
405
|
+
from: null,
|
|
406
|
+
to: {
|
|
407
|
+
params: current.params,
|
|
408
|
+
route: { id: current.route?.id ?? null },
|
|
409
|
+
url: new URL(location.href)
|
|
410
|
+
},
|
|
411
|
+
willUnload: false,
|
|
412
|
+
type: 'enter',
|
|
413
|
+
complete: Promise.resolve()
|
|
414
|
+
};
|
|
384
415
|
|
|
385
|
-
|
|
386
|
-
}
|
|
416
|
+
after_navigate_callbacks.forEach((fn) => fn(navigation));
|
|
387
417
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
418
|
+
started = true;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
*
|
|
423
|
+
* @param {{
|
|
424
|
+
* url: URL;
|
|
425
|
+
* params: Record<string, string>;
|
|
426
|
+
* branch: Array<import('./types.js').BranchNode | undefined>;
|
|
427
|
+
* status: number;
|
|
428
|
+
* error: App.Error | null;
|
|
429
|
+
* route: import('types').CSRRoute | null;
|
|
430
|
+
* form?: Record<string, any> | null;
|
|
431
|
+
* }} opts
|
|
432
|
+
*/
|
|
433
|
+
async function get_navigation_result_from_branch({
|
|
434
|
+
url,
|
|
435
|
+
params,
|
|
436
|
+
branch,
|
|
437
|
+
status,
|
|
438
|
+
error,
|
|
439
|
+
route,
|
|
440
|
+
form
|
|
441
|
+
}) {
|
|
442
|
+
/** @type {import('types').TrailingSlash} */
|
|
443
|
+
let slash = 'never';
|
|
444
|
+
|
|
445
|
+
// if `paths.base === '/a/b/c`, then the root route is always `/a/b/c/`, regardless of
|
|
446
|
+
// the `trailingSlash` route option, so that relative paths to JS and CSS work
|
|
447
|
+
if (base && (url.pathname === base || url.pathname === base + '/')) {
|
|
448
|
+
slash = 'always';
|
|
449
|
+
} else {
|
|
411
450
|
for (const node of branch) {
|
|
412
451
|
if (node?.slash !== undefined) slash = node.slash;
|
|
413
452
|
}
|
|
414
|
-
|
|
415
|
-
// eslint-disable-next-line
|
|
416
|
-
url.search = url.search; // turn `/?` into `/`
|
|
417
|
-
|
|
418
|
-
/** @type {import('./types.js').NavigationFinished} */
|
|
419
|
-
const result = {
|
|
420
|
-
type: 'loaded',
|
|
421
|
-
state: {
|
|
422
|
-
url,
|
|
423
|
-
params,
|
|
424
|
-
branch,
|
|
425
|
-
error,
|
|
426
|
-
route
|
|
427
|
-
},
|
|
428
|
-
props: {
|
|
429
|
-
// @ts-ignore Somehow it's getting SvelteComponent and SvelteComponentDev mixed up
|
|
430
|
-
constructors: compact(branch).map((branch_node) => branch_node.node.component),
|
|
431
|
-
page
|
|
432
|
-
}
|
|
433
|
-
};
|
|
453
|
+
}
|
|
434
454
|
|
|
435
|
-
|
|
436
|
-
result.props.form = form;
|
|
437
|
-
}
|
|
455
|
+
url.pathname = normalize_path(url.pathname, slash);
|
|
438
456
|
|
|
439
|
-
|
|
440
|
-
|
|
457
|
+
// eslint-disable-next-line
|
|
458
|
+
url.search = url.search; // turn `/?` into `/`
|
|
441
459
|
|
|
442
|
-
|
|
460
|
+
/** @type {import('./types.js').NavigationFinished} */
|
|
461
|
+
const result = {
|
|
462
|
+
type: 'loaded',
|
|
463
|
+
state: {
|
|
464
|
+
url,
|
|
465
|
+
params,
|
|
466
|
+
branch,
|
|
467
|
+
error,
|
|
468
|
+
route
|
|
469
|
+
},
|
|
470
|
+
props: {
|
|
471
|
+
// @ts-ignore Somehow it's getting SvelteComponent and SvelteComponentDev mixed up
|
|
472
|
+
constructors: compact(branch).map((branch_node) => branch_node.node.component),
|
|
473
|
+
page
|
|
474
|
+
}
|
|
475
|
+
};
|
|
443
476
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
477
|
+
if (form !== undefined) {
|
|
478
|
+
result.props.form = form;
|
|
479
|
+
}
|
|
447
480
|
|
|
448
|
-
|
|
449
|
-
|
|
481
|
+
let data = {};
|
|
482
|
+
let data_changed = !page;
|
|
450
483
|
|
|
451
|
-
|
|
484
|
+
let p = 0;
|
|
452
485
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}
|
|
486
|
+
for (let i = 0; i < Math.max(branch.length, current.branch.length); i += 1) {
|
|
487
|
+
const node = branch[i];
|
|
488
|
+
const prev = current.branch[i];
|
|
457
489
|
|
|
458
|
-
|
|
459
|
-
|
|
490
|
+
if (node?.data !== prev?.data) data_changed = true;
|
|
491
|
+
if (!node) continue;
|
|
460
492
|
|
|
461
|
-
|
|
462
|
-
!current.url ||
|
|
463
|
-
url.href !== current.url.href ||
|
|
464
|
-
current.error !== error ||
|
|
465
|
-
(form !== undefined && form !== page.form) ||
|
|
466
|
-
data_changed;
|
|
493
|
+
data = { ...data, ...node.data };
|
|
467
494
|
|
|
468
|
-
if
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
params,
|
|
472
|
-
route: {
|
|
473
|
-
id: route?.id ?? null
|
|
474
|
-
},
|
|
475
|
-
state: {},
|
|
476
|
-
status,
|
|
477
|
-
url: new URL(url),
|
|
478
|
-
form: form ?? null,
|
|
479
|
-
// The whole page store is updated, but this way the object reference stays the same
|
|
480
|
-
data: data_changed ? data : page.data
|
|
481
|
-
};
|
|
495
|
+
// Only set props if the node actually updated. This prevents needless rerenders.
|
|
496
|
+
if (data_changed) {
|
|
497
|
+
result.props[`data_${p}`] = data;
|
|
482
498
|
}
|
|
483
499
|
|
|
484
|
-
|
|
500
|
+
p += 1;
|
|
485
501
|
}
|
|
486
502
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
/** @type {import('types').Uses} */
|
|
508
|
-
const uses = {
|
|
509
|
-
dependencies: new Set(),
|
|
510
|
-
params: new Set(),
|
|
511
|
-
parent: false,
|
|
512
|
-
route: false,
|
|
513
|
-
url: false,
|
|
514
|
-
search_params: new Set()
|
|
503
|
+
const page_changed =
|
|
504
|
+
!current.url ||
|
|
505
|
+
url.href !== current.url.href ||
|
|
506
|
+
current.error !== error ||
|
|
507
|
+
(form !== undefined && form !== page.form) ||
|
|
508
|
+
data_changed;
|
|
509
|
+
|
|
510
|
+
if (page_changed) {
|
|
511
|
+
result.props.page = {
|
|
512
|
+
error,
|
|
513
|
+
params,
|
|
514
|
+
route: {
|
|
515
|
+
id: route?.id ?? null
|
|
516
|
+
},
|
|
517
|
+
state: {},
|
|
518
|
+
status,
|
|
519
|
+
url: new URL(url),
|
|
520
|
+
form: form ?? null,
|
|
521
|
+
// The whole page store is updated, but this way the object reference stays the same
|
|
522
|
+
data: data_changed ? data : page.data
|
|
515
523
|
};
|
|
524
|
+
}
|
|
516
525
|
|
|
517
|
-
|
|
526
|
+
return result;
|
|
527
|
+
}
|
|
518
528
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
529
|
+
/**
|
|
530
|
+
* Call the load function of the given node, if it exists.
|
|
531
|
+
* If `server_data` is passed, this is treated as the initial run and the page endpoint is not requested.
|
|
532
|
+
*
|
|
533
|
+
* @param {{
|
|
534
|
+
* loader: import('types').CSRPageNodeLoader;
|
|
535
|
+
* parent: () => Promise<Record<string, any>>;
|
|
536
|
+
* url: URL;
|
|
537
|
+
* params: Record<string, string>;
|
|
538
|
+
* route: { id: string | null };
|
|
539
|
+
* server_data_node: import('./types.js').DataNode | null;
|
|
540
|
+
* }} options
|
|
541
|
+
* @returns {Promise<import('./types.js').BranchNode>}
|
|
542
|
+
*/
|
|
543
|
+
async function load_node({ loader, parent, url, params, route, server_data_node }) {
|
|
544
|
+
/** @type {Record<string, any> | null} */
|
|
545
|
+
let data = null;
|
|
546
|
+
|
|
547
|
+
let is_tracking = true;
|
|
548
|
+
|
|
549
|
+
/** @type {import('types').Uses} */
|
|
550
|
+
const uses = {
|
|
551
|
+
dependencies: new Set(),
|
|
552
|
+
params: new Set(),
|
|
553
|
+
parent: false,
|
|
554
|
+
route: false,
|
|
555
|
+
url: false,
|
|
556
|
+
search_params: new Set()
|
|
557
|
+
};
|
|
522
558
|
|
|
523
|
-
|
|
524
|
-
/** @param {string[]} deps */
|
|
525
|
-
function depends(...deps) {
|
|
526
|
-
for (const dep of deps) {
|
|
527
|
-
if (DEV) validate_depends(/** @type {string} */ (route.id), dep);
|
|
559
|
+
const node = await loader();
|
|
528
560
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
}
|
|
561
|
+
if (DEV) {
|
|
562
|
+
validate_page_exports(node.universal);
|
|
563
|
+
}
|
|
533
564
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
params: new Proxy(params, {
|
|
545
|
-
get: (target, key) => {
|
|
546
|
-
if (is_tracking) {
|
|
547
|
-
uses.params.add(/** @type {string} */ (key));
|
|
548
|
-
}
|
|
549
|
-
return target[/** @type {string} */ (key)];
|
|
550
|
-
}
|
|
551
|
-
}),
|
|
552
|
-
data: server_data_node?.data ?? null,
|
|
553
|
-
url: make_trackable(
|
|
554
|
-
url,
|
|
555
|
-
() => {
|
|
556
|
-
if (is_tracking) {
|
|
557
|
-
uses.url = true;
|
|
558
|
-
}
|
|
559
|
-
},
|
|
560
|
-
(param) => {
|
|
561
|
-
if (is_tracking) {
|
|
562
|
-
uses.search_params.add(param);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
),
|
|
566
|
-
async fetch(resource, init) {
|
|
567
|
-
/** @type {URL | string} */
|
|
568
|
-
let requested;
|
|
569
|
-
|
|
570
|
-
if (resource instanceof Request) {
|
|
571
|
-
requested = resource.url;
|
|
572
|
-
|
|
573
|
-
// we're not allowed to modify the received `Request` object, so in order
|
|
574
|
-
// to fixup relative urls we create a new equivalent `init` object instead
|
|
575
|
-
init = {
|
|
576
|
-
// the request body must be consumed in memory until browsers
|
|
577
|
-
// implement streaming request bodies and/or the body getter
|
|
578
|
-
body:
|
|
579
|
-
resource.method === 'GET' || resource.method === 'HEAD'
|
|
580
|
-
? undefined
|
|
581
|
-
: await resource.blob(),
|
|
582
|
-
cache: resource.cache,
|
|
583
|
-
credentials: resource.credentials,
|
|
584
|
-
headers: resource.headers,
|
|
585
|
-
integrity: resource.integrity,
|
|
586
|
-
keepalive: resource.keepalive,
|
|
587
|
-
method: resource.method,
|
|
588
|
-
mode: resource.mode,
|
|
589
|
-
redirect: resource.redirect,
|
|
590
|
-
referrer: resource.referrer,
|
|
591
|
-
referrerPolicy: resource.referrerPolicy,
|
|
592
|
-
signal: resource.signal,
|
|
593
|
-
...init
|
|
594
|
-
};
|
|
595
|
-
} else {
|
|
596
|
-
requested = resource;
|
|
597
|
-
}
|
|
565
|
+
if (node.universal?.load) {
|
|
566
|
+
/** @param {string[]} deps */
|
|
567
|
+
function depends(...deps) {
|
|
568
|
+
for (const dep of deps) {
|
|
569
|
+
if (DEV) validate_depends(/** @type {string} */ (route.id), dep);
|
|
570
|
+
|
|
571
|
+
const { href } = new URL(dep, url);
|
|
572
|
+
uses.dependencies.add(href);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
598
575
|
|
|
599
|
-
|
|
600
|
-
|
|
576
|
+
/** @type {import('@sveltejs/kit').LoadEvent} */
|
|
577
|
+
const load_input = {
|
|
578
|
+
route: new Proxy(route, {
|
|
579
|
+
get: (target, key) => {
|
|
601
580
|
if (is_tracking) {
|
|
602
|
-
|
|
581
|
+
uses.route = true;
|
|
603
582
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
583
|
+
return target[/** @type {'id'} */ (key)];
|
|
584
|
+
}
|
|
585
|
+
}),
|
|
586
|
+
params: new Proxy(params, {
|
|
587
|
+
get: (target, key) => {
|
|
588
|
+
if (is_tracking) {
|
|
589
|
+
uses.params.add(/** @type {string} */ (key));
|
|
608
590
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
depends,
|
|
617
|
-
parent() {
|
|
591
|
+
return target[/** @type {string} */ (key)];
|
|
592
|
+
}
|
|
593
|
+
}),
|
|
594
|
+
data: server_data_node?.data ?? null,
|
|
595
|
+
url: make_trackable(
|
|
596
|
+
url,
|
|
597
|
+
() => {
|
|
618
598
|
if (is_tracking) {
|
|
619
|
-
uses.
|
|
599
|
+
uses.url = true;
|
|
620
600
|
}
|
|
621
|
-
return parent();
|
|
622
601
|
},
|
|
623
|
-
|
|
624
|
-
is_tracking
|
|
625
|
-
|
|
626
|
-
return fn();
|
|
627
|
-
} finally {
|
|
628
|
-
is_tracking = true;
|
|
602
|
+
(param) => {
|
|
603
|
+
if (is_tracking) {
|
|
604
|
+
uses.search_params.add(param);
|
|
629
605
|
}
|
|
630
606
|
}
|
|
631
|
-
|
|
607
|
+
),
|
|
608
|
+
async fetch(resource, init) {
|
|
609
|
+
/** @type {URL | string} */
|
|
610
|
+
let requested;
|
|
611
|
+
|
|
612
|
+
if (resource instanceof Request) {
|
|
613
|
+
requested = resource.url;
|
|
614
|
+
|
|
615
|
+
// we're not allowed to modify the received `Request` object, so in order
|
|
616
|
+
// to fixup relative urls we create a new equivalent `init` object instead
|
|
617
|
+
init = {
|
|
618
|
+
// the request body must be consumed in memory until browsers
|
|
619
|
+
// implement streaming request bodies and/or the body getter
|
|
620
|
+
body:
|
|
621
|
+
resource.method === 'GET' || resource.method === 'HEAD'
|
|
622
|
+
? undefined
|
|
623
|
+
: await resource.blob(),
|
|
624
|
+
cache: resource.cache,
|
|
625
|
+
credentials: resource.credentials,
|
|
626
|
+
headers: resource.headers,
|
|
627
|
+
integrity: resource.integrity,
|
|
628
|
+
keepalive: resource.keepalive,
|
|
629
|
+
method: resource.method,
|
|
630
|
+
mode: resource.mode,
|
|
631
|
+
redirect: resource.redirect,
|
|
632
|
+
referrer: resource.referrer,
|
|
633
|
+
referrerPolicy: resource.referrerPolicy,
|
|
634
|
+
signal: resource.signal,
|
|
635
|
+
...init
|
|
636
|
+
};
|
|
637
|
+
} else {
|
|
638
|
+
requested = resource;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// we must fixup relative urls so they are resolved from the target page
|
|
642
|
+
const resolved = new URL(requested, url);
|
|
643
|
+
if (is_tracking) {
|
|
644
|
+
depends(resolved.href);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// match ssr serialized data url, which is important to find cached responses
|
|
648
|
+
if (resolved.origin === url.origin) {
|
|
649
|
+
requested = resolved.href.slice(url.origin.length);
|
|
650
|
+
}
|
|
632
651
|
|
|
633
|
-
|
|
652
|
+
// prerendered pages may be served from any origin, so `initial_fetch` urls shouldn't be resolved
|
|
653
|
+
return started
|
|
654
|
+
? subsequent_fetch(requested, resolved.href, init)
|
|
655
|
+
: initial_fetch(requested, init);
|
|
656
|
+
},
|
|
657
|
+
setHeaders: () => {}, // noop
|
|
658
|
+
depends,
|
|
659
|
+
parent() {
|
|
660
|
+
if (is_tracking) {
|
|
661
|
+
uses.parent = true;
|
|
662
|
+
}
|
|
663
|
+
return parent();
|
|
664
|
+
},
|
|
665
|
+
untrack(fn) {
|
|
666
|
+
is_tracking = false;
|
|
634
667
|
try {
|
|
635
|
-
|
|
636
|
-
data = (await node.universal.load.call(null, load_input)) ?? null;
|
|
637
|
-
if (data != null && Object.getPrototypeOf(data) !== Object.prototype) {
|
|
638
|
-
throw new Error(
|
|
639
|
-
`a load function related to route '${route.id}' returned ${
|
|
640
|
-
typeof data !== 'object'
|
|
641
|
-
? `a ${typeof data}`
|
|
642
|
-
: data instanceof Response
|
|
643
|
-
? 'a Response object'
|
|
644
|
-
: Array.isArray(data)
|
|
645
|
-
? 'an array'
|
|
646
|
-
: 'a non-plain object'
|
|
647
|
-
}, but must return a plain object at the top level (i.e. \`return {...}\`)`
|
|
648
|
-
);
|
|
649
|
-
}
|
|
668
|
+
return fn();
|
|
650
669
|
} finally {
|
|
651
|
-
|
|
670
|
+
is_tracking = true;
|
|
652
671
|
}
|
|
653
|
-
} else {
|
|
654
|
-
data = (await node.universal.load.call(null, load_input)) ?? null;
|
|
655
672
|
}
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
return {
|
|
659
|
-
node,
|
|
660
|
-
loader,
|
|
661
|
-
server: server_data_node,
|
|
662
|
-
universal: node.universal?.load ? { type: 'data', data, uses } : null,
|
|
663
|
-
data: data ?? server_data_node?.data ?? null,
|
|
664
|
-
// if `paths.base === '/a/b/c`, then the root route is always `/a/b/c/`, regardless of
|
|
665
|
-
// the `trailingSlash` route option, so that relative paths to JS and CSS work
|
|
666
|
-
slash:
|
|
667
|
-
base && (url.pathname === base || url.pathname === base + '/')
|
|
668
|
-
? 'always'
|
|
669
|
-
: node.universal?.trailingSlash ?? server_data_node?.slash
|
|
670
673
|
};
|
|
671
|
-
}
|
|
672
674
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
if (uses.url && url_changed) return true;
|
|
696
|
-
|
|
697
|
-
for (const tracked_params of uses.search_params) {
|
|
698
|
-
if (search_params_changed.has(tracked_params)) return true;
|
|
675
|
+
if (DEV) {
|
|
676
|
+
try {
|
|
677
|
+
lock_fetch();
|
|
678
|
+
data = (await node.universal.load.call(null, load_input)) ?? null;
|
|
679
|
+
if (data != null && Object.getPrototypeOf(data) !== Object.prototype) {
|
|
680
|
+
throw new Error(
|
|
681
|
+
`a load function related to route '${route.id}' returned ${
|
|
682
|
+
typeof data !== 'object'
|
|
683
|
+
? `a ${typeof data}`
|
|
684
|
+
: data instanceof Response
|
|
685
|
+
? 'a Response object'
|
|
686
|
+
: Array.isArray(data)
|
|
687
|
+
? 'an array'
|
|
688
|
+
: 'a non-plain object'
|
|
689
|
+
}, but must return a plain object at the top level (i.e. \`return {...}\`)`
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
} finally {
|
|
693
|
+
unlock_fetch();
|
|
694
|
+
}
|
|
695
|
+
} else {
|
|
696
|
+
data = (await node.universal.load.call(null, load_input)) ?? null;
|
|
699
697
|
}
|
|
698
|
+
}
|
|
700
699
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
700
|
+
return {
|
|
701
|
+
node,
|
|
702
|
+
loader,
|
|
703
|
+
server: server_data_node,
|
|
704
|
+
universal: node.universal?.load ? { type: 'data', data, uses } : null,
|
|
705
|
+
data: data ?? server_data_node?.data ?? null,
|
|
706
|
+
slash: node.universal?.trailingSlash ?? server_data_node?.slash
|
|
707
|
+
};
|
|
708
|
+
}
|
|
704
709
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
710
|
+
/**
|
|
711
|
+
* @param {boolean} parent_changed
|
|
712
|
+
* @param {boolean} route_changed
|
|
713
|
+
* @param {boolean} url_changed
|
|
714
|
+
* @param {Set<string>} search_params_changed
|
|
715
|
+
* @param {import('types').Uses | undefined} uses
|
|
716
|
+
* @param {Record<string, string>} params
|
|
717
|
+
*/
|
|
718
|
+
function has_changed(
|
|
719
|
+
parent_changed,
|
|
720
|
+
route_changed,
|
|
721
|
+
url_changed,
|
|
722
|
+
search_params_changed,
|
|
723
|
+
uses,
|
|
724
|
+
params
|
|
725
|
+
) {
|
|
726
|
+
if (force_invalidation) return true;
|
|
727
|
+
|
|
728
|
+
if (!uses) return false;
|
|
729
|
+
|
|
730
|
+
if (uses.parent && parent_changed) return true;
|
|
731
|
+
if (uses.route && route_changed) return true;
|
|
732
|
+
if (uses.url && url_changed) return true;
|
|
733
|
+
|
|
734
|
+
for (const tracked_params of uses.search_params) {
|
|
735
|
+
if (search_params_changed.has(tracked_params)) return true;
|
|
736
|
+
}
|
|
708
737
|
|
|
709
|
-
|
|
738
|
+
for (const param of uses.params) {
|
|
739
|
+
if (params[param] !== current.params[param]) return true;
|
|
710
740
|
}
|
|
711
741
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
* @param {import('./types.js').DataNode | null} [previous]
|
|
715
|
-
* @returns {import('./types.js').DataNode | null}
|
|
716
|
-
*/
|
|
717
|
-
function create_data_node(node, previous) {
|
|
718
|
-
if (node?.type === 'data') return node;
|
|
719
|
-
if (node?.type === 'skip') return previous ?? null;
|
|
720
|
-
return null;
|
|
742
|
+
for (const href of uses.dependencies) {
|
|
743
|
+
if (invalidated.some((fn) => fn(new URL(href)))) return true;
|
|
721
744
|
}
|
|
722
745
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
* @param {URL | null} old_url
|
|
726
|
-
* @param {URL} new_url
|
|
727
|
-
*/
|
|
728
|
-
function diff_search_params(old_url, new_url) {
|
|
729
|
-
if (!old_url) return new Set(new_url.searchParams.keys());
|
|
746
|
+
return false;
|
|
747
|
+
}
|
|
730
748
|
|
|
731
|
-
|
|
749
|
+
/**
|
|
750
|
+
* @param {import('types').ServerDataNode | import('types').ServerDataSkippedNode | null} node
|
|
751
|
+
* @param {import('./types.js').DataNode | null} [previous]
|
|
752
|
+
* @returns {import('./types.js').DataNode | null}
|
|
753
|
+
*/
|
|
754
|
+
function create_data_node(node, previous) {
|
|
755
|
+
if (node?.type === 'data') return node;
|
|
756
|
+
if (node?.type === 'skip') return previous ?? null;
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
732
759
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
760
|
+
/**
|
|
761
|
+
*
|
|
762
|
+
* @param {URL | null} old_url
|
|
763
|
+
* @param {URL} new_url
|
|
764
|
+
*/
|
|
765
|
+
function diff_search_params(old_url, new_url) {
|
|
766
|
+
if (!old_url) return new Set(new_url.searchParams.keys());
|
|
736
767
|
|
|
737
|
-
|
|
738
|
-
old_values.every((value) => new_values.includes(value)) &&
|
|
739
|
-
new_values.every((value) => old_values.includes(value))
|
|
740
|
-
) {
|
|
741
|
-
changed.delete(key);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
768
|
+
const changed = new Set([...old_url.searchParams.keys(), ...new_url.searchParams.keys()]);
|
|
744
769
|
|
|
745
|
-
|
|
746
|
-
|
|
770
|
+
for (const key of changed) {
|
|
771
|
+
const old_values = old_url.searchParams.getAll(key);
|
|
772
|
+
const new_values = new_url.searchParams.getAll(key);
|
|
747
773
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
if (load_cache?.id === id) {
|
|
754
|
-
return load_cache.promise;
|
|
774
|
+
if (
|
|
775
|
+
old_values.every((value) => new_values.includes(value)) &&
|
|
776
|
+
new_values.every((value) => old_values.includes(value))
|
|
777
|
+
) {
|
|
778
|
+
changed.delete(key);
|
|
755
779
|
}
|
|
780
|
+
}
|
|
756
781
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
const loaders = [...layouts, leaf];
|
|
760
|
-
|
|
761
|
-
// preload modules to avoid waterfall, but handle rejections
|
|
762
|
-
// so they don't get reported to Sentry et al (we don't need
|
|
763
|
-
// to act on the failures at this point)
|
|
764
|
-
errors.forEach((loader) => loader?.().catch(() => {}));
|
|
765
|
-
loaders.forEach((loader) => loader?.[1]().catch(() => {}));
|
|
766
|
-
|
|
767
|
-
/** @type {import('types').ServerNodesResponse | import('types').ServerRedirectNode | null} */
|
|
768
|
-
let server_data = null;
|
|
769
|
-
const url_changed = current.url ? id !== current.url.pathname + current.url.search : false;
|
|
770
|
-
const route_changed = current.route ? route.id !== current.route.id : false;
|
|
771
|
-
const search_params_changed = diff_search_params(current.url, url);
|
|
772
|
-
|
|
773
|
-
let parent_invalid = false;
|
|
774
|
-
const invalid_server_nodes = loaders.map((loader, i) => {
|
|
775
|
-
const previous = current.branch[i];
|
|
776
|
-
|
|
777
|
-
const invalid =
|
|
778
|
-
!!loader?.[0] &&
|
|
779
|
-
(previous?.loader !== loader[1] ||
|
|
780
|
-
has_changed(
|
|
781
|
-
parent_invalid,
|
|
782
|
-
route_changed,
|
|
783
|
-
url_changed,
|
|
784
|
-
search_params_changed,
|
|
785
|
-
previous.server?.uses,
|
|
786
|
-
params
|
|
787
|
-
));
|
|
788
|
-
|
|
789
|
-
if (invalid) {
|
|
790
|
-
// For the next one
|
|
791
|
-
parent_invalid = true;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
return invalid;
|
|
795
|
-
});
|
|
796
|
-
|
|
797
|
-
if (invalid_server_nodes.some(Boolean)) {
|
|
798
|
-
try {
|
|
799
|
-
server_data = await load_data(url, invalid_server_nodes);
|
|
800
|
-
} catch (error) {
|
|
801
|
-
return load_root_error_page({
|
|
802
|
-
status: get_status(error),
|
|
803
|
-
error: await handle_error(error, { url, params, route: { id: route.id } }),
|
|
804
|
-
url,
|
|
805
|
-
route
|
|
806
|
-
});
|
|
807
|
-
}
|
|
782
|
+
return changed;
|
|
783
|
+
}
|
|
808
784
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
785
|
+
/**
|
|
786
|
+
* @param {import('./types.js').NavigationIntent} intent
|
|
787
|
+
* @returns {Promise<import('./types.js').NavigationResult>}
|
|
788
|
+
*/
|
|
789
|
+
async function load_route({ id, invalidating, url, params, route }) {
|
|
790
|
+
if (load_cache?.id === id) {
|
|
791
|
+
return load_cache.promise;
|
|
792
|
+
}
|
|
813
793
|
|
|
814
|
-
|
|
794
|
+
const { errors, layouts, leaf } = route;
|
|
815
795
|
|
|
816
|
-
|
|
796
|
+
const loaders = [...layouts, leaf];
|
|
817
797
|
|
|
818
|
-
|
|
819
|
-
|
|
798
|
+
// preload modules to avoid waterfall, but handle rejections
|
|
799
|
+
// so they don't get reported to Sentry et al (we don't need
|
|
800
|
+
// to act on the failures at this point)
|
|
801
|
+
errors.forEach((loader) => loader?.().catch(() => {}));
|
|
802
|
+
loaders.forEach((loader) => loader?.[1]().catch(() => {}));
|
|
820
803
|
|
|
821
|
-
|
|
822
|
-
|
|
804
|
+
/** @type {import('types').ServerNodesResponse | import('types').ServerRedirectNode | null} */
|
|
805
|
+
let server_data = null;
|
|
806
|
+
const url_changed = current.url ? id !== current.url.pathname + current.url.search : false;
|
|
807
|
+
const route_changed = current.route ? route.id !== current.route.id : false;
|
|
808
|
+
const search_params_changed = diff_search_params(current.url, url);
|
|
823
809
|
|
|
824
|
-
|
|
810
|
+
let parent_invalid = false;
|
|
811
|
+
const invalid_server_nodes = loaders.map((loader, i) => {
|
|
812
|
+
const previous = current.branch[i];
|
|
825
813
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
parent_changed,
|
|
814
|
+
const invalid =
|
|
815
|
+
!!loader?.[0] &&
|
|
816
|
+
(previous?.loader !== loader[1] ||
|
|
817
|
+
has_changed(
|
|
818
|
+
parent_invalid,
|
|
832
819
|
route_changed,
|
|
833
820
|
url_changed,
|
|
834
821
|
search_params_changed,
|
|
835
|
-
previous.
|
|
822
|
+
previous.server?.uses,
|
|
836
823
|
params
|
|
837
|
-
);
|
|
838
|
-
if (valid) return previous;
|
|
824
|
+
));
|
|
839
825
|
|
|
840
|
-
|
|
826
|
+
if (invalid) {
|
|
827
|
+
// For the next one
|
|
828
|
+
parent_invalid = true;
|
|
829
|
+
}
|
|
841
830
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
throw server_data_node;
|
|
845
|
-
}
|
|
831
|
+
return invalid;
|
|
832
|
+
});
|
|
846
833
|
|
|
847
|
-
|
|
848
|
-
|
|
834
|
+
if (invalid_server_nodes.some(Boolean)) {
|
|
835
|
+
try {
|
|
836
|
+
server_data = await load_data(url, invalid_server_nodes);
|
|
837
|
+
} catch (error) {
|
|
838
|
+
return load_root_error_page({
|
|
839
|
+
status: get_status(error),
|
|
840
|
+
error: await handle_error(error, { url, params, route: { id: route.id } }),
|
|
849
841
|
url,
|
|
850
|
-
|
|
851
|
-
route,
|
|
852
|
-
parent: async () => {
|
|
853
|
-
const data = {};
|
|
854
|
-
for (let j = 0; j < i; j += 1) {
|
|
855
|
-
Object.assign(data, (await branch_promises[j])?.data);
|
|
856
|
-
}
|
|
857
|
-
return data;
|
|
858
|
-
},
|
|
859
|
-
server_data_node: create_data_node(
|
|
860
|
-
// server_data_node is undefined if it wasn't reloaded from the server;
|
|
861
|
-
// and if current loader uses server data, we want to reuse previous data.
|
|
862
|
-
server_data_node === undefined && loader[0] ? { type: 'skip' } : server_data_node ?? null,
|
|
863
|
-
loader[0] ? previous?.server : undefined
|
|
864
|
-
)
|
|
842
|
+
route
|
|
865
843
|
});
|
|
866
|
-
}
|
|
844
|
+
}
|
|
867
845
|
|
|
868
|
-
|
|
869
|
-
|
|
846
|
+
if (server_data.type === 'redirect') {
|
|
847
|
+
return server_data;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
870
850
|
|
|
871
|
-
|
|
872
|
-
const branch = [];
|
|
851
|
+
const server_data_nodes = server_data?.nodes;
|
|
873
852
|
|
|
874
|
-
|
|
875
|
-
if (loaders[i]) {
|
|
876
|
-
try {
|
|
877
|
-
branch.push(await branch_promises[i]);
|
|
878
|
-
} catch (err) {
|
|
879
|
-
if (err instanceof Redirect) {
|
|
880
|
-
return {
|
|
881
|
-
type: 'redirect',
|
|
882
|
-
location: err.location
|
|
883
|
-
};
|
|
884
|
-
}
|
|
853
|
+
let parent_changed = false;
|
|
885
854
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
let error;
|
|
889
|
-
|
|
890
|
-
if (server_data_nodes?.includes(/** @type {import('types').ServerErrorNode} */ (err))) {
|
|
891
|
-
// this is the server error rethrown above, reconstruct but don't invoke
|
|
892
|
-
// the client error handler; it should've already been handled on the server
|
|
893
|
-
status = /** @type {import('types').ServerErrorNode} */ (err).status ?? status;
|
|
894
|
-
error = /** @type {import('types').ServerErrorNode} */ (err).error;
|
|
895
|
-
} else if (err instanceof HttpError) {
|
|
896
|
-
error = err.body;
|
|
897
|
-
} else {
|
|
898
|
-
// Referenced node could have been removed due to redeploy, check
|
|
899
|
-
const updated = await stores.updated.check();
|
|
900
|
-
if (updated) {
|
|
901
|
-
return await native_navigation(url);
|
|
902
|
-
}
|
|
855
|
+
const branch_promises = loaders.map(async (loader, i) => {
|
|
856
|
+
if (!loader) return;
|
|
903
857
|
|
|
904
|
-
|
|
905
|
-
|
|
858
|
+
/** @type {import('./types.js').BranchNode | undefined} */
|
|
859
|
+
const previous = current.branch[i];
|
|
906
860
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
861
|
+
const server_data_node = server_data_nodes?.[i];
|
|
862
|
+
|
|
863
|
+
// re-use data from previous load if it's still valid
|
|
864
|
+
const valid =
|
|
865
|
+
(!server_data_node || server_data_node.type === 'skip') &&
|
|
866
|
+
loader[1] === previous?.loader &&
|
|
867
|
+
!has_changed(
|
|
868
|
+
parent_changed,
|
|
869
|
+
route_changed,
|
|
870
|
+
url_changed,
|
|
871
|
+
search_params_changed,
|
|
872
|
+
previous.universal?.uses,
|
|
873
|
+
params
|
|
874
|
+
);
|
|
875
|
+
if (valid) return previous;
|
|
876
|
+
|
|
877
|
+
parent_changed = true;
|
|
878
|
+
|
|
879
|
+
if (server_data_node?.type === 'error') {
|
|
880
|
+
// rethrow and catch below
|
|
881
|
+
throw server_data_node;
|
|
928
882
|
}
|
|
929
883
|
|
|
930
|
-
return
|
|
884
|
+
return load_node({
|
|
885
|
+
loader: loader[1],
|
|
931
886
|
url,
|
|
932
887
|
params,
|
|
933
|
-
branch,
|
|
934
|
-
status: 200,
|
|
935
|
-
error: null,
|
|
936
888
|
route,
|
|
937
|
-
|
|
938
|
-
|
|
889
|
+
parent: async () => {
|
|
890
|
+
const data = {};
|
|
891
|
+
for (let j = 0; j < i; j += 1) {
|
|
892
|
+
Object.assign(data, (await branch_promises[j])?.data);
|
|
893
|
+
}
|
|
894
|
+
return data;
|
|
895
|
+
},
|
|
896
|
+
server_data_node: create_data_node(
|
|
897
|
+
// server_data_node is undefined if it wasn't reloaded from the server;
|
|
898
|
+
// and if current loader uses server data, we want to reuse previous data.
|
|
899
|
+
server_data_node === undefined && loader[0] ? { type: 'skip' } : server_data_node ?? null,
|
|
900
|
+
loader[0] ? previous?.server : undefined
|
|
901
|
+
)
|
|
939
902
|
});
|
|
940
|
-
}
|
|
903
|
+
});
|
|
941
904
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
905
|
+
// if we don't do this, rejections will be unhandled
|
|
906
|
+
for (const p of branch_promises) p.catch(() => {});
|
|
907
|
+
|
|
908
|
+
/** @type {Array<import('./types.js').BranchNode | undefined>} */
|
|
909
|
+
const branch = [];
|
|
910
|
+
|
|
911
|
+
for (let i = 0; i < loaders.length; i += 1) {
|
|
912
|
+
if (loaders[i]) {
|
|
913
|
+
try {
|
|
914
|
+
branch.push(await branch_promises[i]);
|
|
915
|
+
} catch (err) {
|
|
916
|
+
if (err instanceof Redirect) {
|
|
954
917
|
return {
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
node: await /** @type {import('types').CSRPageNodeLoader } */ (errors[i])(),
|
|
958
|
-
loader: /** @type {import('types').CSRPageNodeLoader } */ (errors[i]),
|
|
959
|
-
data: {},
|
|
960
|
-
server: null,
|
|
961
|
-
universal: null
|
|
962
|
-
}
|
|
918
|
+
type: 'redirect',
|
|
919
|
+
location: err.location
|
|
963
920
|
};
|
|
964
|
-
}
|
|
965
|
-
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
let status = get_status(err);
|
|
924
|
+
/** @type {App.Error} */
|
|
925
|
+
let error;
|
|
926
|
+
|
|
927
|
+
if (server_data_nodes?.includes(/** @type {import('types').ServerErrorNode} */ (err))) {
|
|
928
|
+
// this is the server error rethrown above, reconstruct but don't invoke
|
|
929
|
+
// the client error handler; it should've already been handled on the server
|
|
930
|
+
status = /** @type {import('types').ServerErrorNode} */ (err).status ?? status;
|
|
931
|
+
error = /** @type {import('types').ServerErrorNode} */ (err).error;
|
|
932
|
+
} else if (err instanceof HttpError) {
|
|
933
|
+
error = err.body;
|
|
934
|
+
} else {
|
|
935
|
+
// Referenced node could have been removed due to redeploy, check
|
|
936
|
+
const updated = await stores.updated.check();
|
|
937
|
+
if (updated) {
|
|
938
|
+
return await native_navigation(url);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
error = await handle_error(err, { params, url, route: { id: route.id } });
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
const error_load = await load_nearest_error_page(i, branch, errors);
|
|
945
|
+
if (error_load) {
|
|
946
|
+
return await get_navigation_result_from_branch({
|
|
947
|
+
url,
|
|
948
|
+
params,
|
|
949
|
+
branch: branch.slice(0, error_load.idx).concat(error_load.node),
|
|
950
|
+
status,
|
|
951
|
+
error,
|
|
952
|
+
route
|
|
953
|
+
});
|
|
954
|
+
} else {
|
|
955
|
+
// if we get here, it's because the root `load` function failed,
|
|
956
|
+
// and we need to fall back to the server
|
|
957
|
+
return await server_fallback(url, { id: route.id }, error, status);
|
|
966
958
|
}
|
|
967
959
|
}
|
|
960
|
+
} else {
|
|
961
|
+
// push an empty slot so we can rewind past gaps to the
|
|
962
|
+
// layout that corresponds with an +error.svelte page
|
|
963
|
+
branch.push(undefined);
|
|
968
964
|
}
|
|
969
965
|
}
|
|
970
966
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
const params = {}; // error page does not have params
|
|
967
|
+
return await get_navigation_result_from_branch({
|
|
968
|
+
url,
|
|
969
|
+
params,
|
|
970
|
+
branch,
|
|
971
|
+
status: 200,
|
|
972
|
+
error: null,
|
|
973
|
+
route,
|
|
974
|
+
// Reset `form` on navigation, but not invalidation
|
|
975
|
+
form: invalidating ? undefined : null
|
|
976
|
+
});
|
|
977
|
+
}
|
|
983
978
|
|
|
984
|
-
|
|
985
|
-
|
|
979
|
+
/**
|
|
980
|
+
* @param {number} i Start index to backtrack from
|
|
981
|
+
* @param {Array<import('./types.js').BranchNode | undefined>} branch Branch to backtrack
|
|
982
|
+
* @param {Array<import('types').CSRPageNodeLoader | undefined>} errors All error pages for this branch
|
|
983
|
+
* @returns {Promise<{idx: number; node: import('./types.js').BranchNode} | undefined>}
|
|
984
|
+
*/
|
|
985
|
+
async function load_nearest_error_page(i, branch, errors) {
|
|
986
|
+
while (i--) {
|
|
987
|
+
if (errors[i]) {
|
|
988
|
+
let j = i;
|
|
989
|
+
while (!branch[j]) j -= 1;
|
|
990
|
+
try {
|
|
991
|
+
return {
|
|
992
|
+
idx: j + 1,
|
|
993
|
+
node: {
|
|
994
|
+
node: await /** @type {import('types').CSRPageNodeLoader } */ (errors[i])(),
|
|
995
|
+
loader: /** @type {import('types').CSRPageNodeLoader } */ (errors[i]),
|
|
996
|
+
data: {},
|
|
997
|
+
server: null,
|
|
998
|
+
universal: null
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
} catch (e) {
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* @param {{
|
|
1010
|
+
* status: number;
|
|
1011
|
+
* error: App.Error;
|
|
1012
|
+
* url: URL;
|
|
1013
|
+
* route: { id: string | null }
|
|
1014
|
+
* }} opts
|
|
1015
|
+
* @returns {Promise<import('./types.js').NavigationFinished>}
|
|
1016
|
+
*/
|
|
1017
|
+
async function load_root_error_page({ status, error, url, route }) {
|
|
1018
|
+
/** @type {Record<string, string>} */
|
|
1019
|
+
const params = {}; // error page does not have params
|
|
986
1020
|
|
|
987
|
-
|
|
1021
|
+
/** @type {import('types').ServerDataNode | null} */
|
|
1022
|
+
let server_data_node = null;
|
|
988
1023
|
|
|
989
|
-
|
|
990
|
-
// TODO post-https://github.com/sveltejs/kit/discussions/6124 we can use
|
|
991
|
-
// existing root layout data
|
|
992
|
-
try {
|
|
993
|
-
const server_data = await load_data(url, [true]);
|
|
1024
|
+
const default_layout_has_server_load = app.server_loads[0] === 0;
|
|
994
1025
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
}
|
|
1026
|
+
if (default_layout_has_server_load) {
|
|
1027
|
+
// TODO post-https://github.com/sveltejs/kit/discussions/6124 we can use
|
|
1028
|
+
// existing root layout data
|
|
1029
|
+
try {
|
|
1030
|
+
const server_data = await load_data(url, [true]);
|
|
1001
1031
|
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1032
|
+
if (
|
|
1033
|
+
server_data.type !== 'data' ||
|
|
1034
|
+
(server_data.nodes[0] && server_data.nodes[0].type !== 'data')
|
|
1035
|
+
) {
|
|
1036
|
+
throw 0;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
server_data_node = server_data.nodes[0] ?? null;
|
|
1040
|
+
} catch {
|
|
1041
|
+
// at this point we have no choice but to fall back to the server, if it wouldn't
|
|
1042
|
+
// bring us right back here, turning this into an endless loop
|
|
1043
|
+
if (url.origin !== origin || url.pathname !== location.pathname || hydrated) {
|
|
1044
|
+
await native_navigation(url);
|
|
1009
1045
|
}
|
|
1010
1046
|
}
|
|
1047
|
+
}
|
|
1011
1048
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1049
|
+
const root_layout = await load_node({
|
|
1050
|
+
loader: default_layout_loader,
|
|
1051
|
+
url,
|
|
1052
|
+
params,
|
|
1053
|
+
route,
|
|
1054
|
+
parent: () => Promise.resolve({}),
|
|
1055
|
+
server_data_node: create_data_node(server_data_node)
|
|
1056
|
+
});
|
|
1020
1057
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1058
|
+
/** @type {import('./types.js').BranchNode} */
|
|
1059
|
+
const root_error = {
|
|
1060
|
+
node: await default_error_loader(),
|
|
1061
|
+
loader: default_error_loader,
|
|
1062
|
+
universal: null,
|
|
1063
|
+
server: null,
|
|
1064
|
+
data: null
|
|
1065
|
+
};
|
|
1029
1066
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1067
|
+
return await get_navigation_result_from_branch({
|
|
1068
|
+
url,
|
|
1069
|
+
params,
|
|
1070
|
+
branch: [root_layout, root_error],
|
|
1071
|
+
status,
|
|
1072
|
+
error,
|
|
1073
|
+
route: null
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1039
1076
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1077
|
+
/**
|
|
1078
|
+
* @param {URL} url
|
|
1079
|
+
* @param {boolean} invalidating
|
|
1080
|
+
*/
|
|
1081
|
+
function get_navigation_intent(url, invalidating) {
|
|
1082
|
+
if (is_external_url(url, base)) return;
|
|
1046
1083
|
|
|
1047
|
-
|
|
1084
|
+
const path = get_url_path(url.pathname);
|
|
1048
1085
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1086
|
+
for (const route of routes) {
|
|
1087
|
+
const params = route.exec(path);
|
|
1051
1088
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
}
|
|
1089
|
+
if (params) {
|
|
1090
|
+
const id = url.pathname + url.search;
|
|
1091
|
+
/** @type {import('./types.js').NavigationIntent} */
|
|
1092
|
+
const intent = { id, invalidating, route, params: decode_params(params), url };
|
|
1093
|
+
return intent;
|
|
1058
1094
|
}
|
|
1059
1095
|
}
|
|
1096
|
+
}
|
|
1060
1097
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1098
|
+
/** @param {string} pathname */
|
|
1099
|
+
function get_url_path(pathname) {
|
|
1100
|
+
return decode_pathname(pathname.slice(base.length) || '/');
|
|
1101
|
+
}
|
|
1065
1102
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1103
|
+
/**
|
|
1104
|
+
* @param {{
|
|
1105
|
+
* url: URL;
|
|
1106
|
+
* type: import('@sveltejs/kit').Navigation["type"];
|
|
1107
|
+
* intent?: import('./types.js').NavigationIntent;
|
|
1108
|
+
* delta?: number;
|
|
1109
|
+
* }} opts
|
|
1110
|
+
*/
|
|
1111
|
+
function _before_navigate({ url, type, intent, delta }) {
|
|
1112
|
+
let should_block = false;
|
|
1076
1113
|
|
|
1077
|
-
|
|
1114
|
+
const nav = create_navigation(current, intent, url, type);
|
|
1078
1115
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1116
|
+
if (delta !== undefined) {
|
|
1117
|
+
nav.navigation.delta = delta;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
const cancellable = {
|
|
1121
|
+
...nav.navigation,
|
|
1122
|
+
cancel: () => {
|
|
1123
|
+
should_block = true;
|
|
1124
|
+
nav.reject(new Error('navigation cancelled'));
|
|
1081
1125
|
}
|
|
1126
|
+
};
|
|
1082
1127
|
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
nav.reject(new Error('navigation was cancelled'));
|
|
1088
|
-
}
|
|
1089
|
-
};
|
|
1128
|
+
if (!navigating) {
|
|
1129
|
+
// Don't run the event during redirects
|
|
1130
|
+
before_navigate_callbacks.forEach((fn) => fn(cancellable));
|
|
1131
|
+
}
|
|
1090
1132
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
callbacks.before_navigate.forEach((fn) => fn(cancellable));
|
|
1094
|
-
}
|
|
1133
|
+
return should_block ? null : nav;
|
|
1134
|
+
}
|
|
1095
1135
|
|
|
1096
|
-
|
|
1136
|
+
/**
|
|
1137
|
+
* @param {{
|
|
1138
|
+
* type: import('@sveltejs/kit').Navigation["type"];
|
|
1139
|
+
* url: URL;
|
|
1140
|
+
* popped?: {
|
|
1141
|
+
* state: Record<string, any>;
|
|
1142
|
+
* scroll: { x: number, y: number };
|
|
1143
|
+
* delta: number;
|
|
1144
|
+
* };
|
|
1145
|
+
* keepfocus?: boolean;
|
|
1146
|
+
* noscroll?: boolean;
|
|
1147
|
+
* replace_state?: boolean;
|
|
1148
|
+
* state?: Record<string, any>;
|
|
1149
|
+
* redirect_count?: number;
|
|
1150
|
+
* nav_token?: {};
|
|
1151
|
+
* accept?: () => void;
|
|
1152
|
+
* block?: () => void;
|
|
1153
|
+
* }} opts
|
|
1154
|
+
*/
|
|
1155
|
+
async function navigate({
|
|
1156
|
+
type,
|
|
1157
|
+
url,
|
|
1158
|
+
popped,
|
|
1159
|
+
keepfocus,
|
|
1160
|
+
noscroll,
|
|
1161
|
+
replace_state,
|
|
1162
|
+
state = {},
|
|
1163
|
+
redirect_count = 0,
|
|
1164
|
+
nav_token = {},
|
|
1165
|
+
accept = noop,
|
|
1166
|
+
block = noop
|
|
1167
|
+
}) {
|
|
1168
|
+
const intent = get_navigation_intent(url, false);
|
|
1169
|
+
const nav = _before_navigate({ url, type, delta: popped?.delta, intent });
|
|
1170
|
+
|
|
1171
|
+
if (!nav) {
|
|
1172
|
+
block();
|
|
1173
|
+
return;
|
|
1097
1174
|
}
|
|
1098
1175
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
* state: Record<string, any>;
|
|
1105
|
-
* scroll: { x: number, y: number };
|
|
1106
|
-
* delta: number;
|
|
1107
|
-
* };
|
|
1108
|
-
* keepfocus?: boolean;
|
|
1109
|
-
* noscroll?: boolean;
|
|
1110
|
-
* replace_state?: boolean;
|
|
1111
|
-
* state?: Record<string, any>;
|
|
1112
|
-
* redirect_count?: number;
|
|
1113
|
-
* nav_token?: {};
|
|
1114
|
-
* accept?: () => void;
|
|
1115
|
-
* block?: () => void;
|
|
1116
|
-
* }} opts
|
|
1117
|
-
*/
|
|
1118
|
-
async function navigate({
|
|
1119
|
-
type,
|
|
1120
|
-
url,
|
|
1121
|
-
popped,
|
|
1122
|
-
keepfocus,
|
|
1123
|
-
noscroll,
|
|
1124
|
-
replace_state,
|
|
1125
|
-
state = {},
|
|
1126
|
-
redirect_count = 0,
|
|
1127
|
-
nav_token = {},
|
|
1128
|
-
accept = noop,
|
|
1129
|
-
block = noop
|
|
1130
|
-
}) {
|
|
1131
|
-
const intent = get_navigation_intent(url, false);
|
|
1132
|
-
const nav = before_navigate({ url, type, delta: popped?.delta, intent });
|
|
1133
|
-
|
|
1134
|
-
if (!nav) {
|
|
1135
|
-
block();
|
|
1136
|
-
return;
|
|
1137
|
-
}
|
|
1176
|
+
// store this before calling `accept()`, which may change the index
|
|
1177
|
+
const previous_history_index = current_history_index;
|
|
1178
|
+
const previous_navigation_index = current_navigation_index;
|
|
1179
|
+
|
|
1180
|
+
accept();
|
|
1138
1181
|
|
|
1139
|
-
|
|
1140
|
-
const previous_history_index = current_history_index;
|
|
1141
|
-
const previous_navigation_index = current_navigation_index;
|
|
1182
|
+
navigating = true;
|
|
1142
1183
|
|
|
1143
|
-
|
|
1184
|
+
if (started) {
|
|
1185
|
+
stores.navigating.set(nav.navigation);
|
|
1186
|
+
}
|
|
1144
1187
|
|
|
1145
|
-
|
|
1188
|
+
token = nav_token;
|
|
1189
|
+
let navigation_result = intent && (await load_route(intent));
|
|
1146
1190
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1191
|
+
if (!navigation_result) {
|
|
1192
|
+
if (is_external_url(url, base)) {
|
|
1193
|
+
return await native_navigation(url);
|
|
1149
1194
|
}
|
|
1195
|
+
navigation_result = await server_fallback(
|
|
1196
|
+
url,
|
|
1197
|
+
{ id: null },
|
|
1198
|
+
await handle_error(new SvelteKitError(404, 'Not Found', `Not found: ${url.pathname}`), {
|
|
1199
|
+
url,
|
|
1200
|
+
params: {},
|
|
1201
|
+
route: { id: null }
|
|
1202
|
+
}),
|
|
1203
|
+
404
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1150
1206
|
|
|
1151
|
-
|
|
1152
|
-
|
|
1207
|
+
// if this is an internal navigation intent, use the normalized
|
|
1208
|
+
// URL for the rest of the function
|
|
1209
|
+
url = intent?.url || url;
|
|
1153
1210
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1211
|
+
// abort if user navigated during update
|
|
1212
|
+
if (token !== nav_token) {
|
|
1213
|
+
nav.reject(new Error('navigation aborted'));
|
|
1214
|
+
return false;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
if (navigation_result.type === 'redirect') {
|
|
1218
|
+
// whatwg fetch spec https://fetch.spec.whatwg.org/#http-redirect-fetch says to error after 20 redirects
|
|
1219
|
+
if (redirect_count >= 20) {
|
|
1220
|
+
navigation_result = await load_root_error_page({
|
|
1221
|
+
status: 500,
|
|
1222
|
+
error: await handle_error(new Error('Redirect loop'), {
|
|
1162
1223
|
url,
|
|
1163
1224
|
params: {},
|
|
1164
1225
|
route: { id: null }
|
|
1165
1226
|
}),
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
// URL for the rest of the function
|
|
1172
|
-
url = intent?.url || url;
|
|
1173
|
-
|
|
1174
|
-
// abort if user navigated during update
|
|
1175
|
-
if (token !== nav_token) {
|
|
1176
|
-
nav.reject(new Error('navigation was aborted'));
|
|
1227
|
+
url,
|
|
1228
|
+
route: { id: null }
|
|
1229
|
+
});
|
|
1230
|
+
} else {
|
|
1231
|
+
_goto(new URL(navigation_result.location, url).href, {}, redirect_count + 1, nav_token);
|
|
1177
1232
|
return false;
|
|
1178
1233
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
navigation_result = await load_root_error_page({
|
|
1184
|
-
status: 500,
|
|
1185
|
-
error: await handle_error(new Error('Redirect loop'), {
|
|
1186
|
-
url,
|
|
1187
|
-
params: {},
|
|
1188
|
-
route: { id: null }
|
|
1189
|
-
}),
|
|
1190
|
-
url,
|
|
1191
|
-
route: { id: null }
|
|
1192
|
-
});
|
|
1193
|
-
} else {
|
|
1194
|
-
goto(new URL(navigation_result.location, url).href, {}, redirect_count + 1, nav_token);
|
|
1195
|
-
return false;
|
|
1196
|
-
}
|
|
1197
|
-
} else if (/** @type {number} */ (navigation_result.props.page.status) >= 400) {
|
|
1198
|
-
const updated = await stores.updated.check();
|
|
1199
|
-
if (updated) {
|
|
1200
|
-
await native_navigation(url);
|
|
1201
|
-
}
|
|
1234
|
+
} else if (/** @type {number} */ (navigation_result.props.page.status) >= 400) {
|
|
1235
|
+
const updated = await stores.updated.check();
|
|
1236
|
+
if (updated) {
|
|
1237
|
+
await native_navigation(url);
|
|
1202
1238
|
}
|
|
1239
|
+
}
|
|
1203
1240
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1241
|
+
// reset invalidation only after a finished navigation. If there are redirects or
|
|
1242
|
+
// additional invalidations, they should get the same invalidation treatment
|
|
1243
|
+
invalidated.length = 0;
|
|
1244
|
+
force_invalidation = false;
|
|
1208
1245
|
|
|
1209
|
-
|
|
1246
|
+
updating = true;
|
|
1210
1247
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1248
|
+
update_scroll_positions(previous_history_index);
|
|
1249
|
+
capture_snapshot(previous_navigation_index);
|
|
1213
1250
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1251
|
+
// ensure the url pathname matches the page's trailing slash option
|
|
1252
|
+
if (navigation_result.props.page.url.pathname !== url.pathname) {
|
|
1253
|
+
url.pathname = navigation_result.props.page.url.pathname;
|
|
1254
|
+
}
|
|
1218
1255
|
|
|
1219
|
-
|
|
1256
|
+
state = popped ? popped.state : state;
|
|
1220
1257
|
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1258
|
+
if (!popped) {
|
|
1259
|
+
// this is a new navigation, rather than a popstate
|
|
1260
|
+
const change = replace_state ? 0 : 1;
|
|
1224
1261
|
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1262
|
+
const entry = {
|
|
1263
|
+
[HISTORY_INDEX]: (current_history_index += change),
|
|
1264
|
+
[NAVIGATION_INDEX]: (current_navigation_index += change),
|
|
1265
|
+
[STATES_KEY]: state
|
|
1266
|
+
};
|
|
1229
1267
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1268
|
+
const fn = replace_state ? original_replace_state : original_push_state;
|
|
1269
|
+
fn.call(history, entry, '', url);
|
|
1232
1270
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
}
|
|
1271
|
+
if (!replace_state) {
|
|
1272
|
+
clear_onward_history(current_history_index, current_navigation_index);
|
|
1236
1273
|
}
|
|
1274
|
+
}
|
|
1237
1275
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
// reset preload synchronously after the history state has been set to avoid race conditions
|
|
1241
|
-
load_cache = null;
|
|
1276
|
+
// reset preload synchronously after the history state has been set to avoid race conditions
|
|
1277
|
+
load_cache = null;
|
|
1242
1278
|
|
|
1243
|
-
|
|
1279
|
+
navigation_result.props.page.state = state;
|
|
1244
1280
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1281
|
+
if (started) {
|
|
1282
|
+
current = navigation_result.state;
|
|
1247
1283
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1284
|
+
// reset url before updating page store
|
|
1285
|
+
if (navigation_result.props.page) {
|
|
1286
|
+
navigation_result.props.page.url = url;
|
|
1287
|
+
}
|
|
1252
1288
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
)
|
|
1289
|
+
const after_navigate = (
|
|
1290
|
+
await Promise.all(
|
|
1291
|
+
on_navigate_callbacks.map((fn) =>
|
|
1292
|
+
fn(/** @type {import('@sveltejs/kit').OnNavigate} */ (nav.navigation))
|
|
1258
1293
|
)
|
|
1259
|
-
)
|
|
1260
|
-
|
|
1261
|
-
if (after_navigate.length > 0) {
|
|
1262
|
-
function cleanup() {
|
|
1263
|
-
callbacks.after_navigate = callbacks.after_navigate.filter(
|
|
1264
|
-
// @ts-ignore
|
|
1265
|
-
(fn) => !after_navigate.includes(fn)
|
|
1266
|
-
);
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
after_navigate.push(cleanup);
|
|
1294
|
+
)
|
|
1295
|
+
).filter((value) => typeof value === 'function');
|
|
1270
1296
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1297
|
+
if (after_navigate.length > 0) {
|
|
1298
|
+
function cleanup() {
|
|
1299
|
+
after_navigate_callbacks = after_navigate_callbacks.filter(
|
|
1300
|
+
// @ts-ignore
|
|
1301
|
+
(fn) => !after_navigate.includes(fn)
|
|
1302
|
+
);
|
|
1273
1303
|
}
|
|
1274
1304
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1305
|
+
after_navigate.push(cleanup);
|
|
1306
|
+
|
|
1307
|
+
// @ts-ignore
|
|
1308
|
+
callbacks.after_navigate.push(...after_navigate);
|
|
1279
1309
|
}
|
|
1280
1310
|
|
|
1281
|
-
|
|
1311
|
+
root.$set(navigation_result.props);
|
|
1312
|
+
has_navigated = true;
|
|
1313
|
+
} else {
|
|
1314
|
+
initialize(navigation_result, target);
|
|
1315
|
+
}
|
|
1282
1316
|
|
|
1283
|
-
|
|
1284
|
-
await tick();
|
|
1317
|
+
const { activeElement } = document;
|
|
1285
1318
|
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
if (autoscroll) {
|
|
1290
|
-
const deep_linked =
|
|
1291
|
-
url.hash && document.getElementById(decodeURIComponent(url.hash.slice(1)));
|
|
1292
|
-
if (scroll) {
|
|
1293
|
-
scrollTo(scroll.x, scroll.y);
|
|
1294
|
-
} else if (deep_linked) {
|
|
1295
|
-
// Here we use `scrollIntoView` on the element instead of `scrollTo`
|
|
1296
|
-
// because it natively supports the `scroll-margin` and `scroll-behavior`
|
|
1297
|
-
// CSS properties.
|
|
1298
|
-
deep_linked.scrollIntoView();
|
|
1299
|
-
} else {
|
|
1300
|
-
scrollTo(0, 0);
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1319
|
+
// need to render the DOM before we can scroll to the rendered elements and do focus management
|
|
1320
|
+
await tick();
|
|
1303
1321
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
document.activeElement !== activeElement &&
|
|
1307
|
-
// also refocus when activeElement is body already because the
|
|
1308
|
-
// focus event might not have been fired on it yet
|
|
1309
|
-
document.activeElement !== document.body;
|
|
1322
|
+
// we reset scroll before dealing with focus, to avoid a flash of unscrolled content
|
|
1323
|
+
const scroll = popped ? popped.scroll : noscroll ? scroll_state() : null;
|
|
1310
1324
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1325
|
+
if (autoscroll) {
|
|
1326
|
+
const deep_linked = url.hash && document.getElementById(decodeURIComponent(url.hash.slice(1)));
|
|
1327
|
+
if (scroll) {
|
|
1328
|
+
scrollTo(scroll.x, scroll.y);
|
|
1329
|
+
} else if (deep_linked) {
|
|
1330
|
+
// Here we use `scrollIntoView` on the element instead of `scrollTo`
|
|
1331
|
+
// because it natively supports the `scroll-margin` and `scroll-behavior`
|
|
1332
|
+
// CSS properties.
|
|
1333
|
+
deep_linked.scrollIntoView();
|
|
1334
|
+
} else {
|
|
1335
|
+
scrollTo(0, 0);
|
|
1313
1336
|
}
|
|
1337
|
+
}
|
|
1314
1338
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1339
|
+
const changed_focus =
|
|
1340
|
+
// reset focus only if any manual focus management didn't override it
|
|
1341
|
+
document.activeElement !== activeElement &&
|
|
1342
|
+
// also refocus when activeElement is body already because the
|
|
1343
|
+
// focus event might not have been fired on it yet
|
|
1344
|
+
document.activeElement !== document.body;
|
|
1320
1345
|
|
|
1321
|
-
|
|
1346
|
+
if (!keepfocus && !changed_focus) {
|
|
1347
|
+
reset_focus();
|
|
1348
|
+
}
|
|
1322
1349
|
|
|
1323
|
-
|
|
1324
|
-
restore_snapshot(current_navigation_index);
|
|
1325
|
-
}
|
|
1350
|
+
autoscroll = true;
|
|
1326
1351
|
|
|
1327
|
-
|
|
1352
|
+
if (navigation_result.props.page) {
|
|
1353
|
+
page = navigation_result.props.page;
|
|
1354
|
+
}
|
|
1328
1355
|
|
|
1329
|
-
|
|
1330
|
-
fn(/** @type {import('@sveltejs/kit').AfterNavigate} */ (nav.navigation))
|
|
1331
|
-
);
|
|
1332
|
-
stores.navigating.set(null);
|
|
1356
|
+
navigating = false;
|
|
1333
1357
|
|
|
1334
|
-
|
|
1358
|
+
if (type === 'popstate') {
|
|
1359
|
+
restore_snapshot(current_navigation_index);
|
|
1335
1360
|
}
|
|
1336
1361
|
|
|
1337
|
-
|
|
1338
|
-
* Does a full page reload if it wouldn't result in an endless loop in the SPA case
|
|
1339
|
-
* @param {URL} url
|
|
1340
|
-
* @param {{ id: string | null }} route
|
|
1341
|
-
* @param {App.Error} error
|
|
1342
|
-
* @param {number} status
|
|
1343
|
-
* @returns {Promise<import('./types.js').NavigationFinished>}
|
|
1344
|
-
*/
|
|
1345
|
-
async function server_fallback(url, route, error, status) {
|
|
1346
|
-
if (url.origin === origin && url.pathname === location.pathname && !hydrated) {
|
|
1347
|
-
// We would reload the same page we're currently on, which isn't hydrated,
|
|
1348
|
-
// which means no SSR, which means we would end up in an endless loop
|
|
1349
|
-
return await load_root_error_page({
|
|
1350
|
-
status,
|
|
1351
|
-
error,
|
|
1352
|
-
url,
|
|
1353
|
-
route
|
|
1354
|
-
});
|
|
1355
|
-
}
|
|
1362
|
+
nav.fulfil(undefined);
|
|
1356
1363
|
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
);
|
|
1364
|
+
after_navigate_callbacks.forEach((fn) =>
|
|
1365
|
+
fn(/** @type {import('@sveltejs/kit').AfterNavigate} */ (nav.navigation))
|
|
1366
|
+
);
|
|
1361
1367
|
|
|
1362
|
-
|
|
1363
|
-
}
|
|
1368
|
+
stores.navigating.set(null);
|
|
1364
1369
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1370
|
+
updating = false;
|
|
1371
|
+
}
|
|
1367
1372
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1373
|
+
/**
|
|
1374
|
+
* Does a full page reload if it wouldn't result in an endless loop in the SPA case
|
|
1375
|
+
* @param {URL} url
|
|
1376
|
+
* @param {{ id: string | null }} route
|
|
1377
|
+
* @param {App.Error} error
|
|
1378
|
+
* @param {number} status
|
|
1379
|
+
* @returns {Promise<import('./types.js').NavigationFinished>}
|
|
1380
|
+
*/
|
|
1381
|
+
async function server_fallback(url, route, error, status) {
|
|
1382
|
+
if (url.origin === origin && url.pathname === location.pathname && !hydrated) {
|
|
1383
|
+
// We would reload the same page we're currently on, which isn't hydrated,
|
|
1384
|
+
// which means no SSR, which means we would end up in an endless loop
|
|
1385
|
+
return await load_root_error_page({
|
|
1386
|
+
status,
|
|
1387
|
+
error,
|
|
1388
|
+
url,
|
|
1389
|
+
route
|
|
1371
1390
|
});
|
|
1372
1391
|
}
|
|
1373
1392
|
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1393
|
+
if (DEV && status !== 404) {
|
|
1394
|
+
console.error(
|
|
1395
|
+
'An error occurred while loading the page. This will cause a full page reload. (This message will only appear during development.)'
|
|
1396
|
+
);
|
|
1377
1397
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1398
|
+
debugger; // eslint-disable-line
|
|
1399
|
+
}
|
|
1380
1400
|
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
preload(target, 2);
|
|
1384
|
-
}, 20);
|
|
1385
|
-
});
|
|
1401
|
+
return await native_navigation(url);
|
|
1402
|
+
}
|
|
1386
1403
|
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1404
|
+
if (import.meta.hot) {
|
|
1405
|
+
import.meta.hot.on('vite:beforeUpdate', () => {
|
|
1406
|
+
if (current.error) location.reload();
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1391
1409
|
|
|
1392
|
-
|
|
1393
|
-
|
|
1410
|
+
function setup_preload() {
|
|
1411
|
+
/** @type {NodeJS.Timeout} */
|
|
1412
|
+
let mousemove_timeout;
|
|
1394
1413
|
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
for (const entry of entries) {
|
|
1398
|
-
if (entry.isIntersecting) {
|
|
1399
|
-
preload_code(/** @type {HTMLAnchorElement} */ (entry.target).href);
|
|
1400
|
-
observer.unobserve(entry.target);
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
},
|
|
1404
|
-
{ threshold: 0 }
|
|
1405
|
-
);
|
|
1414
|
+
container.addEventListener('mousemove', (event) => {
|
|
1415
|
+
const target = /** @type {Element} */ (event.target);
|
|
1406
1416
|
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
const a = find_anchor(element, container);
|
|
1413
|
-
if (!a) return;
|
|
1417
|
+
clearTimeout(mousemove_timeout);
|
|
1418
|
+
mousemove_timeout = setTimeout(() => {
|
|
1419
|
+
preload(target, 2);
|
|
1420
|
+
}, 20);
|
|
1421
|
+
});
|
|
1414
1422
|
|
|
1415
|
-
|
|
1416
|
-
|
|
1423
|
+
/** @param {Event} event */
|
|
1424
|
+
function tap(event) {
|
|
1425
|
+
preload(/** @type {Element} */ (event.composedPath()[0]), 1);
|
|
1426
|
+
}
|
|
1417
1427
|
|
|
1418
|
-
|
|
1428
|
+
container.addEventListener('mousedown', tap);
|
|
1429
|
+
container.addEventListener('touchstart', tap, { passive: true });
|
|
1419
1430
|
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1431
|
+
const observer = new IntersectionObserver(
|
|
1432
|
+
(entries) => {
|
|
1433
|
+
for (const entry of entries) {
|
|
1434
|
+
if (entry.isIntersecting) {
|
|
1435
|
+
_preload_code(/** @type {HTMLAnchorElement} */ (entry.target).href);
|
|
1436
|
+
observer.unobserve(entry.target);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
},
|
|
1440
|
+
{ threshold: 0 }
|
|
1441
|
+
);
|
|
1442
|
+
|
|
1443
|
+
/**
|
|
1444
|
+
* @param {Element} element
|
|
1445
|
+
* @param {number} priority
|
|
1446
|
+
*/
|
|
1447
|
+
function preload(element, priority) {
|
|
1448
|
+
const a = find_anchor(element, container);
|
|
1449
|
+
if (!a) return;
|
|
1450
|
+
|
|
1451
|
+
const { url, external, download } = get_link_info(a, base);
|
|
1452
|
+
if (external || download) return;
|
|
1453
|
+
|
|
1454
|
+
const options = get_router_options(a);
|
|
1455
|
+
|
|
1456
|
+
if (!options.reload) {
|
|
1457
|
+
if (priority <= options.preload_data) {
|
|
1458
|
+
const intent = get_navigation_intent(/** @type {URL} */ (url), false);
|
|
1459
|
+
if (intent) {
|
|
1460
|
+
if (DEV) {
|
|
1461
|
+
_preload_data(intent).then((result) => {
|
|
1462
|
+
if (result.type === 'loaded' && result.state.error) {
|
|
1463
|
+
console.warn(
|
|
1464
|
+
`Preloading data for ${intent.url.pathname} failed with the following error: ${result.state.error.message}\n` +
|
|
1465
|
+
'If this error is transient, you can ignore it. Otherwise, consider disabling preloading for this route. ' +
|
|
1466
|
+
'This route was preloaded due to a data-sveltekit-preload-data attribute. ' +
|
|
1467
|
+
'See https://kit.svelte.dev/docs/link-options for more info'
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
});
|
|
1471
|
+
} else {
|
|
1472
|
+
_preload_data(intent);
|
|
1438
1473
|
}
|
|
1439
|
-
} else if (priority <= options.preload_code) {
|
|
1440
|
-
preload_code(/** @type {URL} */ (url).pathname);
|
|
1441
1474
|
}
|
|
1475
|
+
} else if (priority <= options.preload_code) {
|
|
1476
|
+
_preload_code(/** @type {URL} */ (url).pathname);
|
|
1442
1477
|
}
|
|
1443
1478
|
}
|
|
1479
|
+
}
|
|
1444
1480
|
|
|
1445
|
-
|
|
1446
|
-
|
|
1481
|
+
function after_navigate() {
|
|
1482
|
+
observer.disconnect();
|
|
1447
1483
|
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1484
|
+
for (const a of container.querySelectorAll('a')) {
|
|
1485
|
+
const { url, external, download } = get_link_info(a, base);
|
|
1486
|
+
if (external || download) continue;
|
|
1451
1487
|
|
|
1452
|
-
|
|
1453
|
-
|
|
1488
|
+
const options = get_router_options(a);
|
|
1489
|
+
if (options.reload) continue;
|
|
1454
1490
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1491
|
+
if (options.preload_code === PRELOAD_PRIORITIES.viewport) {
|
|
1492
|
+
observer.observe(a);
|
|
1493
|
+
}
|
|
1458
1494
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
}
|
|
1495
|
+
if (options.preload_code === PRELOAD_PRIORITIES.eager) {
|
|
1496
|
+
_preload_code(/** @type {URL} */ (url).pathname);
|
|
1462
1497
|
}
|
|
1463
1498
|
}
|
|
1464
|
-
|
|
1465
|
-
callbacks.after_navigate.push(after_navigate);
|
|
1466
|
-
after_navigate();
|
|
1467
1499
|
}
|
|
1468
1500
|
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
* @returns {import('types').MaybePromise<App.Error>}
|
|
1473
|
-
*/
|
|
1474
|
-
function handle_error(error, event) {
|
|
1475
|
-
if (error instanceof HttpError) {
|
|
1476
|
-
return error.body;
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1479
|
-
if (DEV) {
|
|
1480
|
-
errored = true;
|
|
1481
|
-
console.warn('The next HMR update will cause the page to reload');
|
|
1482
|
-
}
|
|
1501
|
+
after_navigate_callbacks.push(after_navigate);
|
|
1502
|
+
after_navigate();
|
|
1503
|
+
}
|
|
1483
1504
|
|
|
1484
|
-
|
|
1485
|
-
|
|
1505
|
+
/**
|
|
1506
|
+
* @param {unknown} error
|
|
1507
|
+
* @param {import('@sveltejs/kit').NavigationEvent} event
|
|
1508
|
+
* @returns {import('types').MaybePromise<App.Error>}
|
|
1509
|
+
*/
|
|
1510
|
+
function handle_error(error, event) {
|
|
1511
|
+
if (error instanceof HttpError) {
|
|
1512
|
+
return error.body;
|
|
1513
|
+
}
|
|
1486
1514
|
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
);
|
|
1515
|
+
if (DEV) {
|
|
1516
|
+
errored = true;
|
|
1517
|
+
console.warn('The next HMR update will cause the page to reload');
|
|
1490
1518
|
}
|
|
1491
1519
|
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
onMount(() => {
|
|
1495
|
-
callbacks.after_navigate.push(fn);
|
|
1520
|
+
const status = get_status(error);
|
|
1521
|
+
const message = get_message(error);
|
|
1496
1522
|
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
});
|
|
1502
|
-
},
|
|
1523
|
+
return (
|
|
1524
|
+
app.hooks.handleError({ error, event, status, message }) ?? /** @type {any} */ ({ message })
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1503
1527
|
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1528
|
+
/**
|
|
1529
|
+
* @template {Function} T
|
|
1530
|
+
* @param {T[]} callbacks
|
|
1531
|
+
* @param {T} callback
|
|
1532
|
+
*/
|
|
1533
|
+
function add_navigation_callback(callbacks, callback) {
|
|
1534
|
+
onMount(() => {
|
|
1535
|
+
callbacks.push(callback);
|
|
1507
1536
|
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1537
|
+
return () => {
|
|
1538
|
+
const i = callbacks.indexOf(callback);
|
|
1539
|
+
callbacks.splice(i, 1);
|
|
1540
|
+
};
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1514
1543
|
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1544
|
+
/**
|
|
1545
|
+
* A lifecycle function that runs the supplied `callback` when the current component mounts, and also whenever we navigate to a new URL.
|
|
1546
|
+
*
|
|
1547
|
+
* `afterNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
|
|
1548
|
+
* @param {(navigation: import('@sveltejs/kit').AfterNavigate) => void} callback
|
|
1549
|
+
* @returns {void}
|
|
1550
|
+
*/
|
|
1551
|
+
export function afterNavigate(callback) {
|
|
1552
|
+
add_navigation_callback(after_navigate_callbacks, callback);
|
|
1553
|
+
}
|
|
1518
1554
|
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1555
|
+
/**
|
|
1556
|
+
* A navigation interceptor that triggers before we navigate to a new URL, whether by clicking a link, calling `goto(...)`, or using the browser back/forward controls.
|
|
1557
|
+
*
|
|
1558
|
+
* Calling `cancel()` will prevent the navigation from completing. If `navigation.type === 'leave'` — meaning the user is navigating away from the app (or closing the tab) — calling `cancel` will trigger the native browser unload confirmation dialog. In this case, the navigation may or may not be cancelled depending on the user's response.
|
|
1559
|
+
*
|
|
1560
|
+
* When a navigation isn't to a SvelteKit-owned route (and therefore controlled by SvelteKit's client-side router), `navigation.to.route.id` will be `null`.
|
|
1561
|
+
*
|
|
1562
|
+
* If the navigation will (if not cancelled) cause the document to unload — in other words `'leave'` navigations and `'link'` navigations where `navigation.to.route === null` — `navigation.willUnload` is `true`.
|
|
1563
|
+
*
|
|
1564
|
+
* `beforeNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
|
|
1565
|
+
* @param {(navigation: import('@sveltejs/kit').BeforeNavigate) => void} callback
|
|
1566
|
+
* @returns {void}
|
|
1567
|
+
*/
|
|
1568
|
+
export function beforeNavigate(callback) {
|
|
1569
|
+
add_navigation_callback(before_navigate_callbacks, callback);
|
|
1570
|
+
}
|
|
1525
1571
|
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1572
|
+
/**
|
|
1573
|
+
* A lifecycle function that runs the supplied `callback` immediately before we navigate to a new URL except during full-page navigations.
|
|
1574
|
+
*
|
|
1575
|
+
* If you return a `Promise`, SvelteKit will wait for it to resolve before completing the navigation. This allows you to — for example — use `document.startViewTransition`. Avoid promises that are slow to resolve, since navigation will appear stalled to the user.
|
|
1576
|
+
*
|
|
1577
|
+
* If a function (or a `Promise` that resolves to a function) is returned from the callback, it will be called once the DOM has updated.
|
|
1578
|
+
*
|
|
1579
|
+
* `onNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
|
|
1580
|
+
* @param {(navigation: import('@sveltejs/kit').OnNavigate) => import('types').MaybePromise<void>} callback
|
|
1581
|
+
* @returns {void}
|
|
1582
|
+
*/
|
|
1583
|
+
export function onNavigate(callback) {
|
|
1584
|
+
add_navigation_callback(on_navigate_callbacks, callback);
|
|
1585
|
+
}
|
|
1530
1586
|
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1587
|
+
/**
|
|
1588
|
+
* If called when the page is being updated following a navigation (in `onMount` or `afterNavigate` or an action, for example), this disables SvelteKit's built-in scroll handling.
|
|
1589
|
+
* This is generally discouraged, since it breaks user expectations.
|
|
1590
|
+
* @returns {void}
|
|
1591
|
+
*/
|
|
1592
|
+
export function disableScrollHandling() {
|
|
1593
|
+
if (!BROWSER) {
|
|
1594
|
+
throw new Error('Cannot call disableScrollHandling() on the server');
|
|
1595
|
+
}
|
|
1535
1596
|
|
|
1536
|
-
|
|
1537
|
-
|
|
1597
|
+
if (DEV && started && !updating) {
|
|
1598
|
+
throw new Error('Can only disable scroll handling during navigation');
|
|
1599
|
+
}
|
|
1538
1600
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
? `Cannot use \`goto\` with an external URL. Use \`window.location = "${url}"\` instead`
|
|
1544
|
-
: 'goto: invalid URL'
|
|
1545
|
-
)
|
|
1546
|
-
);
|
|
1547
|
-
}
|
|
1601
|
+
if (updating || !started) {
|
|
1602
|
+
autoscroll = false;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1548
1605
|
|
|
1549
|
-
|
|
1550
|
-
|
|
1606
|
+
/**
|
|
1607
|
+
* Returns a Promise that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) to the specified `url`.
|
|
1608
|
+
* For external URLs, use `window.location = url` instead of calling `goto(url)`.
|
|
1609
|
+
*
|
|
1610
|
+
* @param {string | URL} url Where to navigate to. Note that if you've set [`config.kit.paths.base`](https://kit.svelte.dev/docs/configuration#paths) and the URL is root-relative, you need to prepend the base path if you want to navigate within the app.
|
|
1611
|
+
* @param {Object} [opts] Options related to the navigation
|
|
1612
|
+
* @param {boolean} [opts.replaceState] If `true`, will replace the current `history` entry rather than creating a new one with `pushState`
|
|
1613
|
+
* @param {boolean} [opts.noScroll] If `true`, the browser will maintain its scroll position rather than scrolling to the top of the page after navigation
|
|
1614
|
+
* @param {boolean} [opts.keepFocus] If `true`, the currently focused element will retain focus after navigation. Otherwise, focus will be reset to the body
|
|
1615
|
+
* @param {boolean} [opts.invalidateAll] If `true`, all `load` functions of the page will be rerun. See https://kit.svelte.dev/docs/load#rerunning-load-functions for more info on invalidation.
|
|
1616
|
+
* @param {App.PageState} [opts.state] An optional object that will be available on the `$page.state` store
|
|
1617
|
+
* @returns {Promise<void>}
|
|
1618
|
+
*/
|
|
1619
|
+
export function goto(url, opts = {}) {
|
|
1620
|
+
if (!BROWSER) {
|
|
1621
|
+
throw new Error('Cannot call goto(...) on the server');
|
|
1622
|
+
}
|
|
1551
1623
|
|
|
1552
|
-
|
|
1553
|
-
if (typeof resource === 'function') {
|
|
1554
|
-
invalidated.push(resource);
|
|
1555
|
-
} else {
|
|
1556
|
-
const { href } = new URL(resource, location.href);
|
|
1557
|
-
invalidated.push((url) => url.href === href);
|
|
1558
|
-
}
|
|
1624
|
+
url = resolve_url(url);
|
|
1559
1625
|
|
|
1560
|
-
|
|
1561
|
-
|
|
1626
|
+
if (url.origin !== origin) {
|
|
1627
|
+
return Promise.reject(
|
|
1628
|
+
new Error(
|
|
1629
|
+
DEV
|
|
1630
|
+
? `Cannot use \`goto\` with an external URL. Use \`window.location = "${url}"\` instead`
|
|
1631
|
+
: 'goto: invalid URL'
|
|
1632
|
+
)
|
|
1633
|
+
);
|
|
1634
|
+
}
|
|
1562
1635
|
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
return invalidate();
|
|
1566
|
-
},
|
|
1636
|
+
return _goto(url, opts, 0);
|
|
1637
|
+
}
|
|
1567
1638
|
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1639
|
+
/**
|
|
1640
|
+
* Causes any `load` functions belonging to the currently active page to re-run if they depend on the `url` in question, via `fetch` or `depends`. Returns a `Promise` that resolves when the page is subsequently updated.
|
|
1641
|
+
*
|
|
1642
|
+
* If the argument is given as a `string` or `URL`, it must resolve to the same URL that was passed to `fetch` or `depends` (including query parameters).
|
|
1643
|
+
* To create a custom identifier, use a string beginning with `[a-z]+:` (e.g. `custom:state`) — this is a valid URL.
|
|
1644
|
+
*
|
|
1645
|
+
* The `function` argument can be used define a custom predicate. It receives the full `URL` and causes `load` to rerun if `true` is returned.
|
|
1646
|
+
* This can be useful if you want to invalidate based on a pattern instead of a exact match.
|
|
1647
|
+
*
|
|
1648
|
+
* ```ts
|
|
1649
|
+
* // Example: Match '/path' regardless of the query parameters
|
|
1650
|
+
* import { invalidate } from '$app/navigation';
|
|
1651
|
+
*
|
|
1652
|
+
* invalidate((url) => url.pathname === '/path');
|
|
1653
|
+
* ```
|
|
1654
|
+
* @param {string | URL | ((url: URL) => boolean)} resource The invalidated URL
|
|
1655
|
+
* @returns {Promise<void>}
|
|
1656
|
+
*/
|
|
1657
|
+
export function invalidate(resource) {
|
|
1658
|
+
if (!BROWSER) {
|
|
1659
|
+
throw new Error('Cannot call invalidate(...) on the server');
|
|
1660
|
+
}
|
|
1571
1661
|
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1662
|
+
if (typeof resource === 'function') {
|
|
1663
|
+
invalidated.push(resource);
|
|
1664
|
+
} else {
|
|
1665
|
+
const { href } = new URL(resource, location.href);
|
|
1666
|
+
invalidated.push((url) => url.href === href);
|
|
1667
|
+
}
|
|
1575
1668
|
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
return {
|
|
1579
|
-
type: result.type,
|
|
1580
|
-
location: result.location
|
|
1581
|
-
};
|
|
1582
|
-
}
|
|
1669
|
+
return _invalidate();
|
|
1670
|
+
}
|
|
1583
1671
|
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1672
|
+
/**
|
|
1673
|
+
* Causes all `load` functions belonging to the currently active page to re-run. Returns a `Promise` that resolves when the page is subsequently updated.
|
|
1674
|
+
* @returns {Promise<void>}
|
|
1675
|
+
*/
|
|
1676
|
+
export function invalidateAll() {
|
|
1677
|
+
if (!BROWSER) {
|
|
1678
|
+
throw new Error('Cannot call invalidateAll() on the server');
|
|
1679
|
+
}
|
|
1587
1680
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
throw new Error(
|
|
1592
|
-
`pathnames passed to preloadCode must start with \`paths.base\` (i.e. "${base}${pathname}" rather than "${pathname}")`
|
|
1593
|
-
);
|
|
1594
|
-
}
|
|
1681
|
+
force_invalidation = true;
|
|
1682
|
+
return _invalidate();
|
|
1683
|
+
}
|
|
1595
1684
|
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1685
|
+
/**
|
|
1686
|
+
* Programmatically preloads the given page, which means
|
|
1687
|
+
* 1. ensuring that the code for the page is loaded, and
|
|
1688
|
+
* 2. calling the page's load function with the appropriate options.
|
|
1689
|
+
*
|
|
1690
|
+
* This is the same behaviour that SvelteKit triggers when the user taps or mouses over an `<a>` element with `data-sveltekit-preload-data`.
|
|
1691
|
+
* If the next navigation is to `href`, the values returned from load will be used, making navigation instantaneous.
|
|
1692
|
+
* Returns a Promise that resolves with the result of running the new route's `load` functions once the preload is complete.
|
|
1693
|
+
*
|
|
1694
|
+
* @param {string} href Page to preload
|
|
1695
|
+
* @returns {Promise<{ type: 'loaded'; status: number; data: Record<string, any> } | { type: 'redirect'; location: string }>}
|
|
1696
|
+
*/
|
|
1697
|
+
export async function preloadData(href) {
|
|
1698
|
+
if (!BROWSER) {
|
|
1699
|
+
throw new Error('Cannot call preloadData(...) on the server');
|
|
1700
|
+
}
|
|
1600
1701
|
|
|
1601
|
-
|
|
1602
|
-
|
|
1702
|
+
const url = resolve_url(href);
|
|
1703
|
+
const intent = get_navigation_intent(url, false);
|
|
1603
1704
|
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
devalue.stringify(state);
|
|
1608
|
-
} catch (error) {
|
|
1609
|
-
// @ts-expect-error
|
|
1610
|
-
throw new Error(`Could not serialize state${error.path}`);
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1705
|
+
if (!intent) {
|
|
1706
|
+
throw new Error(`Attempted to preload a URL that does not belong to this app: ${url}`);
|
|
1707
|
+
}
|
|
1613
1708
|
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1709
|
+
const result = await _preload_data(intent);
|
|
1710
|
+
if (result.type === 'redirect') {
|
|
1711
|
+
return {
|
|
1712
|
+
type: result.type,
|
|
1713
|
+
location: result.location
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1620
1716
|
|
|
1621
|
-
|
|
1717
|
+
const { status, data } = result.props.page ?? page;
|
|
1718
|
+
return { type: result.type, status, data };
|
|
1719
|
+
}
|
|
1622
1720
|
|
|
1623
|
-
|
|
1624
|
-
|
|
1721
|
+
/**
|
|
1722
|
+
* Programmatically imports the code for routes that haven't yet been fetched.
|
|
1723
|
+
* Typically, you might call this to speed up subsequent navigation.
|
|
1724
|
+
*
|
|
1725
|
+
* You can specify routes by any matching pathname such as `/about` (to match `src/routes/about/+page.svelte`) or `/blog/*` (to match `src/routes/blog/[slug]/+page.svelte`).
|
|
1726
|
+
*
|
|
1727
|
+
* Unlike `preloadData`, this won't call `load` functions.
|
|
1728
|
+
* Returns a Promise that resolves when the modules have been imported.
|
|
1729
|
+
*
|
|
1730
|
+
* @param {string} pathname
|
|
1731
|
+
* @returns {Promise<void>}
|
|
1732
|
+
*/
|
|
1733
|
+
export function preloadCode(pathname) {
|
|
1734
|
+
if (!BROWSER) {
|
|
1735
|
+
throw new Error('Cannot call preloadCode(...) on the server');
|
|
1736
|
+
}
|
|
1625
1737
|
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1738
|
+
if (DEV) {
|
|
1739
|
+
if (!pathname.startsWith(base)) {
|
|
1740
|
+
throw new Error(
|
|
1741
|
+
`pathnames passed to preloadCode must start with \`paths.base\` (i.e. "${base}${pathname}" rather than "${pathname}")`
|
|
1742
|
+
);
|
|
1743
|
+
}
|
|
1629
1744
|
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
} catch (error) {
|
|
1635
|
-
// @ts-expect-error
|
|
1636
|
-
throw new Error(`Could not serialize state${error.path}`);
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1745
|
+
if (!routes.find((route) => route.exec(get_url_path(pathname)))) {
|
|
1746
|
+
throw new Error(`'${pathname}' did not match any routes`);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1639
1749
|
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
[NAVIGATION_INDEX]: current_navigation_index,
|
|
1643
|
-
[PAGE_URL_KEY]: page.url.href
|
|
1644
|
-
};
|
|
1750
|
+
return _preload_code(pathname);
|
|
1751
|
+
}
|
|
1645
1752
|
|
|
1646
|
-
|
|
1753
|
+
/**
|
|
1754
|
+
* Programmatically create a new history entry with the given `$page.state`. To use the current URL, you can pass `''` as the first argument. Used for [shallow routing](https://kit.svelte.dev/docs/shallow-routing).
|
|
1755
|
+
*
|
|
1756
|
+
* @param {string | URL} url
|
|
1757
|
+
* @param {App.PageState} state
|
|
1758
|
+
* @returns {void}
|
|
1759
|
+
*/
|
|
1760
|
+
export function pushState(url, state) {
|
|
1761
|
+
if (!BROWSER) {
|
|
1762
|
+
throw new Error('Cannot call pushState(...) on the server');
|
|
1763
|
+
}
|
|
1647
1764
|
|
|
1648
|
-
|
|
1649
|
-
|
|
1765
|
+
if (DEV) {
|
|
1766
|
+
try {
|
|
1767
|
+
// use `devalue.stringify` as a convenient way to ensure we exclude values that can't be properly rehydrated, such as custom class instances
|
|
1768
|
+
devalue.stringify(state);
|
|
1769
|
+
} catch (error) {
|
|
1770
|
+
// @ts-expect-error
|
|
1771
|
+
throw new Error(`Could not serialize state${error.path}`);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1650
1774
|
|
|
1651
|
-
|
|
1652
|
-
},
|
|
1775
|
+
update_scroll_positions(current_history_index);
|
|
1653
1776
|
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1777
|
+
const opts = {
|
|
1778
|
+
[HISTORY_INDEX]: (current_history_index += 1),
|
|
1779
|
+
[NAVIGATION_INDEX]: current_navigation_index,
|
|
1780
|
+
[PAGE_URL_KEY]: page.url.href,
|
|
1781
|
+
[STATES_KEY]: state
|
|
1782
|
+
};
|
|
1657
1783
|
|
|
1658
|
-
|
|
1659
|
-
if (!route) return;
|
|
1784
|
+
original_push_state.call(history, opts, '', resolve_url(url));
|
|
1660
1785
|
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
branch,
|
|
1664
|
-
route.errors
|
|
1665
|
-
);
|
|
1666
|
-
if (error_load) {
|
|
1667
|
-
const navigation_result = await get_navigation_result_from_branch({
|
|
1668
|
-
url,
|
|
1669
|
-
params: current.params,
|
|
1670
|
-
branch: branch.slice(0, error_load.idx).concat(error_load.node),
|
|
1671
|
-
status: result.status ?? 500,
|
|
1672
|
-
error: result.error,
|
|
1673
|
-
route
|
|
1674
|
-
});
|
|
1786
|
+
page = { ...page, state };
|
|
1787
|
+
root.$set({ page });
|
|
1675
1788
|
|
|
1676
|
-
|
|
1789
|
+
clear_onward_history(current_history_index, current_navigation_index);
|
|
1790
|
+
}
|
|
1677
1791
|
|
|
1678
|
-
|
|
1792
|
+
/**
|
|
1793
|
+
* Programmatically replace the current history entry with the given `$page.state`. To use the current URL, you can pass `''` as the first argument. Used for [shallow routing](https://kit.svelte.dev/docs/shallow-routing).
|
|
1794
|
+
*
|
|
1795
|
+
* @param {string | URL} url
|
|
1796
|
+
* @param {App.PageState} state
|
|
1797
|
+
* @returns {void}
|
|
1798
|
+
*/
|
|
1799
|
+
export function replaceState(url, state) {
|
|
1800
|
+
if (!BROWSER) {
|
|
1801
|
+
throw new Error('Cannot call replaceState(...) on the server');
|
|
1802
|
+
}
|
|
1679
1803
|
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
form: null,
|
|
1690
|
-
page: { ...page, form: result.data, status: result.status }
|
|
1691
|
-
});
|
|
1692
|
-
|
|
1693
|
-
// ...so that setting the `form` prop takes effect and isn't ignored
|
|
1694
|
-
await tick();
|
|
1695
|
-
root.$set({ form: result.data });
|
|
1696
|
-
|
|
1697
|
-
if (result.type === 'success') {
|
|
1698
|
-
reset_focus();
|
|
1699
|
-
}
|
|
1700
|
-
}
|
|
1701
|
-
},
|
|
1804
|
+
if (DEV) {
|
|
1805
|
+
try {
|
|
1806
|
+
// use `devalue.stringify` as a convenient way to ensure we exclude values that can't be properly rehydrated, such as custom class instances
|
|
1807
|
+
devalue.stringify(state);
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
// @ts-expect-error
|
|
1810
|
+
throw new Error(`Could not serialize state${error.path}`);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1702
1813
|
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
// scrolling position.
|
|
1710
|
-
addEventListener('beforeunload', (e) => {
|
|
1711
|
-
let should_block = false;
|
|
1712
|
-
|
|
1713
|
-
persist_state();
|
|
1714
|
-
|
|
1715
|
-
if (!navigating) {
|
|
1716
|
-
const nav = create_navigation(current, undefined, null, 'leave');
|
|
1717
|
-
|
|
1718
|
-
// If we're navigating, beforeNavigate was already called. If we end up in here during navigation,
|
|
1719
|
-
// it's due to an external or full-page-reload link, for which we don't want to call the hook again.
|
|
1720
|
-
/** @type {import('@sveltejs/kit').BeforeNavigate} */
|
|
1721
|
-
const navigation = {
|
|
1722
|
-
...nav.navigation,
|
|
1723
|
-
cancel: () => {
|
|
1724
|
-
should_block = true;
|
|
1725
|
-
nav.reject(new Error('navigation was cancelled'));
|
|
1726
|
-
}
|
|
1727
|
-
};
|
|
1814
|
+
const opts = {
|
|
1815
|
+
[HISTORY_INDEX]: current_history_index,
|
|
1816
|
+
[NAVIGATION_INDEX]: current_navigation_index,
|
|
1817
|
+
[PAGE_URL_KEY]: page.url.href,
|
|
1818
|
+
[STATES_KEY]: state
|
|
1819
|
+
};
|
|
1728
1820
|
|
|
1729
|
-
|
|
1730
|
-
}
|
|
1821
|
+
original_replace_state.call(history, opts, '', resolve_url(url));
|
|
1731
1822
|
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
} else {
|
|
1736
|
-
history.scrollRestoration = 'auto';
|
|
1737
|
-
}
|
|
1738
|
-
});
|
|
1823
|
+
page = { ...page, state };
|
|
1824
|
+
root.$set({ page });
|
|
1825
|
+
}
|
|
1739
1826
|
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1827
|
+
/**
|
|
1828
|
+
* This action updates the `form` property of the current page with the given data and updates `$page.status`.
|
|
1829
|
+
* In case of an error, it redirects to the nearest error page.
|
|
1830
|
+
* @template {Record<string, unknown> | undefined} Success
|
|
1831
|
+
* @template {Record<string, unknown> | undefined} Failure
|
|
1832
|
+
* @param {import('@sveltejs/kit').ActionResult<Success, Failure>} result
|
|
1833
|
+
* @returns {Promise<void>}
|
|
1834
|
+
*/
|
|
1835
|
+
export async function applyAction(result) {
|
|
1836
|
+
if (!BROWSER) {
|
|
1837
|
+
throw new Error('Cannot call applyAction(...) on the server');
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
if (result.type === 'error') {
|
|
1841
|
+
const url = new URL(location.href);
|
|
1842
|
+
|
|
1843
|
+
const { branch, route } = current;
|
|
1844
|
+
if (!route) return;
|
|
1845
|
+
|
|
1846
|
+
const error_load = await load_nearest_error_page(current.branch.length, branch, route.errors);
|
|
1847
|
+
if (error_load) {
|
|
1848
|
+
const navigation_result = await get_navigation_result_from_branch({
|
|
1849
|
+
url,
|
|
1850
|
+
params: current.params,
|
|
1851
|
+
branch: branch.slice(0, error_load.idx).concat(error_load.node),
|
|
1852
|
+
status: result.status ?? 500,
|
|
1853
|
+
error: result.error,
|
|
1854
|
+
route
|
|
1744
1855
|
});
|
|
1745
1856
|
|
|
1746
|
-
|
|
1747
|
-
if (!navigator.connection?.saveData) {
|
|
1748
|
-
setup_preload();
|
|
1749
|
-
}
|
|
1857
|
+
current = navigation_result.state;
|
|
1750
1858
|
|
|
1751
|
-
|
|
1752
|
-
container.addEventListener('click', (event) => {
|
|
1753
|
-
// Adapted from https://github.com/visionmedia/page.js
|
|
1754
|
-
// MIT license https://github.com/visionmedia/page.js#license
|
|
1755
|
-
if (event.button || event.which !== 1) return;
|
|
1756
|
-
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
1757
|
-
if (event.defaultPrevented) return;
|
|
1758
|
-
|
|
1759
|
-
const a = find_anchor(/** @type {Element} */ (event.composedPath()[0]), container);
|
|
1760
|
-
if (!a) return;
|
|
1761
|
-
|
|
1762
|
-
const { url, external, target, download } = get_link_info(a, base);
|
|
1763
|
-
if (!url) return;
|
|
1764
|
-
|
|
1765
|
-
// bail out before `beforeNavigate` if link opens in a different tab
|
|
1766
|
-
if (target === '_parent' || target === '_top') {
|
|
1767
|
-
if (window.parent !== window) return;
|
|
1768
|
-
} else if (target && target !== '_self') {
|
|
1769
|
-
return;
|
|
1770
|
-
}
|
|
1859
|
+
root.$set(navigation_result.props);
|
|
1771
1860
|
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
!is_svg_a_element &&
|
|
1785
|
-
url.protocol !== location.protocol &&
|
|
1786
|
-
!(url.protocol === 'https:' || url.protocol === 'http:')
|
|
1787
|
-
)
|
|
1788
|
-
return;
|
|
1861
|
+
tick().then(reset_focus);
|
|
1862
|
+
}
|
|
1863
|
+
} else if (result.type === 'redirect') {
|
|
1864
|
+
_goto(result.location, { invalidateAll: true }, 0);
|
|
1865
|
+
} else {
|
|
1866
|
+
/** @type {Record<string, any>} */
|
|
1867
|
+
root.$set({
|
|
1868
|
+
// this brings Svelte's view of the world in line with SvelteKit's
|
|
1869
|
+
// after use:enhance reset the form....
|
|
1870
|
+
form: null,
|
|
1871
|
+
page: { ...page, form: result.data, status: result.status }
|
|
1872
|
+
});
|
|
1789
1873
|
|
|
1790
|
-
|
|
1874
|
+
// ...so that setting the `form` prop takes effect and isn't ignored
|
|
1875
|
+
await tick();
|
|
1876
|
+
root.$set({ form: result.data });
|
|
1791
1877
|
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
}
|
|
1878
|
+
if (result.type === 'success') {
|
|
1879
|
+
reset_focus();
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
function _start_router() {
|
|
1885
|
+
history.scrollRestoration = 'manual';
|
|
1801
1886
|
|
|
1802
|
-
|
|
1887
|
+
// Adopted from Nuxt.js
|
|
1888
|
+
// Reset scrollRestoration to auto when leaving page, allowing page reload
|
|
1889
|
+
// and back-navigation from other pages to use the browser to restore the
|
|
1890
|
+
// scrolling position.
|
|
1891
|
+
addEventListener('beforeunload', (e) => {
|
|
1892
|
+
let should_block = false;
|
|
1893
|
+
|
|
1894
|
+
persist_state();
|
|
1895
|
+
|
|
1896
|
+
if (!navigating) {
|
|
1897
|
+
const nav = create_navigation(current, undefined, null, 'leave');
|
|
1898
|
+
|
|
1899
|
+
// If we're navigating, beforeNavigate was already called. If we end up in here during navigation,
|
|
1900
|
+
// it's due to an external or full-page-reload link, for which we don't want to call the hook again.
|
|
1901
|
+
/** @type {import('@sveltejs/kit').BeforeNavigate} */
|
|
1902
|
+
const navigation = {
|
|
1903
|
+
...nav.navigation,
|
|
1904
|
+
cancel: () => {
|
|
1905
|
+
should_block = true;
|
|
1906
|
+
nav.reject(new Error('navigation cancelled'));
|
|
1803
1907
|
}
|
|
1908
|
+
};
|
|
1804
1909
|
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
// Removing the hash does a full page navigation in the browser, so make sure a hash is present
|
|
1808
|
-
const [nonhash, hash] = url.href.split('#');
|
|
1809
|
-
if (hash !== undefined && nonhash === strip_hash(location)) {
|
|
1810
|
-
// If we are trying to navigate to the same hash, we should only
|
|
1811
|
-
// attempt to scroll to that element and avoid any history changes.
|
|
1812
|
-
// Otherwise, this can cause Firefox to incorrectly assign a null
|
|
1813
|
-
// history state value without any signal that we can detect.
|
|
1814
|
-
const [, current_hash] = current.url.href.split('#');
|
|
1815
|
-
if (current_hash === hash) {
|
|
1816
|
-
event.preventDefault();
|
|
1817
|
-
|
|
1818
|
-
// We're already on /# and click on a link that goes to /#, or we're on
|
|
1819
|
-
// /#top and click on a link that goes to /#top. In those cases just go to
|
|
1820
|
-
// the top of the page, and avoid a history change.
|
|
1821
|
-
if (hash === '' || (hash === 'top' && a.ownerDocument.getElementById('top') === null)) {
|
|
1822
|
-
window.scrollTo({ top: 0 });
|
|
1823
|
-
} else {
|
|
1824
|
-
a.ownerDocument.getElementById(hash)?.scrollIntoView();
|
|
1825
|
-
}
|
|
1910
|
+
before_navigate_callbacks.forEach((fn) => fn(navigation));
|
|
1911
|
+
}
|
|
1826
1912
|
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1913
|
+
if (should_block) {
|
|
1914
|
+
e.preventDefault();
|
|
1915
|
+
e.returnValue = '';
|
|
1916
|
+
} else {
|
|
1917
|
+
history.scrollRestoration = 'auto';
|
|
1918
|
+
}
|
|
1919
|
+
});
|
|
1920
|
+
|
|
1921
|
+
addEventListener('visibilitychange', () => {
|
|
1922
|
+
if (document.visibilityState === 'hidden') {
|
|
1923
|
+
persist_state();
|
|
1924
|
+
}
|
|
1925
|
+
});
|
|
1832
1926
|
|
|
1833
|
-
|
|
1927
|
+
// @ts-expect-error this isn't supported everywhere yet
|
|
1928
|
+
if (!navigator.connection?.saveData) {
|
|
1929
|
+
setup_preload();
|
|
1930
|
+
}
|
|
1834
1931
|
|
|
1835
|
-
|
|
1932
|
+
/** @param {MouseEvent} event */
|
|
1933
|
+
container.addEventListener('click', (event) => {
|
|
1934
|
+
// Adapted from https://github.com/visionmedia/page.js
|
|
1935
|
+
// MIT license https://github.com/visionmedia/page.js#license
|
|
1936
|
+
if (event.button || event.which !== 1) return;
|
|
1937
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
1938
|
+
if (event.defaultPrevented) return;
|
|
1836
1939
|
|
|
1837
|
-
|
|
1940
|
+
const a = find_anchor(/** @type {Element} */ (event.composedPath()[0]), container);
|
|
1941
|
+
if (!a) return;
|
|
1838
1942
|
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1943
|
+
const { url, external, target, download } = get_link_info(a, base);
|
|
1944
|
+
if (!url) return;
|
|
1945
|
+
|
|
1946
|
+
// bail out before `beforeNavigate` if link opens in a different tab
|
|
1947
|
+
if (target === '_parent' || target === '_top') {
|
|
1948
|
+
if (window.parent !== window) return;
|
|
1949
|
+
} else if (target && target !== '_self') {
|
|
1950
|
+
return;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
const options = get_router_options(a);
|
|
1954
|
+
const is_svg_a_element = a instanceof SVGAElement;
|
|
1955
|
+
|
|
1956
|
+
// Ignore URL protocols that differ to the current one and are not http(s) (e.g. `mailto:`, `tel:`, `myapp:`, etc.)
|
|
1957
|
+
// This may be wrong when the protocol is x: and the link goes to y:.. which should be treated as an external
|
|
1958
|
+
// navigation, but it's not clear how to handle that case and it's not likely to come up in practice.
|
|
1959
|
+
// MEMO: Without this condition, firefox will open mailer twice.
|
|
1960
|
+
// See:
|
|
1961
|
+
// - https://github.com/sveltejs/kit/issues/4045
|
|
1962
|
+
// - https://github.com/sveltejs/kit/issues/5725
|
|
1963
|
+
// - https://github.com/sveltejs/kit/issues/6496
|
|
1964
|
+
if (
|
|
1965
|
+
!is_svg_a_element &&
|
|
1966
|
+
url.protocol !== location.protocol &&
|
|
1967
|
+
!(url.protocol === 'https:' || url.protocol === 'http:')
|
|
1968
|
+
)
|
|
1969
|
+
return;
|
|
1970
|
+
|
|
1971
|
+
if (download) return;
|
|
1842
1972
|
|
|
1973
|
+
// Ignore the following but fire beforeNavigate
|
|
1974
|
+
if (external || options.reload) {
|
|
1975
|
+
if (_before_navigate({ url, type: 'link' })) {
|
|
1976
|
+
// set `navigating` to `true` to prevent `beforeNavigate` callbacks
|
|
1977
|
+
// being called when the page unloads
|
|
1978
|
+
navigating = true;
|
|
1979
|
+
} else {
|
|
1843
1980
|
event.preventDefault();
|
|
1981
|
+
}
|
|
1844
1982
|
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
url,
|
|
1848
|
-
keepfocus: options.keepfocus,
|
|
1849
|
-
noscroll: options.noscroll,
|
|
1850
|
-
replace_state: options.replace_state ?? url.href === location.href
|
|
1851
|
-
});
|
|
1852
|
-
});
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1853
1985
|
|
|
1854
|
-
|
|
1855
|
-
|
|
1986
|
+
// Check if new url only differs by hash and use the browser default behavior in that case
|
|
1987
|
+
// This will ensure the `hashchange` event is fired
|
|
1988
|
+
// Removing the hash does a full page navigation in the browser, so make sure a hash is present
|
|
1989
|
+
const [nonhash, hash] = url.href.split('#');
|
|
1990
|
+
if (hash !== undefined && nonhash === strip_hash(location)) {
|
|
1991
|
+
// If we are trying to navigate to the same hash, we should only
|
|
1992
|
+
// attempt to scroll to that element and avoid any history changes.
|
|
1993
|
+
// Otherwise, this can cause Firefox to incorrectly assign a null
|
|
1994
|
+
// history state value without any signal that we can detect.
|
|
1995
|
+
const [, current_hash] = current.url.href.split('#');
|
|
1996
|
+
if (current_hash === hash) {
|
|
1997
|
+
event.preventDefault();
|
|
1856
1998
|
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1999
|
+
// We're already on /# and click on a link that goes to /#, or we're on
|
|
2000
|
+
// /#top and click on a link that goes to /#top. In those cases just go to
|
|
2001
|
+
// the top of the page, and avoid a history change.
|
|
2002
|
+
if (hash === '' || (hash === 'top' && a.ownerDocument.getElementById('top') === null)) {
|
|
2003
|
+
window.scrollTo({ top: 0 });
|
|
2004
|
+
} else {
|
|
2005
|
+
a.ownerDocument.getElementById(hash)?.scrollIntoView();
|
|
2006
|
+
}
|
|
1860
2007
|
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
// set this flag to distinguish between navigations triggered by
|
|
2011
|
+
// clicking a hash link and those triggered by popstate
|
|
2012
|
+
hash_navigating = true;
|
|
1864
2013
|
|
|
1865
|
-
|
|
2014
|
+
update_scroll_positions(current_history_index);
|
|
1866
2015
|
|
|
1867
|
-
|
|
2016
|
+
update_url(url);
|
|
1868
2017
|
|
|
1869
|
-
|
|
1870
|
-
(submitter?.hasAttribute('formaction') && submitter?.formAction) || form.action
|
|
1871
|
-
);
|
|
2018
|
+
if (!options.replace_state) return;
|
|
1872
2019
|
|
|
1873
|
-
|
|
2020
|
+
// hashchange event shouldn't occur if the router is replacing state.
|
|
2021
|
+
hash_navigating = false;
|
|
2022
|
+
}
|
|
1874
2023
|
|
|
1875
|
-
|
|
2024
|
+
event.preventDefault();
|
|
1876
2025
|
|
|
1877
|
-
|
|
1878
|
-
|
|
2026
|
+
navigate({
|
|
2027
|
+
type: 'link',
|
|
2028
|
+
url,
|
|
2029
|
+
keepfocus: options.keepfocus,
|
|
2030
|
+
noscroll: options.noscroll,
|
|
2031
|
+
replace_state: options.replace_state ?? url.href === location.href
|
|
2032
|
+
});
|
|
2033
|
+
});
|
|
1879
2034
|
|
|
1880
|
-
|
|
1881
|
-
|
|
2035
|
+
container.addEventListener('submit', (event) => {
|
|
2036
|
+
if (event.defaultPrevented) return;
|
|
1882
2037
|
|
|
1883
|
-
|
|
2038
|
+
const form = /** @type {HTMLFormElement} */ (
|
|
2039
|
+
HTMLFormElement.prototype.cloneNode.call(event.target)
|
|
2040
|
+
);
|
|
1884
2041
|
|
|
1885
|
-
|
|
1886
|
-
if (submitter_name) {
|
|
1887
|
-
data.append(submitter_name, submitter?.getAttribute('value') ?? '');
|
|
1888
|
-
}
|
|
2042
|
+
const submitter = /** @type {HTMLButtonElement | HTMLInputElement | null} */ (event.submitter);
|
|
1889
2043
|
|
|
1890
|
-
|
|
1891
|
-
url.search = new URLSearchParams(data).toString();
|
|
2044
|
+
const method = submitter?.formMethod || form.method;
|
|
1892
2045
|
|
|
1893
|
-
|
|
1894
|
-
type: 'form',
|
|
1895
|
-
url,
|
|
1896
|
-
keepfocus: options.keepfocus,
|
|
1897
|
-
noscroll: options.noscroll,
|
|
1898
|
-
replace_state: options.replace_state ?? url.href === location.href
|
|
1899
|
-
});
|
|
1900
|
-
});
|
|
2046
|
+
if (method !== 'get') return;
|
|
1901
2047
|
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
token = {};
|
|
1906
|
-
|
|
1907
|
-
// if a popstate-driven navigation is cancelled, we need to counteract it
|
|
1908
|
-
// with history.go, which means we end up back here, hence this check
|
|
1909
|
-
if (history_index === current_history_index) return;
|
|
1910
|
-
|
|
1911
|
-
const scroll = scroll_positions[history_index];
|
|
1912
|
-
const state = states[history_index] ?? {};
|
|
1913
|
-
const url = new URL(event.state[PAGE_URL_KEY] ?? location.href);
|
|
1914
|
-
const navigation_index = event.state[NAVIGATION_INDEX];
|
|
1915
|
-
const is_hash_change = strip_hash(location) === strip_hash(current.url);
|
|
1916
|
-
const shallow =
|
|
1917
|
-
navigation_index === current_navigation_index && (has_navigated || is_hash_change);
|
|
1918
|
-
|
|
1919
|
-
if (shallow) {
|
|
1920
|
-
// We don't need to navigate, we just need to update scroll and/or state.
|
|
1921
|
-
// This happens with hash links and `pushState`/`replaceState`. The
|
|
1922
|
-
// exception is if we haven't navigated yet, since we could have
|
|
1923
|
-
// got here after a modal navigation then a reload
|
|
1924
|
-
update_url(url);
|
|
1925
|
-
|
|
1926
|
-
scroll_positions[current_history_index] = scroll_state();
|
|
1927
|
-
if (scroll) scrollTo(scroll.x, scroll.y);
|
|
1928
|
-
|
|
1929
|
-
if (state !== page.state) {
|
|
1930
|
-
page = { ...page, state };
|
|
1931
|
-
root.$set({ page });
|
|
1932
|
-
}
|
|
2048
|
+
const url = new URL(
|
|
2049
|
+
(submitter?.hasAttribute('formaction') && submitter?.formAction) || form.action
|
|
2050
|
+
);
|
|
1933
2051
|
|
|
1934
|
-
|
|
1935
|
-
return;
|
|
1936
|
-
}
|
|
2052
|
+
if (is_external_url(url, base)) return;
|
|
1937
2053
|
|
|
1938
|
-
|
|
2054
|
+
const event_form = /** @type {HTMLFormElement} */ (event.target);
|
|
1939
2055
|
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
url,
|
|
1943
|
-
popped: {
|
|
1944
|
-
state,
|
|
1945
|
-
scroll,
|
|
1946
|
-
delta
|
|
1947
|
-
},
|
|
1948
|
-
accept: () => {
|
|
1949
|
-
current_history_index = history_index;
|
|
1950
|
-
current_navigation_index = navigation_index;
|
|
1951
|
-
},
|
|
1952
|
-
block: () => {
|
|
1953
|
-
history.go(-delta);
|
|
1954
|
-
},
|
|
1955
|
-
nav_token: token
|
|
1956
|
-
});
|
|
1957
|
-
} else {
|
|
1958
|
-
// since popstate event is also emitted when an anchor referencing the same
|
|
1959
|
-
// document is clicked, we have to check that the router isn't already handling
|
|
1960
|
-
// the navigation. otherwise we would be updating the page store twice.
|
|
1961
|
-
if (!hash_navigating) {
|
|
1962
|
-
const url = new URL(location.href);
|
|
1963
|
-
update_url(url);
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
});
|
|
2056
|
+
const options = get_router_options(event_form);
|
|
2057
|
+
if (options.reload) return;
|
|
1967
2058
|
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
// we need to update history, otherwise we have to leave it alone
|
|
1971
|
-
if (hash_navigating) {
|
|
1972
|
-
hash_navigating = false;
|
|
1973
|
-
original_replace_state.call(
|
|
1974
|
-
history,
|
|
1975
|
-
{
|
|
1976
|
-
...history.state,
|
|
1977
|
-
[HISTORY_INDEX]: ++current_history_index,
|
|
1978
|
-
[NAVIGATION_INDEX]: current_navigation_index
|
|
1979
|
-
},
|
|
1980
|
-
'',
|
|
1981
|
-
location.href
|
|
1982
|
-
);
|
|
1983
|
-
}
|
|
1984
|
-
});
|
|
2059
|
+
event.preventDefault();
|
|
2060
|
+
event.stopPropagation();
|
|
1985
2061
|
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
2062
|
+
const data = new FormData(event_form);
|
|
2063
|
+
|
|
2064
|
+
const submitter_name = submitter?.getAttribute('name');
|
|
2065
|
+
if (submitter_name) {
|
|
2066
|
+
data.append(submitter_name, submitter?.getAttribute('value') ?? '');
|
|
2067
|
+
}
|
|
1992
2068
|
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2069
|
+
// @ts-expect-error `URLSearchParams(fd)` is kosher, but typescript doesn't know that
|
|
2070
|
+
url.search = new URLSearchParams(data).toString();
|
|
2071
|
+
|
|
2072
|
+
navigate({
|
|
2073
|
+
type: 'form',
|
|
2074
|
+
url,
|
|
2075
|
+
keepfocus: options.keepfocus,
|
|
2076
|
+
noscroll: options.noscroll,
|
|
2077
|
+
replace_state: options.replace_state ?? url.href === location.href
|
|
2078
|
+
});
|
|
2079
|
+
});
|
|
2080
|
+
|
|
2081
|
+
addEventListener('popstate', async (event) => {
|
|
2082
|
+
if (event.state?.[HISTORY_INDEX]) {
|
|
2083
|
+
const history_index = event.state[HISTORY_INDEX];
|
|
2084
|
+
token = {};
|
|
2085
|
+
|
|
2086
|
+
// if a popstate-driven navigation is cancelled, we need to counteract it
|
|
2087
|
+
// with history.go, which means we end up back here, hence this check
|
|
2088
|
+
if (history_index === current_history_index) return;
|
|
2089
|
+
|
|
2090
|
+
const scroll = scroll_positions[history_index];
|
|
2091
|
+
const state = event.state[STATES_KEY] ?? {};
|
|
2092
|
+
const url = new URL(event.state[PAGE_URL_KEY] ?? location.href);
|
|
2093
|
+
const navigation_index = event.state[NAVIGATION_INDEX];
|
|
2094
|
+
const is_hash_change = strip_hash(location) === strip_hash(current.url);
|
|
2095
|
+
const shallow =
|
|
2096
|
+
navigation_index === current_navigation_index && (has_navigated || is_hash_change);
|
|
2097
|
+
|
|
2098
|
+
if (shallow) {
|
|
2099
|
+
// We don't need to navigate, we just need to update scroll and/or state.
|
|
2100
|
+
// This happens with hash links and `pushState`/`replaceState`. The
|
|
2101
|
+
// exception is if we haven't navigated yet, since we could have
|
|
2102
|
+
// got here after a modal navigation then a reload
|
|
2103
|
+
update_url(url);
|
|
2104
|
+
|
|
2105
|
+
scroll_positions[current_history_index] = scroll_state();
|
|
2106
|
+
if (scroll) scrollTo(scroll.x, scroll.y);
|
|
2107
|
+
|
|
2108
|
+
if (state !== page.state) {
|
|
2109
|
+
page = { ...page, state };
|
|
2110
|
+
root.$set({ page });
|
|
2000
2111
|
}
|
|
2001
|
-
});
|
|
2002
2112
|
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
*/
|
|
2006
|
-
function update_url(url) {
|
|
2007
|
-
current.url = url;
|
|
2008
|
-
stores.page.set({ ...page, url });
|
|
2009
|
-
stores.page.notify();
|
|
2113
|
+
current_history_index = history_index;
|
|
2114
|
+
return;
|
|
2010
2115
|
}
|
|
2011
|
-
},
|
|
2012
|
-
|
|
2013
|
-
_hydrate: async ({
|
|
2014
|
-
status = 200,
|
|
2015
|
-
error,
|
|
2016
|
-
node_ids,
|
|
2017
|
-
params,
|
|
2018
|
-
route,
|
|
2019
|
-
data: server_data_nodes,
|
|
2020
|
-
form
|
|
2021
|
-
}) => {
|
|
2022
|
-
hydrated = true;
|
|
2023
2116
|
|
|
2024
|
-
const
|
|
2117
|
+
const delta = history_index - current_history_index;
|
|
2025
2118
|
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2119
|
+
await navigate({
|
|
2120
|
+
type: 'popstate',
|
|
2121
|
+
url,
|
|
2122
|
+
popped: {
|
|
2123
|
+
state,
|
|
2124
|
+
scroll,
|
|
2125
|
+
delta
|
|
2126
|
+
},
|
|
2127
|
+
accept: () => {
|
|
2128
|
+
current_history_index = history_index;
|
|
2129
|
+
current_navigation_index = navigation_index;
|
|
2130
|
+
},
|
|
2131
|
+
block: () => {
|
|
2132
|
+
history.go(-delta);
|
|
2133
|
+
},
|
|
2134
|
+
nav_token: token
|
|
2135
|
+
});
|
|
2136
|
+
} else {
|
|
2137
|
+
// since popstate event is also emitted when an anchor referencing the same
|
|
2138
|
+
// document is clicked, we have to check that the router isn't already handling
|
|
2139
|
+
// the navigation. otherwise we would be updating the page store twice.
|
|
2140
|
+
if (!hash_navigating) {
|
|
2141
|
+
const url = new URL(location.href);
|
|
2142
|
+
update_url(url);
|
|
2030
2143
|
}
|
|
2144
|
+
}
|
|
2145
|
+
});
|
|
2031
2146
|
|
|
2032
|
-
|
|
2033
|
-
|
|
2147
|
+
addEventListener('hashchange', () => {
|
|
2148
|
+
// if the hashchange happened as a result of clicking on a link,
|
|
2149
|
+
// we need to update history, otherwise we have to leave it alone
|
|
2150
|
+
if (hash_navigating) {
|
|
2151
|
+
hash_navigating = false;
|
|
2152
|
+
original_replace_state.call(
|
|
2153
|
+
history,
|
|
2154
|
+
{
|
|
2155
|
+
...history.state,
|
|
2156
|
+
[HISTORY_INDEX]: ++current_history_index,
|
|
2157
|
+
[NAVIGATION_INDEX]: current_navigation_index
|
|
2158
|
+
},
|
|
2159
|
+
'',
|
|
2160
|
+
location.href
|
|
2161
|
+
);
|
|
2162
|
+
}
|
|
2163
|
+
});
|
|
2034
2164
|
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
}
|
|
2165
|
+
// fix link[rel=icon], because browsers will occasionally try to load relative
|
|
2166
|
+
// URLs after a pushState/replaceState, resulting in a 404 — see
|
|
2167
|
+
// https://github.com/sveltejs/kit/issues/3748#issuecomment-1125980897
|
|
2168
|
+
for (const link of document.querySelectorAll('link')) {
|
|
2169
|
+
if (link.rel === 'icon') link.href = link.href; // eslint-disable-line
|
|
2170
|
+
}
|
|
2042
2171
|
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
}
|
|
2053
|
-
return data;
|
|
2054
|
-
},
|
|
2055
|
-
server_data_node: create_data_node(server_data_node)
|
|
2056
|
-
});
|
|
2057
|
-
});
|
|
2172
|
+
addEventListener('pageshow', (event) => {
|
|
2173
|
+
// If the user navigates to another site and then uses the back button and
|
|
2174
|
+
// bfcache hits, we need to set navigating to null, the site doesn't know
|
|
2175
|
+
// the navigation away from it was successful.
|
|
2176
|
+
// Info about bfcache here: https://web.dev/bfcache
|
|
2177
|
+
if (event.persisted) {
|
|
2178
|
+
stores.navigating.set(null);
|
|
2179
|
+
}
|
|
2180
|
+
});
|
|
2058
2181
|
|
|
2059
|
-
|
|
2060
|
-
|
|
2182
|
+
/**
|
|
2183
|
+
* @param {URL} url
|
|
2184
|
+
*/
|
|
2185
|
+
function update_url(url) {
|
|
2186
|
+
current.url = url;
|
|
2187
|
+
stores.page.set({ ...page, url });
|
|
2188
|
+
stores.page.notify();
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2061
2191
|
|
|
2062
|
-
|
|
2192
|
+
/**
|
|
2193
|
+
* @param {HTMLElement} target
|
|
2194
|
+
* @param {{
|
|
2195
|
+
* status: number;
|
|
2196
|
+
* error: App.Error | null;
|
|
2197
|
+
* node_ids: number[];
|
|
2198
|
+
* params: Record<string, string>;
|
|
2199
|
+
* route: { id: string | null };
|
|
2200
|
+
* data: Array<import('types').ServerDataNode | null>;
|
|
2201
|
+
* form: Record<string, any> | null;
|
|
2202
|
+
* }} opts
|
|
2203
|
+
*/
|
|
2204
|
+
async function _hydrate(
|
|
2205
|
+
target,
|
|
2206
|
+
{ status = 200, error, node_ids, params, route, data: server_data_nodes, form }
|
|
2207
|
+
) {
|
|
2208
|
+
hydrated = true;
|
|
2209
|
+
|
|
2210
|
+
const url = new URL(location.href);
|
|
2211
|
+
|
|
2212
|
+
if (!__SVELTEKIT_EMBEDDED__) {
|
|
2213
|
+
// See https://github.com/sveltejs/kit/pull/4935#issuecomment-1328093358 for one motivation
|
|
2214
|
+
// of determining the params on the client side.
|
|
2215
|
+
({ params = {}, route = { id: null } } = get_navigation_intent(url, false) || {});
|
|
2216
|
+
}
|
|
2063
2217
|
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2218
|
+
/** @type {import('./types.js').NavigationFinished | undefined} */
|
|
2219
|
+
let result;
|
|
2220
|
+
|
|
2221
|
+
try {
|
|
2222
|
+
const branch_promises = node_ids.map(async (n, i) => {
|
|
2223
|
+
const server_data_node = server_data_nodes[i];
|
|
2224
|
+
// Type isn't completely accurate, we still need to deserialize uses
|
|
2225
|
+
if (server_data_node?.uses) {
|
|
2226
|
+
server_data_node.uses = deserialize_uses(server_data_node.uses);
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
return load_node({
|
|
2230
|
+
loader: app.nodes[n],
|
|
2231
|
+
url,
|
|
2232
|
+
params,
|
|
2233
|
+
route,
|
|
2234
|
+
parent: async () => {
|
|
2235
|
+
const data = {};
|
|
2236
|
+
for (let j = 0; j < i; j += 1) {
|
|
2237
|
+
Object.assign(data, (await branch_promises[j]).data);
|
|
2072
2238
|
}
|
|
2073
|
-
|
|
2239
|
+
return data;
|
|
2240
|
+
},
|
|
2241
|
+
server_data_node: create_data_node(server_data_node)
|
|
2242
|
+
});
|
|
2243
|
+
});
|
|
2074
2244
|
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
params,
|
|
2078
|
-
branch,
|
|
2079
|
-
status,
|
|
2080
|
-
error,
|
|
2081
|
-
form,
|
|
2082
|
-
route: parsed_route ?? null
|
|
2083
|
-
});
|
|
2084
|
-
} catch (error) {
|
|
2085
|
-
if (error instanceof Redirect) {
|
|
2086
|
-
// this is a real edge case — `load` would need to return
|
|
2087
|
-
// a redirect but only in the browser
|
|
2088
|
-
await native_navigation(new URL(error.location, location.href));
|
|
2089
|
-
return;
|
|
2090
|
-
}
|
|
2245
|
+
/** @type {Array<import('./types.js').BranchNode | undefined>} */
|
|
2246
|
+
const branch = await Promise.all(branch_promises);
|
|
2091
2247
|
|
|
2092
|
-
|
|
2093
|
-
status: get_status(error),
|
|
2094
|
-
error: await handle_error(error, { url, params, route }),
|
|
2095
|
-
url,
|
|
2096
|
-
route
|
|
2097
|
-
});
|
|
2098
|
-
}
|
|
2248
|
+
const parsed_route = routes.find(({ id }) => id === route.id);
|
|
2099
2249
|
|
|
2100
|
-
|
|
2101
|
-
|
|
2250
|
+
// server-side will have compacted the branch, reinstate empty slots
|
|
2251
|
+
// so that error boundaries can be lined up correctly
|
|
2252
|
+
if (parsed_route) {
|
|
2253
|
+
const layouts = parsed_route.layouts;
|
|
2254
|
+
for (let i = 0; i < layouts.length; i++) {
|
|
2255
|
+
if (!layouts[i]) {
|
|
2256
|
+
branch.splice(i, 0, undefined);
|
|
2257
|
+
}
|
|
2102
2258
|
}
|
|
2259
|
+
}
|
|
2103
2260
|
|
|
2104
|
-
|
|
2261
|
+
result = await get_navigation_result_from_branch({
|
|
2262
|
+
url,
|
|
2263
|
+
params,
|
|
2264
|
+
branch,
|
|
2265
|
+
status,
|
|
2266
|
+
error,
|
|
2267
|
+
form,
|
|
2268
|
+
route: parsed_route ?? null
|
|
2269
|
+
});
|
|
2270
|
+
} catch (error) {
|
|
2271
|
+
if (error instanceof Redirect) {
|
|
2272
|
+
// this is a real edge case — `load` would need to return
|
|
2273
|
+
// a redirect but only in the browser
|
|
2274
|
+
await native_navigation(new URL(error.location, location.href));
|
|
2275
|
+
return;
|
|
2105
2276
|
}
|
|
2106
|
-
|
|
2277
|
+
|
|
2278
|
+
result = await load_root_error_page({
|
|
2279
|
+
status: get_status(error),
|
|
2280
|
+
error: await handle_error(error, { url, params, route }),
|
|
2281
|
+
url,
|
|
2282
|
+
route
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
if (result.props.page) {
|
|
2287
|
+
result.props.page.state = {};
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
initialize(result, target);
|
|
2107
2291
|
}
|
|
2108
2292
|
|
|
2109
2293
|
/**
|