@sveltejs/kit 1.0.0-next.4 → 1.0.0-next.401

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.
Files changed (69) hide show
  1. package/README.md +12 -9
  2. package/assets/app/env.js +13 -0
  3. package/assets/app/navigation.js +24 -0
  4. package/assets/app/paths.js +1 -0
  5. package/assets/app/stores.js +97 -0
  6. package/assets/client/singletons.js +13 -0
  7. package/assets/client/start.js +1845 -0
  8. package/assets/components/error.svelte +19 -3
  9. package/assets/env/dynamic/private.js +1 -0
  10. package/assets/env/dynamic/public.js +1 -0
  11. package/assets/env-private.js +9 -0
  12. package/assets/env-public.js +9 -0
  13. package/assets/env.js +8 -0
  14. package/assets/paths.js +13 -0
  15. package/assets/server/index.js +3579 -0
  16. package/dist/chunks/error.js +12 -0
  17. package/dist/chunks/filesystem.js +110 -0
  18. package/dist/chunks/index.js +675 -0
  19. package/dist/chunks/index2.js +15745 -0
  20. package/dist/chunks/index3.js +218 -0
  21. package/dist/chunks/multipart-parser.js +458 -0
  22. package/dist/chunks/sync.js +1368 -0
  23. package/dist/chunks/utils.js +66 -0
  24. package/dist/chunks/write_tsconfig.js +273 -0
  25. package/dist/cli.js +91 -101
  26. package/dist/hooks.js +28 -0
  27. package/dist/node/polyfills.js +17778 -0
  28. package/dist/node.js +348 -0
  29. package/dist/prerender.js +788 -0
  30. package/dist/vite.js +2520 -0
  31. package/package.json +98 -52
  32. package/svelte-kit.js +11 -0
  33. package/types/ambient.d.ts +375 -0
  34. package/types/index.d.ts +298 -0
  35. package/types/internal.d.ts +335 -0
  36. package/types/private.d.ts +235 -0
  37. package/CHANGELOG.md +0 -171
  38. package/assets/runtime/app/navigation.js +0 -47
  39. package/assets/runtime/app/navigation.js.map +0 -1
  40. package/assets/runtime/app/stores.js +0 -78
  41. package/assets/runtime/app/stores.js.map +0 -1
  42. package/assets/runtime/internal/singletons.js +0 -10
  43. package/assets/runtime/internal/singletons.js.map +0 -1
  44. package/assets/runtime/internal/start.js +0 -517
  45. package/assets/runtime/internal/start.js.map +0 -1
  46. package/dist/api.js +0 -40
  47. package/dist/api.js.map +0 -1
  48. package/dist/cli.js.map +0 -1
  49. package/dist/create_app.js +0 -550
  50. package/dist/create_app.js.map +0 -1
  51. package/dist/index.js +0 -8331
  52. package/dist/index.js.map +0 -1
  53. package/dist/index2.js +0 -509
  54. package/dist/index2.js.map +0 -1
  55. package/dist/index3.js +0 -63
  56. package/dist/index3.js.map +0 -1
  57. package/dist/index4.js +0 -466
  58. package/dist/index4.js.map +0 -1
  59. package/dist/index5.js +0 -276
  60. package/dist/index5.js.map +0 -1
  61. package/dist/package.js +0 -235
  62. package/dist/package.js.map +0 -1
  63. package/dist/renderer.js +0 -2397
  64. package/dist/renderer.js.map +0 -1
  65. package/dist/standard.js +0 -101
  66. package/dist/standard.js.map +0 -1
  67. package/dist/utils.js +0 -58
  68. package/dist/utils.js.map +0 -1
  69. package/svelte-kit +0 -3
@@ -0,0 +1,1845 @@
1
+ import { onMount, tick } from 'svelte';
2
+ import { writable } from 'svelte/store';
3
+ import { assets, set_paths } from '../paths.js';
4
+ import Root from '__GENERATED__/root.svelte';
5
+ import { components, dictionary, matchers } from '__GENERATED__/client-manifest.js';
6
+ import { init } from './singletons.js';
7
+ export { set_public_env } from '../env-public.js';
8
+
9
+ /**
10
+ * @param {unknown} err
11
+ * @return {Error}
12
+ */
13
+ function coalesce_to_error(err) {
14
+ return err instanceof Error ||
15
+ (err && /** @type {any} */ (err).name && /** @type {any} */ (err).message)
16
+ ? /** @type {Error} */ (err)
17
+ : new Error(JSON.stringify(err));
18
+ }
19
+
20
+ /**
21
+ * @param {import('types').LoadOutput | void} loaded
22
+ * @returns {import('types').NormalizedLoadOutput}
23
+ */
24
+ function normalize(loaded) {
25
+ if (!loaded) {
26
+ return {};
27
+ }
28
+
29
+ // TODO remove for 1.0
30
+ // @ts-expect-error
31
+ if (loaded.fallthrough) {
32
+ throw new Error(
33
+ 'fallthrough is no longer supported. Use matchers instead: https://kit.svelte.dev/docs/routing#advanced-routing-matching'
34
+ );
35
+ }
36
+
37
+ // TODO remove for 1.0
38
+ if ('maxage' in loaded) {
39
+ throw new Error('maxage should be replaced with cache: { maxage }');
40
+ }
41
+
42
+ const has_error_status =
43
+ loaded.status && loaded.status >= 400 && loaded.status <= 599 && !loaded.redirect;
44
+ if (loaded.error || has_error_status) {
45
+ const status = loaded.status;
46
+
47
+ if (!loaded.error && has_error_status) {
48
+ return {
49
+ status: status || 500,
50
+ error: new Error(`${status}`)
51
+ };
52
+ }
53
+
54
+ const error = typeof loaded.error === 'string' ? new Error(loaded.error) : loaded.error;
55
+
56
+ if (!(error instanceof Error)) {
57
+ return {
58
+ status: 500,
59
+ error: new Error(
60
+ `"error" property returned from load() must be a string or instance of Error, received type "${typeof error}"`
61
+ )
62
+ };
63
+ }
64
+
65
+ if (!status || status < 400 || status > 599) {
66
+ console.warn('"error" returned from load() without a valid status code — defaulting to 500');
67
+ return { status: 500, error };
68
+ }
69
+
70
+ return { status, error };
71
+ }
72
+
73
+ if (loaded.redirect) {
74
+ if (!loaded.status || Math.floor(loaded.status / 100) !== 3) {
75
+ throw new Error(
76
+ '"redirect" property returned from load() must be accompanied by a 3xx status code'
77
+ );
78
+ }
79
+
80
+ if (typeof loaded.redirect !== 'string') {
81
+ throw new Error('"redirect" property returned from load() must be a string');
82
+ }
83
+ }
84
+
85
+ if (loaded.dependencies) {
86
+ if (
87
+ !Array.isArray(loaded.dependencies) ||
88
+ loaded.dependencies.some((dep) => typeof dep !== 'string')
89
+ ) {
90
+ throw new Error('"dependencies" property returned from load() must be of type string[]');
91
+ }
92
+ }
93
+
94
+ // TODO remove before 1.0
95
+ if (/** @type {any} */ (loaded).context) {
96
+ throw new Error(
97
+ 'You are returning "context" from a load function. ' +
98
+ '"context" was renamed to "stuff", please adjust your code accordingly.'
99
+ );
100
+ }
101
+
102
+ return /** @type {import('types').NormalizedLoadOutput} */ (loaded);
103
+ }
104
+
105
+ /**
106
+ * @param {string} path
107
+ * @param {import('types').TrailingSlash} trailing_slash
108
+ */
109
+ function normalize_path(path, trailing_slash) {
110
+ if (path === '/' || trailing_slash === 'ignore') return path;
111
+
112
+ if (trailing_slash === 'never') {
113
+ return path.endsWith('/') ? path.slice(0, -1) : path;
114
+ } else if (trailing_slash === 'always' && !path.endsWith('/')) {
115
+ return path + '/';
116
+ }
117
+
118
+ return path;
119
+ }
120
+
121
+ /** @param {Record<string, string>} params */
122
+ function decode_params(params) {
123
+ for (const key in params) {
124
+ // input has already been decoded by decodeURI
125
+ // now handle the rest that decodeURIComponent would do
126
+ params[key] = params[key]
127
+ .replace(/%23/g, '#')
128
+ .replace(/%3[Bb]/g, ';')
129
+ .replace(/%2[Cc]/g, ',')
130
+ .replace(/%2[Ff]/g, '/')
131
+ .replace(/%3[Ff]/g, '?')
132
+ .replace(/%3[Aa]/g, ':')
133
+ .replace(/%40/g, '@')
134
+ .replace(/%26/g, '&')
135
+ .replace(/%3[Dd]/g, '=')
136
+ .replace(/%2[Bb]/g, '+')
137
+ .replace(/%24/g, '$');
138
+ }
139
+
140
+ return params;
141
+ }
142
+
143
+ class LoadURL extends URL {
144
+ /** @returns {string} */
145
+ get hash() {
146
+ throw new Error(
147
+ 'url.hash is inaccessible from load. Consider accessing hash from the page store within the script tag of your component.'
148
+ );
149
+ }
150
+ }
151
+
152
+ /* global __SVELTEKIT_APP_VERSION__, __SVELTEKIT_APP_VERSION_FILE__, __SVELTEKIT_APP_VERSION_POLL_INTERVAL__ */
153
+
154
+ /** @param {HTMLDocument} doc */
155
+ function get_base_uri(doc) {
156
+ let baseURI = doc.baseURI;
157
+
158
+ if (!baseURI) {
159
+ const baseTags = doc.getElementsByTagName('base');
160
+ baseURI = baseTags.length ? baseTags[0].href : doc.URL;
161
+ }
162
+
163
+ return baseURI;
164
+ }
165
+
166
+ function scroll_state() {
167
+ return {
168
+ x: pageXOffset,
169
+ y: pageYOffset
170
+ };
171
+ }
172
+
173
+ /** @param {Event} event */
174
+ function find_anchor(event) {
175
+ const node = event
176
+ .composedPath()
177
+ .find((e) => e instanceof Node && e.nodeName.toUpperCase() === 'A'); // SVG <a> elements have a lowercase name
178
+ return /** @type {HTMLAnchorElement | SVGAElement | undefined} */ (node);
179
+ }
180
+
181
+ /** @param {HTMLAnchorElement | SVGAElement} node */
182
+ function get_href(node) {
183
+ return node instanceof SVGAElement
184
+ ? new URL(node.href.baseVal, document.baseURI)
185
+ : new URL(node.href);
186
+ }
187
+
188
+ /** @param {any} value */
189
+ function notifiable_store(value) {
190
+ const store = writable(value);
191
+ let ready = true;
192
+
193
+ function notify() {
194
+ ready = true;
195
+ store.update((val) => val);
196
+ }
197
+
198
+ /** @param {any} new_value */
199
+ function set(new_value) {
200
+ ready = false;
201
+ store.set(new_value);
202
+ }
203
+
204
+ /** @param {(value: any) => void} run */
205
+ function subscribe(run) {
206
+ /** @type {any} */
207
+ let old_value;
208
+ return store.subscribe((new_value) => {
209
+ if (old_value === undefined || (ready && new_value !== old_value)) {
210
+ run((old_value = new_value));
211
+ }
212
+ });
213
+ }
214
+
215
+ return { notify, set, subscribe };
216
+ }
217
+
218
+ function create_updated_store() {
219
+ const { set, subscribe } = writable(false);
220
+
221
+ const interval = __SVELTEKIT_APP_VERSION_POLL_INTERVAL__;
222
+
223
+ /** @type {NodeJS.Timeout} */
224
+ let timeout;
225
+
226
+ async function check() {
227
+ if (import.meta.env.DEV || import.meta.env.SSR) return false;
228
+
229
+ clearTimeout(timeout);
230
+
231
+ if (interval) timeout = setTimeout(check, interval);
232
+
233
+ const res = await fetch(`${assets}/${__SVELTEKIT_APP_VERSION_FILE__}`, {
234
+ headers: {
235
+ pragma: 'no-cache',
236
+ 'cache-control': 'no-cache'
237
+ }
238
+ });
239
+
240
+ if (res.ok) {
241
+ const { version } = await res.json();
242
+ const updated = version !== __SVELTEKIT_APP_VERSION__;
243
+
244
+ if (updated) {
245
+ set(true);
246
+ clearTimeout(timeout);
247
+ }
248
+
249
+ return updated;
250
+ } else {
251
+ throw new Error(`Version check failed: ${res.status}`);
252
+ }
253
+ }
254
+
255
+ if (interval) timeout = setTimeout(check, interval);
256
+
257
+ return {
258
+ subscribe,
259
+ check
260
+ };
261
+ }
262
+
263
+ /**
264
+ * Hash using djb2
265
+ * @param {import('types').StrictBody} value
266
+ */
267
+ function hash(value) {
268
+ let hash = 5381;
269
+ let i = value.length;
270
+
271
+ if (typeof value === 'string') {
272
+ while (i) hash = (hash * 33) ^ value.charCodeAt(--i);
273
+ } else {
274
+ while (i) hash = (hash * 33) ^ value[--i];
275
+ }
276
+
277
+ return (hash >>> 0).toString(36);
278
+ }
279
+
280
+ let loading = 0;
281
+
282
+ const native_fetch = window.fetch;
283
+
284
+ function lock_fetch() {
285
+ loading += 1;
286
+ }
287
+
288
+ function unlock_fetch() {
289
+ loading -= 1;
290
+ }
291
+
292
+ if (import.meta.env.DEV) {
293
+ let can_inspect_stack_trace = false;
294
+
295
+ const check_stack_trace = async () => {
296
+ const stack = /** @type {string} */ (new Error().stack);
297
+ can_inspect_stack_trace = stack.includes('check_stack_trace');
298
+ };
299
+
300
+ check_stack_trace();
301
+
302
+ window.fetch = (input, init) => {
303
+ const url = input instanceof Request ? input.url : input.toString();
304
+ const stack = /** @type {string} */ (new Error().stack);
305
+
306
+ const heuristic = can_inspect_stack_trace ? stack.includes('load_node') : loading;
307
+ if (heuristic) {
308
+ console.warn(
309
+ `Loading ${url} using \`window.fetch\`. For best results, use the \`fetch\` that is passed to your \`load\` function: https://kit.svelte.dev/docs/loading#input-fetch`
310
+ );
311
+ }
312
+
313
+ return native_fetch(input, init);
314
+ };
315
+ }
316
+
317
+ /**
318
+ * @param {RequestInfo} resource
319
+ * @param {RequestInit} [opts]
320
+ */
321
+ function initial_fetch(resource, opts) {
322
+ const url = JSON.stringify(typeof resource === 'string' ? resource : resource.url);
323
+
324
+ let selector = `script[sveltekit\\:data-type="data"][sveltekit\\:data-url=${url}]`;
325
+
326
+ if (opts && typeof opts.body === 'string') {
327
+ selector += `[sveltekit\\:data-body="${hash(opts.body)}"]`;
328
+ }
329
+
330
+ const script = document.querySelector(selector);
331
+ if (script && script.textContent) {
332
+ const { body, ...init } = JSON.parse(script.textContent);
333
+ return Promise.resolve(new Response(body, init));
334
+ }
335
+
336
+ return native_fetch(resource, opts);
337
+ }
338
+
339
+ const param_pattern = /^(\.\.\.)?(\w+)(?:=(\w+))?$/;
340
+
341
+ /** @param {string} id */
342
+ function parse_route_id(id) {
343
+ /** @type {string[]} */
344
+ const names = [];
345
+
346
+ /** @type {string[]} */
347
+ const types = [];
348
+
349
+ // `/foo` should get an optional trailing slash, `/foo.json` should not
350
+ // const add_trailing_slash = !/\.[a-z]+$/.test(key);
351
+ let add_trailing_slash = true;
352
+
353
+ const pattern =
354
+ id === ''
355
+ ? /^\/$/
356
+ : new RegExp(
357
+ `^${decodeURIComponent(id)
358
+ .split(/(?:@[a-zA-Z0-9_-]+)?(?:\/|$)/)
359
+ .map((segment, i, segments) => {
360
+ // special case — /[...rest]/ could contain zero segments
361
+ const match = /^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(segment);
362
+ if (match) {
363
+ names.push(match[1]);
364
+ types.push(match[2]);
365
+ return '(?:/(.*))?';
366
+ }
367
+
368
+ const is_last = i === segments.length - 1;
369
+
370
+ return (
371
+ segment &&
372
+ '/' +
373
+ segment
374
+ .split(/\[(.+?)\]/)
375
+ .map((content, i) => {
376
+ if (i % 2) {
377
+ const match = param_pattern.exec(content);
378
+ if (!match) {
379
+ throw new Error(
380
+ `Invalid param: ${content}. Params and matcher names can only have underscores and alphanumeric characters.`
381
+ );
382
+ }
383
+
384
+ const [, rest, name, type] = match;
385
+ names.push(name);
386
+ types.push(type);
387
+ return rest ? '(.*?)' : '([^/]+?)';
388
+ }
389
+
390
+ if (is_last && content.includes('.')) add_trailing_slash = false;
391
+
392
+ return (
393
+ content // allow users to specify characters on the file system in an encoded manner
394
+ .normalize()
395
+ // We use [ and ] to denote parameters, so users must encode these on the file
396
+ // system to match against them. We don't decode all characters since others
397
+ // can already be epressed and so that '%' can be easily used directly in filenames
398
+ .replace(/%5[Bb]/g, '[')
399
+ .replace(/%5[Dd]/g, ']')
400
+ // '#', '/', and '?' can only appear in URL path segments in an encoded manner.
401
+ // They will not be touched by decodeURI so need to be encoded here, so
402
+ // that we can match against them.
403
+ // We skip '/' since you can't create a file with it on any OS
404
+ .replace(/#/g, '%23')
405
+ .replace(/\?/g, '%3F')
406
+ // escape characters that have special meaning in regex
407
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
408
+ ); // TODO handle encoding
409
+ })
410
+ .join('')
411
+ );
412
+ })
413
+ .join('')}${add_trailing_slash ? '/?' : ''}$`
414
+ );
415
+
416
+ return { pattern, names, types };
417
+ }
418
+
419
+ /**
420
+ * @param {RegExpMatchArray} match
421
+ * @param {string[]} names
422
+ * @param {string[]} types
423
+ * @param {Record<string, import('types').ParamMatcher>} matchers
424
+ */
425
+ function exec(match, names, types, matchers) {
426
+ /** @type {Record<string, string>} */
427
+ const params = {};
428
+
429
+ for (let i = 0; i < names.length; i += 1) {
430
+ const name = names[i];
431
+ const type = types[i];
432
+ const value = match[i + 1] || '';
433
+
434
+ if (type) {
435
+ const matcher = matchers[type];
436
+ if (!matcher) throw new Error(`Missing "${type}" param matcher`); // TODO do this ahead of time?
437
+
438
+ if (!matcher(value)) return;
439
+ }
440
+
441
+ params[name] = value;
442
+ }
443
+
444
+ return params;
445
+ }
446
+
447
+ /**
448
+ * @param {import('types').CSRComponentLoader[]} components
449
+ * @param {Record<string, [number[], number[], 1?]>} dictionary
450
+ * @param {Record<string, (param: string) => boolean>} matchers
451
+ * @returns {import('types').CSRRoute[]}
452
+ */
453
+ function parse(components, dictionary, matchers) {
454
+ const routes = Object.entries(dictionary).map(([id, [a, b, has_shadow]]) => {
455
+ const { pattern, names, types } = parse_route_id(id);
456
+
457
+ return {
458
+ id,
459
+ /** @param {string} path */
460
+ exec: (path) => {
461
+ const match = pattern.exec(path);
462
+ if (match) return exec(match, names, types, matchers);
463
+ },
464
+ a: a.map((n) => components[n]),
465
+ b: b.map((n) => components[n]),
466
+ has_shadow: !!has_shadow
467
+ };
468
+ });
469
+
470
+ return routes;
471
+ }
472
+
473
+ const SCROLL_KEY = 'sveltekit:scroll';
474
+ const INDEX_KEY = 'sveltekit:index';
475
+
476
+ const routes = parse(components, dictionary, matchers);
477
+
478
+ // we import the root layout/error components eagerly, so that
479
+ // connectivity errors after initialisation don't nuke the app
480
+ const default_layout = components[0]();
481
+ const default_error = components[1]();
482
+
483
+ const root_stuff = {};
484
+
485
+ // We track the scroll position associated with each history entry in sessionStorage,
486
+ // rather than on history.state itself, because when navigation is driven by
487
+ // popstate it's too late to update the scroll position associated with the
488
+ // state we're navigating from
489
+
490
+ /** @typedef {{ x: number, y: number }} ScrollPosition */
491
+ /** @type {Record<number, ScrollPosition>} */
492
+ let scroll_positions = {};
493
+ try {
494
+ scroll_positions = JSON.parse(sessionStorage[SCROLL_KEY]);
495
+ } catch {
496
+ // do nothing
497
+ }
498
+
499
+ /** @param {number} index */
500
+ function update_scroll_positions(index) {
501
+ scroll_positions[index] = scroll_state();
502
+ }
503
+
504
+ /**
505
+ * @param {{
506
+ * target: Element;
507
+ * session: App.Session;
508
+ * base: string;
509
+ * trailing_slash: import('types').TrailingSlash;
510
+ * }} opts
511
+ * @returns {import('./types').Client}
512
+ */
513
+ function create_client({ target, session, base, trailing_slash }) {
514
+ /** @type {Map<string, import('./types').NavigationResult>} */
515
+ const cache = new Map();
516
+
517
+ /** @type {Array<((href: string) => boolean)>} */
518
+ const invalidated = [];
519
+
520
+ const stores = {
521
+ url: notifiable_store({}),
522
+ page: notifiable_store({}),
523
+ navigating: writable(/** @type {import('types').Navigation | null} */ (null)),
524
+ session: writable(session),
525
+ updated: create_updated_store()
526
+ };
527
+
528
+ /** @type {{id: string | null, promise: Promise<import('./types').NavigationResult | undefined> | null}} */
529
+ const load_cache = {
530
+ id: null,
531
+ promise: null
532
+ };
533
+
534
+ const callbacks = {
535
+ /** @type {Array<(opts: { from: URL, to: URL | null, cancel: () => void }) => void>} */
536
+ before_navigate: [],
537
+
538
+ /** @type {Array<(opts: { from: URL | null, to: URL }) => void>} */
539
+ after_navigate: []
540
+ };
541
+
542
+ /** @type {import('./types').NavigationState} */
543
+ let current = {
544
+ branch: [],
545
+ error: null,
546
+ session_id: 0,
547
+ stuff: root_stuff,
548
+ // @ts-ignore - we need the initial value to be null
549
+ url: null
550
+ };
551
+
552
+ let started = false;
553
+ let autoscroll = true;
554
+ let updating = false;
555
+ let session_id = 1;
556
+
557
+ /** @type {Promise<void> | null} */
558
+ let invalidating = null;
559
+
560
+ /** @type {import('svelte').SvelteComponent} */
561
+ let root;
562
+
563
+ /** @type {App.Session} */
564
+ let $session;
565
+
566
+ let ready = false;
567
+ stores.session.subscribe(async (value) => {
568
+ $session = value;
569
+
570
+ if (!ready) return;
571
+ session_id += 1;
572
+
573
+ const current_load_uses_session = current.branch.some((node) => node?.uses.session);
574
+ if (!current_load_uses_session) return;
575
+
576
+ update(new URL(location.href), [], true);
577
+ });
578
+ ready = true;
579
+
580
+ let router_enabled = true;
581
+
582
+ // keeping track of the history index in order to prevent popstate navigation events if needed
583
+ let current_history_index = history.state?.[INDEX_KEY];
584
+
585
+ if (!current_history_index) {
586
+ // we use Date.now() as an offset so that cross-document navigations
587
+ // within the app don't result in data loss
588
+ current_history_index = Date.now();
589
+
590
+ // create initial history entry, so we can return here
591
+ history.replaceState(
592
+ { ...history.state, [INDEX_KEY]: current_history_index },
593
+ '',
594
+ location.href
595
+ );
596
+ }
597
+
598
+ // if we reload the page, or Cmd-Shift-T back to it,
599
+ // recover scroll position
600
+ const scroll = scroll_positions[current_history_index];
601
+ if (scroll) {
602
+ history.scrollRestoration = 'manual';
603
+ scrollTo(scroll.x, scroll.y);
604
+ }
605
+
606
+ let hash_navigating = false;
607
+
608
+ /** @type {import('types').Page} */
609
+ let page;
610
+
611
+ /** @type {{}} */
612
+ let token;
613
+
614
+ /**
615
+ * @param {string | URL} url
616
+ * @param {{ noscroll?: boolean; replaceState?: boolean; keepfocus?: boolean; state?: any }} opts
617
+ * @param {string[]} redirect_chain
618
+ */
619
+ async function goto(
620
+ url,
621
+ { noscroll = false, replaceState = false, keepfocus = false, state = {} },
622
+ redirect_chain
623
+ ) {
624
+ if (typeof url === 'string') {
625
+ url = new URL(url, get_base_uri(document));
626
+ }
627
+
628
+ if (router_enabled) {
629
+ return navigate({
630
+ url,
631
+ scroll: noscroll ? scroll_state() : null,
632
+ keepfocus,
633
+ redirect_chain,
634
+ details: {
635
+ state,
636
+ replaceState
637
+ },
638
+ accepted: () => {},
639
+ blocked: () => {}
640
+ });
641
+ }
642
+
643
+ await native_navigation(url);
644
+ }
645
+
646
+ /** @param {URL} url */
647
+ async function prefetch(url) {
648
+ const intent = get_navigation_intent(url);
649
+
650
+ if (!intent) {
651
+ throw new Error('Attempted to prefetch a URL that does not belong to this app');
652
+ }
653
+
654
+ load_cache.promise = load_route(intent, false);
655
+ load_cache.id = intent.id;
656
+
657
+ return load_cache.promise;
658
+ }
659
+
660
+ /**
661
+ * Returns `true` if update completes, `false` if it is aborted
662
+ * @param {URL} url
663
+ * @param {string[]} redirect_chain
664
+ * @param {boolean} no_cache
665
+ * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean, details: { replaceState: boolean, state: any } | null}} [opts]
666
+ * @param {() => void} [callback]
667
+ */
668
+ async function update(url, redirect_chain, no_cache, opts, callback) {
669
+ const intent = get_navigation_intent(url);
670
+
671
+ const current_token = (token = {});
672
+ let navigation_result = intent && (await load_route(intent, no_cache));
673
+
674
+ if (
675
+ !navigation_result &&
676
+ url.origin === location.origin &&
677
+ url.pathname === location.pathname
678
+ ) {
679
+ // this could happen in SPA fallback mode if the user navigated to
680
+ // `/non-existent-page`. if we fall back to reloading the page, it
681
+ // will create an infinite loop. so whereas we normally handle
682
+ // unknown routes by going to the server, in this special case
683
+ // we render a client-side error page instead
684
+ navigation_result = await load_root_error_page({
685
+ status: 404,
686
+ error: new Error(`Not found: ${url.pathname}`),
687
+ url,
688
+ routeId: null
689
+ });
690
+ }
691
+
692
+ if (!navigation_result) {
693
+ await native_navigation(url);
694
+ return false; // unnecessary, but TypeScript prefers it this way
695
+ }
696
+
697
+ // if this is an internal navigation intent, use the normalized
698
+ // URL for the rest of the function
699
+ url = intent?.url || url;
700
+
701
+ // abort if user navigated during update
702
+ if (token !== current_token) return false;
703
+
704
+ invalidated.length = 0;
705
+
706
+ if (navigation_result.redirect) {
707
+ if (redirect_chain.length > 10 || redirect_chain.includes(url.pathname)) {
708
+ navigation_result = await load_root_error_page({
709
+ status: 500,
710
+ error: new Error('Redirect loop'),
711
+ url,
712
+ routeId: null
713
+ });
714
+ } else {
715
+ if (router_enabled) {
716
+ goto(new URL(navigation_result.redirect, url).href, {}, [
717
+ ...redirect_chain,
718
+ url.pathname
719
+ ]);
720
+ } else {
721
+ await native_navigation(new URL(navigation_result.redirect, location.href));
722
+ }
723
+
724
+ return false;
725
+ }
726
+ } else if (navigation_result.props?.page?.status >= 400) {
727
+ const updated = await stores.updated.check();
728
+ if (updated) {
729
+ await native_navigation(url);
730
+ }
731
+ }
732
+
733
+ updating = true;
734
+
735
+ if (opts && opts.details) {
736
+ const { details } = opts;
737
+ const change = details.replaceState ? 0 : 1;
738
+ details.state[INDEX_KEY] = current_history_index += change;
739
+ history[details.replaceState ? 'replaceState' : 'pushState'](details.state, '', url);
740
+ }
741
+
742
+ if (started) {
743
+ current = navigation_result.state;
744
+
745
+ if (navigation_result.props.page) {
746
+ navigation_result.props.page.url = url;
747
+ }
748
+
749
+ root.$set(navigation_result.props);
750
+ } else {
751
+ initialize(navigation_result);
752
+ }
753
+
754
+ // opts must be passed if we're navigating
755
+ if (opts) {
756
+ const { scroll, keepfocus } = opts;
757
+
758
+ if (!keepfocus) {
759
+ // Reset page selection and focus
760
+ // We try to mimic browsers' behaviour as closely as possible by targeting the
761
+ // first scrollable region, but unfortunately it's not a perfect match — e.g.
762
+ // shift-tabbing won't immediately cycle up from the end of the page on Chromium
763
+ // See https://html.spec.whatwg.org/multipage/interaction.html#get-the-focusable-area
764
+ const root = document.body;
765
+ const tabindex = root.getAttribute('tabindex');
766
+
767
+ root.tabIndex = -1;
768
+ root.focus({ preventScroll: true });
769
+
770
+ setTimeout(() => {
771
+ getSelection()?.removeAllRanges();
772
+ });
773
+
774
+ // restore `tabindex` as to prevent `root` from stealing input from elements
775
+ if (tabindex !== null) {
776
+ root.setAttribute('tabindex', tabindex);
777
+ } else {
778
+ root.removeAttribute('tabindex');
779
+ }
780
+ }
781
+
782
+ // need to render the DOM before we can scroll to the rendered elements
783
+ await tick();
784
+
785
+ if (autoscroll) {
786
+ const deep_linked = url.hash && document.getElementById(url.hash.slice(1));
787
+ if (scroll) {
788
+ scrollTo(scroll.x, scroll.y);
789
+ } else if (deep_linked) {
790
+ // Here we use `scrollIntoView` on the element instead of `scrollTo`
791
+ // because it natively supports the `scroll-margin` and `scroll-behavior`
792
+ // CSS properties.
793
+ deep_linked.scrollIntoView();
794
+ } else {
795
+ scrollTo(0, 0);
796
+ }
797
+ }
798
+ } else {
799
+ // in this case we're simply invalidating
800
+ await tick();
801
+ }
802
+
803
+ load_cache.promise = null;
804
+ load_cache.id = null;
805
+ autoscroll = true;
806
+
807
+ if (navigation_result.props.page) {
808
+ page = navigation_result.props.page;
809
+ }
810
+
811
+ const leaf_node = navigation_result.state.branch[navigation_result.state.branch.length - 1];
812
+ router_enabled = leaf_node?.module.router !== false;
813
+
814
+ if (callback) callback();
815
+
816
+ updating = false;
817
+ }
818
+
819
+ /** @param {import('./types').NavigationResult} result */
820
+ function initialize(result) {
821
+ current = result.state;
822
+
823
+ const style = document.querySelector('style[data-sveltekit]');
824
+ if (style) style.remove();
825
+
826
+ page = result.props.page;
827
+
828
+ root = new Root({
829
+ target,
830
+ props: { ...result.props, stores },
831
+ hydrate: true
832
+ });
833
+
834
+ if (router_enabled) {
835
+ const navigation = { from: null, to: new URL(location.href) };
836
+ callbacks.after_navigate.forEach((fn) => fn(navigation));
837
+ }
838
+
839
+ started = true;
840
+ }
841
+
842
+ /**
843
+ *
844
+ * @param {{
845
+ * url: URL;
846
+ * params: Record<string, string>;
847
+ * stuff: Record<string, any>;
848
+ * branch: Array<import('./types').BranchNode | undefined>;
849
+ * status: number;
850
+ * error: Error | null;
851
+ * routeId: string | null;
852
+ * }} opts
853
+ */
854
+ async function get_navigation_result_from_branch({
855
+ url,
856
+ params,
857
+ stuff,
858
+ branch,
859
+ status,
860
+ error,
861
+ routeId
862
+ }) {
863
+ const filtered = /** @type {import('./types').BranchNode[] } */ (branch.filter(Boolean));
864
+ const redirect = filtered.find((f) => f.loaded?.redirect);
865
+
866
+ /** @type {import('./types').NavigationResult} */
867
+ const result = {
868
+ redirect: redirect?.loaded?.redirect,
869
+ state: {
870
+ url,
871
+ params,
872
+ branch,
873
+ error,
874
+ stuff,
875
+ session_id
876
+ },
877
+ props: {
878
+ components: filtered.map((node) => node.module.default)
879
+ }
880
+ };
881
+
882
+ for (let i = 0; i < filtered.length; i += 1) {
883
+ // Only set props if the node actually updated. This prevents needless rerenders.
884
+ if (!current.branch.some((node) => node === filtered[i])) {
885
+ const loaded = filtered[i].loaded;
886
+ result.props[`props_${i}`] = loaded ? await loaded.props : null;
887
+ }
888
+ }
889
+
890
+ const page_changed =
891
+ !current.url ||
892
+ url.href !== current.url.href ||
893
+ current.error !== error ||
894
+ current.stuff !== stuff;
895
+
896
+ if (page_changed) {
897
+ result.props.page = { error, params, routeId, status, stuff, url };
898
+
899
+ // TODO remove this for 1.0
900
+ /**
901
+ * @param {string} property
902
+ * @param {string} replacement
903
+ */
904
+ const print_error = (property, replacement) => {
905
+ Object.defineProperty(result.props.page, property, {
906
+ get: () => {
907
+ throw new Error(`$page.${property} has been replaced by $page.url.${replacement}`);
908
+ }
909
+ });
910
+ };
911
+
912
+ print_error('origin', 'origin');
913
+ print_error('path', 'pathname');
914
+ print_error('query', 'searchParams');
915
+ }
916
+
917
+ const leaf = filtered[filtered.length - 1];
918
+ const load_cache = leaf?.loaded?.cache;
919
+
920
+ if (load_cache) {
921
+ const key = url.pathname + url.search; // omit hash
922
+ let ready = false;
923
+
924
+ const clear = () => {
925
+ if (cache.get(key) === result) {
926
+ cache.delete(key);
927
+ }
928
+
929
+ unsubscribe();
930
+ clearTimeout(timeout);
931
+ };
932
+
933
+ const timeout = setTimeout(clear, load_cache.maxage * 1000);
934
+
935
+ const unsubscribe = stores.session.subscribe(() => {
936
+ if (ready) clear();
937
+ });
938
+
939
+ ready = true;
940
+
941
+ cache.set(key, result);
942
+ }
943
+
944
+ return result;
945
+ }
946
+
947
+ /**
948
+ * @param {{
949
+ * status?: number;
950
+ * error?: Error;
951
+ * module: import('types').CSRComponent;
952
+ * url: URL;
953
+ * params: Record<string, string>;
954
+ * stuff: Record<string, any>;
955
+ * props?: Record<string, any>;
956
+ * routeId: string | null;
957
+ * }} options
958
+ */
959
+ async function load_node({ status, error, module, url, params, stuff, props, routeId }) {
960
+ /** @type {import('./types').BranchNode} */
961
+ const node = {
962
+ module,
963
+ uses: {
964
+ params: new Set(),
965
+ url: false,
966
+ session: false,
967
+ stuff: false,
968
+ dependencies: new Set()
969
+ },
970
+ loaded: null,
971
+ stuff
972
+ };
973
+
974
+ /** @param dep {string} */
975
+ function add_dependency(dep) {
976
+ const { href } = new URL(dep, url);
977
+ node.uses.dependencies.add(href);
978
+ }
979
+
980
+ if (props) {
981
+ // shadow endpoint props means we need to mark this URL as a dependency of itself
982
+ node.uses.dependencies.add(url.href);
983
+ }
984
+
985
+ /** @type {Record<string, string>} */
986
+ const uses_params = {};
987
+ for (const key in params) {
988
+ Object.defineProperty(uses_params, key, {
989
+ get() {
990
+ node.uses.params.add(key);
991
+ return params[key];
992
+ },
993
+ enumerable: true
994
+ });
995
+ }
996
+
997
+ const session = $session;
998
+ const load_url = new LoadURL(url);
999
+
1000
+ if (module.load) {
1001
+ /** @type {import('types').LoadEvent} */
1002
+ const load_input = {
1003
+ routeId,
1004
+ params: uses_params,
1005
+ props: props || {},
1006
+ get url() {
1007
+ node.uses.url = true;
1008
+ return load_url;
1009
+ },
1010
+ get session() {
1011
+ node.uses.session = true;
1012
+ return session;
1013
+ },
1014
+ get stuff() {
1015
+ node.uses.stuff = true;
1016
+ return { ...stuff };
1017
+ },
1018
+ async fetch(resource, init) {
1019
+ let requested;
1020
+
1021
+ if (typeof resource === 'string') {
1022
+ requested = resource;
1023
+ } else {
1024
+ requested = resource.url;
1025
+
1026
+ // we're not allowed to modify the received `Request` object, so in order
1027
+ // to fixup relative urls we create a new equivalent `init` object instead
1028
+ init = {
1029
+ // the request body must be consumed in memory until browsers
1030
+ // implement streaming request bodies and/or the body getter
1031
+ body:
1032
+ resource.method === 'GET' || resource.method === 'HEAD'
1033
+ ? undefined
1034
+ : await resource.blob(),
1035
+ cache: resource.cache,
1036
+ credentials: resource.credentials,
1037
+ headers: resource.headers,
1038
+ integrity: resource.integrity,
1039
+ keepalive: resource.keepalive,
1040
+ method: resource.method,
1041
+ mode: resource.mode,
1042
+ redirect: resource.redirect,
1043
+ referrer: resource.referrer,
1044
+ referrerPolicy: resource.referrerPolicy,
1045
+ signal: resource.signal,
1046
+ ...init
1047
+ };
1048
+ }
1049
+
1050
+ // we must fixup relative urls so they are resolved from the target page
1051
+ const normalized = new URL(requested, url).href;
1052
+ add_dependency(normalized);
1053
+
1054
+ // prerendered pages may be served from any origin, so `initial_fetch` urls shouldn't be normalized
1055
+ return started ? native_fetch(normalized, init) : initial_fetch(requested, init);
1056
+ },
1057
+ status: status ?? null,
1058
+ error: error ?? null
1059
+ };
1060
+
1061
+ if (import.meta.env.DEV) {
1062
+ // TODO remove this for 1.0
1063
+ Object.defineProperty(load_input, 'page', {
1064
+ get: () => {
1065
+ throw new Error('`page` in `load` functions has been replaced by `url` and `params`');
1066
+ }
1067
+ });
1068
+ }
1069
+
1070
+ if (import.meta.env.DEV) {
1071
+ try {
1072
+ lock_fetch();
1073
+ node.loaded = normalize(await module.load.call(null, load_input));
1074
+ } finally {
1075
+ unlock_fetch();
1076
+ }
1077
+ } else {
1078
+ node.loaded = normalize(await module.load.call(null, load_input));
1079
+ }
1080
+
1081
+ if (node.loaded.stuff) node.stuff = node.loaded.stuff;
1082
+ if (node.loaded.dependencies) {
1083
+ node.loaded.dependencies.forEach(add_dependency);
1084
+ }
1085
+ } else if (props) {
1086
+ node.loaded = normalize({ props });
1087
+ }
1088
+
1089
+ return node;
1090
+ }
1091
+
1092
+ /**
1093
+ * @param {import('./types').NavigationIntent} intent
1094
+ * @param {boolean} no_cache
1095
+ */
1096
+ async function load_route({ id, url, params, route }, no_cache) {
1097
+ if (load_cache.id === id && load_cache.promise) {
1098
+ return load_cache.promise;
1099
+ }
1100
+
1101
+ if (!no_cache) {
1102
+ const cached = cache.get(id);
1103
+ if (cached) return cached;
1104
+ }
1105
+
1106
+ const { a, b, has_shadow } = route;
1107
+
1108
+ const changed = current.url && {
1109
+ url: id !== current.url.pathname + current.url.search,
1110
+ params: Object.keys(params).filter((key) => current.params[key] !== params[key]),
1111
+ session: session_id !== current.session_id
1112
+ };
1113
+
1114
+ /** @type {Array<import('./types').BranchNode | undefined>} */
1115
+ let branch = [];
1116
+
1117
+ /** @type {Record<string, any>} */
1118
+ let stuff = root_stuff;
1119
+ let stuff_changed = false;
1120
+
1121
+ /** @type {number} */
1122
+ let status = 200;
1123
+
1124
+ /** @type {Error | null} */
1125
+ let error = null;
1126
+
1127
+ // preload modules to avoid waterfall, but handle rejections
1128
+ // so they don't get reported to Sentry et al (we don't need
1129
+ // to act on the failures at this point)
1130
+ a.forEach((loader) => loader().catch(() => {}));
1131
+
1132
+ load: for (let i = 0; i < a.length; i += 1) {
1133
+ /** @type {import('./types').BranchNode | undefined} */
1134
+ let node;
1135
+
1136
+ try {
1137
+ if (!a[i]) continue;
1138
+
1139
+ const module = await a[i]();
1140
+ const previous = current.branch[i];
1141
+
1142
+ const changed_since_last_render =
1143
+ !previous ||
1144
+ module !== previous.module ||
1145
+ (changed.url && previous.uses.url) ||
1146
+ changed.params.some((param) => previous.uses.params.has(param)) ||
1147
+ (changed.session && previous.uses.session) ||
1148
+ Array.from(previous.uses.dependencies).some((dep) => invalidated.some((fn) => fn(dep))) ||
1149
+ (stuff_changed && previous.uses.stuff);
1150
+
1151
+ if (changed_since_last_render) {
1152
+ /** @type {Record<string, any>} */
1153
+ let props = {};
1154
+
1155
+ const is_shadow_page = has_shadow && i === a.length - 1;
1156
+
1157
+ if (is_shadow_page) {
1158
+ const res = await native_fetch(
1159
+ `${url.pathname}${url.pathname.endsWith('/') ? '' : '/'}__data.json${url.search}`,
1160
+ {
1161
+ headers: {
1162
+ 'x-sveltekit-load': 'true'
1163
+ }
1164
+ }
1165
+ );
1166
+
1167
+ if (res.ok) {
1168
+ const redirect = res.headers.get('x-sveltekit-location');
1169
+
1170
+ if (redirect) {
1171
+ return {
1172
+ redirect,
1173
+ props: {},
1174
+ state: current
1175
+ };
1176
+ }
1177
+
1178
+ props = res.status === 204 ? {} : await res.json();
1179
+ } else {
1180
+ status = res.status;
1181
+ try {
1182
+ error = await res.json();
1183
+ } catch (e) {
1184
+ error = new Error('Failed to load data');
1185
+ }
1186
+ }
1187
+ }
1188
+
1189
+ if (!error) {
1190
+ node = await load_node({
1191
+ module,
1192
+ url,
1193
+ params,
1194
+ props,
1195
+ stuff,
1196
+ routeId: route.id
1197
+ });
1198
+ }
1199
+
1200
+ if (node) {
1201
+ if (is_shadow_page) {
1202
+ node.uses.url = true;
1203
+ }
1204
+
1205
+ if (node.loaded) {
1206
+ if (node.loaded.error) {
1207
+ status = node.loaded.status ?? 500;
1208
+ error = node.loaded.error;
1209
+ }
1210
+
1211
+ if (node.loaded.redirect) {
1212
+ return {
1213
+ redirect: node.loaded.redirect,
1214
+ props: {},
1215
+ state: current
1216
+ };
1217
+ }
1218
+
1219
+ if (node.loaded.stuff) {
1220
+ stuff_changed = true;
1221
+ }
1222
+ }
1223
+ }
1224
+ } else {
1225
+ node = previous;
1226
+ }
1227
+ } catch (e) {
1228
+ status = 500;
1229
+ error = coalesce_to_error(e);
1230
+ }
1231
+
1232
+ if (error) {
1233
+ while (i--) {
1234
+ if (b[i]) {
1235
+ let error_loaded;
1236
+
1237
+ /** @type {import('./types').BranchNode | undefined} */
1238
+ let node_loaded;
1239
+ let j = i;
1240
+ while (!(node_loaded = branch[j])) {
1241
+ j -= 1;
1242
+ }
1243
+
1244
+ try {
1245
+ error_loaded = await load_node({
1246
+ status,
1247
+ error,
1248
+ module: await b[i](),
1249
+ url,
1250
+ params,
1251
+ stuff: node_loaded.stuff,
1252
+ routeId: route.id
1253
+ });
1254
+
1255
+ if (error_loaded?.loaded?.error) {
1256
+ continue;
1257
+ }
1258
+
1259
+ if (error_loaded?.loaded?.stuff) {
1260
+ stuff = {
1261
+ ...stuff,
1262
+ ...error_loaded.loaded.stuff
1263
+ };
1264
+ }
1265
+
1266
+ branch = branch.slice(0, j + 1).concat(error_loaded);
1267
+ break load;
1268
+ } catch (e) {
1269
+ continue;
1270
+ }
1271
+ }
1272
+ }
1273
+
1274
+ return await load_root_error_page({
1275
+ status,
1276
+ error,
1277
+ url,
1278
+ routeId: route.id
1279
+ });
1280
+ } else {
1281
+ if (node?.loaded?.stuff) {
1282
+ stuff = {
1283
+ ...stuff,
1284
+ ...node.loaded.stuff
1285
+ };
1286
+ }
1287
+
1288
+ branch.push(node);
1289
+ }
1290
+ }
1291
+
1292
+ return await get_navigation_result_from_branch({
1293
+ url,
1294
+ params,
1295
+ stuff,
1296
+ branch,
1297
+ status,
1298
+ error,
1299
+ routeId: route.id
1300
+ });
1301
+ }
1302
+
1303
+ /**
1304
+ * @param {{
1305
+ * status: number;
1306
+ * error: Error;
1307
+ * url: URL;
1308
+ * routeId: string | null
1309
+ * }} opts
1310
+ */
1311
+ async function load_root_error_page({ status, error, url, routeId }) {
1312
+ /** @type {Record<string, string>} */
1313
+ const params = {}; // error page does not have params
1314
+
1315
+ const root_layout = await load_node({
1316
+ module: await default_layout,
1317
+ url,
1318
+ params,
1319
+ stuff: {},
1320
+ routeId
1321
+ });
1322
+
1323
+ const root_error = await load_node({
1324
+ status,
1325
+ error,
1326
+ module: await default_error,
1327
+ url,
1328
+ params,
1329
+ stuff: (root_layout && root_layout.loaded && root_layout.loaded.stuff) || {},
1330
+ routeId
1331
+ });
1332
+
1333
+ return await get_navigation_result_from_branch({
1334
+ url,
1335
+ params,
1336
+ stuff: {
1337
+ ...root_layout?.loaded?.stuff,
1338
+ ...root_error?.loaded?.stuff
1339
+ },
1340
+ branch: [root_layout, root_error],
1341
+ status,
1342
+ error,
1343
+ routeId
1344
+ });
1345
+ }
1346
+
1347
+ /** @param {URL} url */
1348
+ function get_navigation_intent(url) {
1349
+ if (url.origin !== location.origin || !url.pathname.startsWith(base)) return;
1350
+
1351
+ const path = decodeURI(url.pathname.slice(base.length) || '/');
1352
+
1353
+ for (const route of routes) {
1354
+ const params = route.exec(path);
1355
+
1356
+ if (params) {
1357
+ const normalized = new URL(
1358
+ url.origin + normalize_path(url.pathname, trailing_slash) + url.search + url.hash
1359
+ );
1360
+ const id = normalized.pathname + normalized.search;
1361
+ /** @type {import('./types').NavigationIntent} */
1362
+ const intent = { id, route, params: decode_params(params), url: normalized };
1363
+ return intent;
1364
+ }
1365
+ }
1366
+ }
1367
+
1368
+ /**
1369
+ * @param {{
1370
+ * url: URL;
1371
+ * scroll: { x: number, y: number } | null;
1372
+ * keepfocus: boolean;
1373
+ * redirect_chain: string[];
1374
+ * details: {
1375
+ * replaceState: boolean;
1376
+ * state: any;
1377
+ * } | null;
1378
+ * accepted: () => void;
1379
+ * blocked: () => void;
1380
+ * }} opts
1381
+ */
1382
+ async function navigate({ url, scroll, keepfocus, redirect_chain, details, accepted, blocked }) {
1383
+ const from = current.url;
1384
+ let should_block = false;
1385
+
1386
+ const navigation = {
1387
+ from,
1388
+ to: url,
1389
+ cancel: () => (should_block = true)
1390
+ };
1391
+
1392
+ callbacks.before_navigate.forEach((fn) => fn(navigation));
1393
+
1394
+ if (should_block) {
1395
+ blocked();
1396
+ return;
1397
+ }
1398
+
1399
+ update_scroll_positions(current_history_index);
1400
+
1401
+ accepted();
1402
+
1403
+ if (started) {
1404
+ stores.navigating.set({
1405
+ from: current.url,
1406
+ to: url
1407
+ });
1408
+ }
1409
+
1410
+ await update(
1411
+ url,
1412
+ redirect_chain,
1413
+ false,
1414
+ {
1415
+ scroll,
1416
+ keepfocus,
1417
+ details
1418
+ },
1419
+ () => {
1420
+ const navigation = { from, to: url };
1421
+ callbacks.after_navigate.forEach((fn) => fn(navigation));
1422
+
1423
+ stores.navigating.set(null);
1424
+ }
1425
+ );
1426
+ }
1427
+
1428
+ /**
1429
+ * Loads `href` the old-fashioned way, with a full page reload.
1430
+ * Returns a `Promise` that never resolves (to prevent any
1431
+ * subsequent work, e.g. history manipulation, from happening)
1432
+ * @param {URL} url
1433
+ */
1434
+ function native_navigation(url) {
1435
+ location.href = url.href;
1436
+ return new Promise(() => {});
1437
+ }
1438
+
1439
+ if (import.meta.hot) {
1440
+ import.meta.hot.on('vite:beforeUpdate', () => {
1441
+ if (current.error) location.reload();
1442
+ });
1443
+ }
1444
+
1445
+ return {
1446
+ after_navigate: (fn) => {
1447
+ onMount(() => {
1448
+ callbacks.after_navigate.push(fn);
1449
+
1450
+ return () => {
1451
+ const i = callbacks.after_navigate.indexOf(fn);
1452
+ callbacks.after_navigate.splice(i, 1);
1453
+ };
1454
+ });
1455
+ },
1456
+
1457
+ before_navigate: (fn) => {
1458
+ onMount(() => {
1459
+ callbacks.before_navigate.push(fn);
1460
+
1461
+ return () => {
1462
+ const i = callbacks.before_navigate.indexOf(fn);
1463
+ callbacks.before_navigate.splice(i, 1);
1464
+ };
1465
+ });
1466
+ },
1467
+
1468
+ disable_scroll_handling: () => {
1469
+ if (import.meta.env.DEV && started && !updating) {
1470
+ throw new Error('Can only disable scroll handling during navigation');
1471
+ }
1472
+
1473
+ if (updating || !started) {
1474
+ autoscroll = false;
1475
+ }
1476
+ },
1477
+
1478
+ goto: (href, opts = {}) => goto(href, opts, []),
1479
+
1480
+ invalidate: (resource) => {
1481
+ if (typeof resource === 'function') {
1482
+ invalidated.push(resource);
1483
+ } else {
1484
+ const { href } = new URL(resource, location.href);
1485
+ invalidated.push((dep) => dep === href);
1486
+ }
1487
+
1488
+ if (!invalidating) {
1489
+ invalidating = Promise.resolve().then(async () => {
1490
+ await update(new URL(location.href), [], true);
1491
+
1492
+ invalidating = null;
1493
+ });
1494
+ }
1495
+
1496
+ return invalidating;
1497
+ },
1498
+
1499
+ prefetch: async (href) => {
1500
+ const url = new URL(href, get_base_uri(document));
1501
+ await prefetch(url);
1502
+ },
1503
+
1504
+ // TODO rethink this API
1505
+ prefetch_routes: async (pathnames) => {
1506
+ const matching = pathnames
1507
+ ? routes.filter((route) => pathnames.some((pathname) => route.exec(pathname)))
1508
+ : routes;
1509
+
1510
+ const promises = matching.map((r) => Promise.all(r.a.map((load) => load())));
1511
+
1512
+ await Promise.all(promises);
1513
+ },
1514
+
1515
+ _start_router: () => {
1516
+ history.scrollRestoration = 'manual';
1517
+
1518
+ // Adopted from Nuxt.js
1519
+ // Reset scrollRestoration to auto when leaving page, allowing page reload
1520
+ // and back-navigation from other pages to use the browser to restore the
1521
+ // scrolling position.
1522
+ addEventListener('beforeunload', (e) => {
1523
+ let should_block = false;
1524
+
1525
+ const navigation = {
1526
+ from: current.url,
1527
+ to: null,
1528
+ cancel: () => (should_block = true)
1529
+ };
1530
+
1531
+ callbacks.before_navigate.forEach((fn) => fn(navigation));
1532
+
1533
+ if (should_block) {
1534
+ e.preventDefault();
1535
+ e.returnValue = '';
1536
+ } else {
1537
+ history.scrollRestoration = 'auto';
1538
+ }
1539
+ });
1540
+
1541
+ addEventListener('visibilitychange', () => {
1542
+ if (document.visibilityState === 'hidden') {
1543
+ update_scroll_positions(current_history_index);
1544
+
1545
+ try {
1546
+ sessionStorage[SCROLL_KEY] = JSON.stringify(scroll_positions);
1547
+ } catch {
1548
+ // do nothing
1549
+ }
1550
+ }
1551
+ });
1552
+
1553
+ /** @param {Event} event */
1554
+ const trigger_prefetch = (event) => {
1555
+ const a = find_anchor(event);
1556
+ if (a && a.href && a.hasAttribute('sveltekit:prefetch')) {
1557
+ prefetch(get_href(a));
1558
+ }
1559
+ };
1560
+
1561
+ /** @type {NodeJS.Timeout} */
1562
+ let mousemove_timeout;
1563
+
1564
+ /** @param {MouseEvent|TouchEvent} event */
1565
+ const handle_mousemove = (event) => {
1566
+ clearTimeout(mousemove_timeout);
1567
+ mousemove_timeout = setTimeout(() => {
1568
+ // event.composedPath(), which is used in find_anchor, will be empty if the event is read in a timeout
1569
+ // add a layer of indirection to address that
1570
+ event.target?.dispatchEvent(
1571
+ new CustomEvent('sveltekit:trigger_prefetch', { bubbles: true })
1572
+ );
1573
+ }, 20);
1574
+ };
1575
+
1576
+ addEventListener('touchstart', trigger_prefetch);
1577
+ addEventListener('mousemove', handle_mousemove);
1578
+ addEventListener('sveltekit:trigger_prefetch', trigger_prefetch);
1579
+
1580
+ /** @param {MouseEvent} event */
1581
+ addEventListener('click', (event) => {
1582
+ if (!router_enabled) return;
1583
+
1584
+ // Adapted from https://github.com/visionmedia/page.js
1585
+ // MIT license https://github.com/visionmedia/page.js#license
1586
+ if (event.button || event.which !== 1) return;
1587
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
1588
+ if (event.defaultPrevented) return;
1589
+
1590
+ const a = find_anchor(event);
1591
+ if (!a) return;
1592
+
1593
+ if (!a.href) return;
1594
+
1595
+ const is_svg_a_element = a instanceof SVGAElement;
1596
+ const url = get_href(a);
1597
+
1598
+ // Ignore non-HTTP URL protocols (e.g. `mailto:`, `tel:`, `myapp:`, etc.)
1599
+ // MEMO: Without this condition, firefox will open mailer twice.
1600
+ // See:
1601
+ // - https://github.com/sveltejs/kit/issues/4045
1602
+ // - https://github.com/sveltejs/kit/issues/5725
1603
+ if (!is_svg_a_element && !(url.protocol === 'https:' || url.protocol === 'http:')) return;
1604
+
1605
+ // Ignore if tag has
1606
+ // 1. 'download' attribute
1607
+ // 2. 'rel' attribute includes external
1608
+ const rel = (a.getAttribute('rel') || '').split(/\s+/);
1609
+
1610
+ if (
1611
+ a.hasAttribute('download') ||
1612
+ rel.includes('external') ||
1613
+ a.hasAttribute('sveltekit:reload')
1614
+ ) {
1615
+ return;
1616
+ }
1617
+
1618
+ // Ignore if <a> has a target
1619
+ if (is_svg_a_element ? a.target.baseVal : a.target) return;
1620
+
1621
+ // Check if new url only differs by hash and use the browser default behavior in that case
1622
+ // This will ensure the `hashchange` event is fired
1623
+ // Removing the hash does a full page navigation in the browser, so make sure a hash is present
1624
+ const [base, hash] = url.href.split('#');
1625
+ if (hash !== undefined && base === location.href.split('#')[0]) {
1626
+ // set this flag to distinguish between navigations triggered by
1627
+ // clicking a hash link and those triggered by popstate
1628
+ hash_navigating = true;
1629
+
1630
+ update_scroll_positions(current_history_index);
1631
+
1632
+ stores.page.set({ ...page, url });
1633
+ stores.page.notify();
1634
+
1635
+ return;
1636
+ }
1637
+
1638
+ navigate({
1639
+ url,
1640
+ scroll: a.hasAttribute('sveltekit:noscroll') ? scroll_state() : null,
1641
+ keepfocus: false,
1642
+ redirect_chain: [],
1643
+ details: {
1644
+ state: {},
1645
+ replaceState: url.href === location.href
1646
+ },
1647
+ accepted: () => event.preventDefault(),
1648
+ blocked: () => event.preventDefault()
1649
+ });
1650
+ });
1651
+
1652
+ addEventListener('popstate', (event) => {
1653
+ if (event.state && router_enabled) {
1654
+ // if a popstate-driven navigation is cancelled, we need to counteract it
1655
+ // with history.go, which means we end up back here, hence this check
1656
+ if (event.state[INDEX_KEY] === current_history_index) return;
1657
+
1658
+ navigate({
1659
+ url: new URL(location.href),
1660
+ scroll: scroll_positions[event.state[INDEX_KEY]],
1661
+ keepfocus: false,
1662
+ redirect_chain: [],
1663
+ details: null,
1664
+ accepted: () => {
1665
+ current_history_index = event.state[INDEX_KEY];
1666
+ },
1667
+ blocked: () => {
1668
+ const delta = current_history_index - event.state[INDEX_KEY];
1669
+ history.go(delta);
1670
+ }
1671
+ });
1672
+ }
1673
+ });
1674
+
1675
+ addEventListener('hashchange', () => {
1676
+ // if the hashchange happened as a result of clicking on a link,
1677
+ // we need to update history, otherwise we have to leave it alone
1678
+ if (hash_navigating) {
1679
+ hash_navigating = false;
1680
+ history.replaceState(
1681
+ { ...history.state, [INDEX_KEY]: ++current_history_index },
1682
+ '',
1683
+ location.href
1684
+ );
1685
+ }
1686
+ });
1687
+
1688
+ // fix link[rel=icon], because browsers will occasionally try to load relative
1689
+ // URLs after a pushState/replaceState, resulting in a 404 — see
1690
+ // https://github.com/sveltejs/kit/issues/3748#issuecomment-1125980897
1691
+ for (const link of document.querySelectorAll('link')) {
1692
+ if (link.rel === 'icon') link.href = link.href;
1693
+ }
1694
+
1695
+ addEventListener('pageshow', (event) => {
1696
+ // If the user navigates to another site and then uses the back button and
1697
+ // bfcache hits, we need to set navigating to null, the site doesn't know
1698
+ // the navigation away from it was successful.
1699
+ // Info about bfcache here: https://web.dev/bfcache
1700
+ if (event.persisted) {
1701
+ stores.navigating.set(null);
1702
+ }
1703
+ });
1704
+ },
1705
+
1706
+ _hydrate: async ({ status, error, nodes, params, routeId }) => {
1707
+ const url = new URL(location.href);
1708
+
1709
+ /** @type {Array<import('./types').BranchNode | undefined>} */
1710
+ const branch = [];
1711
+
1712
+ /** @type {Record<string, any>} */
1713
+ let stuff = {};
1714
+
1715
+ /** @type {import('./types').NavigationResult | undefined} */
1716
+ let result;
1717
+
1718
+ let error_args;
1719
+
1720
+ try {
1721
+ for (let i = 0; i < nodes.length; i += 1) {
1722
+ const is_leaf = i === nodes.length - 1;
1723
+
1724
+ let props;
1725
+
1726
+ if (is_leaf) {
1727
+ const serialized = document.querySelector('script[sveltekit\\:data-type="props"]');
1728
+ if (serialized) {
1729
+ props = JSON.parse(/** @type {string} */ (serialized.textContent));
1730
+ }
1731
+ }
1732
+
1733
+ const node = await load_node({
1734
+ module: await components[nodes[i]](),
1735
+ url,
1736
+ params,
1737
+ stuff,
1738
+ status: is_leaf ? status : undefined,
1739
+ error: is_leaf ? error : undefined,
1740
+ props,
1741
+ routeId
1742
+ });
1743
+
1744
+ if (props) {
1745
+ node.uses.dependencies.add(url.href);
1746
+ node.uses.url = true;
1747
+ }
1748
+
1749
+ branch.push(node);
1750
+
1751
+ if (node && node.loaded) {
1752
+ if (node.loaded.error) {
1753
+ if (error) throw node.loaded.error;
1754
+ error_args = {
1755
+ status: node.loaded.status ?? 500,
1756
+ error: node.loaded.error,
1757
+ url,
1758
+ routeId
1759
+ };
1760
+ } else if (node.loaded.stuff) {
1761
+ stuff = {
1762
+ ...stuff,
1763
+ ...node.loaded.stuff
1764
+ };
1765
+ }
1766
+ }
1767
+ }
1768
+
1769
+ result = error_args
1770
+ ? await load_root_error_page(error_args)
1771
+ : await get_navigation_result_from_branch({
1772
+ url,
1773
+ params,
1774
+ stuff,
1775
+ branch,
1776
+ status,
1777
+ error,
1778
+ routeId
1779
+ });
1780
+ } catch (e) {
1781
+ if (error) throw e;
1782
+
1783
+ result = await load_root_error_page({
1784
+ status: 500,
1785
+ error: coalesce_to_error(e),
1786
+ url,
1787
+ routeId
1788
+ });
1789
+ }
1790
+
1791
+ if (result.redirect) {
1792
+ // this is a real edge case — `load` would need to return
1793
+ // a redirect but only in the browser
1794
+ await native_navigation(new URL(result.redirect, location.href));
1795
+ }
1796
+
1797
+ initialize(result);
1798
+ }
1799
+ };
1800
+ }
1801
+
1802
+ /**
1803
+ * @param {{
1804
+ * paths: {
1805
+ * assets: string;
1806
+ * base: string;
1807
+ * },
1808
+ * target: Element;
1809
+ * session: any;
1810
+ * route: boolean;
1811
+ * spa: boolean;
1812
+ * trailing_slash: import('types').TrailingSlash;
1813
+ * hydrate: {
1814
+ * status: number;
1815
+ * error: Error;
1816
+ * nodes: number[];
1817
+ * params: Record<string, string>;
1818
+ * routeId: string | null;
1819
+ * };
1820
+ * }} opts
1821
+ */
1822
+ async function start({ paths, target, session, route, spa, trailing_slash, hydrate }) {
1823
+ const client = create_client({
1824
+ target,
1825
+ session,
1826
+ base: paths.base,
1827
+ trailing_slash
1828
+ });
1829
+
1830
+ init({ client });
1831
+ set_paths(paths);
1832
+
1833
+ if (hydrate) {
1834
+ await client._hydrate(hydrate);
1835
+ }
1836
+
1837
+ if (route) {
1838
+ if (spa) client.goto(location.href, { replaceState: true });
1839
+ client._start_router();
1840
+ }
1841
+
1842
+ dispatchEvent(new CustomEvent('sveltekit:start'));
1843
+ }
1844
+
1845
+ export { start };