@sveltejs/kit 1.0.0-next.52 → 1.0.0-next.520

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 (128) hide show
  1. package/README.md +6 -3
  2. package/package.json +93 -67
  3. package/postinstall.js +47 -0
  4. package/scripts/special-types/$env+dynamic+private.md +10 -0
  5. package/scripts/special-types/$env+dynamic+public.md +8 -0
  6. package/scripts/special-types/$env+static+private.md +19 -0
  7. package/scripts/special-types/$env+static+public.md +7 -0
  8. package/scripts/special-types/$lib.md +5 -0
  9. package/src/cli.js +108 -0
  10. package/src/constants.js +7 -0
  11. package/src/core/adapt/builder.js +215 -0
  12. package/src/core/adapt/index.js +31 -0
  13. package/src/core/config/default-error.html +56 -0
  14. package/src/core/config/index.js +110 -0
  15. package/src/core/config/options.js +504 -0
  16. package/src/core/config/types.d.ts +1 -0
  17. package/src/core/env.js +121 -0
  18. package/src/core/generate_manifest/index.js +94 -0
  19. package/src/core/prerender/crawl.js +198 -0
  20. package/src/core/prerender/entities.js +2252 -0
  21. package/src/core/prerender/prerender.js +458 -0
  22. package/src/core/prerender/queue.js +80 -0
  23. package/src/core/sync/create_manifest_data/conflict.js +0 -0
  24. package/src/core/sync/create_manifest_data/index.js +470 -0
  25. package/src/core/sync/create_manifest_data/sort.js +163 -0
  26. package/src/core/sync/create_manifest_data/types.d.ts +37 -0
  27. package/src/core/sync/sync.js +78 -0
  28. package/src/core/sync/utils.js +33 -0
  29. package/src/core/sync/write_ambient.js +53 -0
  30. package/src/core/sync/write_client_manifest.js +106 -0
  31. package/src/core/sync/write_matchers.js +25 -0
  32. package/src/core/sync/write_root.js +91 -0
  33. package/src/core/sync/write_tsconfig.js +195 -0
  34. package/src/core/sync/write_types/index.js +783 -0
  35. package/src/core/utils.js +70 -0
  36. package/src/exports/hooks/index.js +1 -0
  37. package/src/exports/hooks/sequence.js +44 -0
  38. package/src/exports/index.js +45 -0
  39. package/src/exports/node/index.js +161 -0
  40. package/src/exports/node/polyfills.js +28 -0
  41. package/src/exports/vite/build/build_server.js +378 -0
  42. package/src/exports/vite/build/build_service_worker.js +91 -0
  43. package/src/exports/vite/build/utils.js +181 -0
  44. package/src/exports/vite/dev/index.js +581 -0
  45. package/src/exports/vite/graph_analysis/index.js +277 -0
  46. package/src/exports/vite/graph_analysis/types.d.ts +5 -0
  47. package/src/exports/vite/graph_analysis/utils.js +30 -0
  48. package/src/exports/vite/index.js +603 -0
  49. package/src/exports/vite/preview/index.js +189 -0
  50. package/src/exports/vite/types.d.ts +3 -0
  51. package/src/exports/vite/utils.js +157 -0
  52. package/src/runtime/app/env.js +1 -0
  53. package/src/runtime/app/environment.js +11 -0
  54. package/src/runtime/app/forms.js +123 -0
  55. package/src/runtime/app/navigation.js +23 -0
  56. package/src/runtime/app/paths.js +1 -0
  57. package/src/runtime/app/stores.js +102 -0
  58. package/src/runtime/client/ambient.d.ts +30 -0
  59. package/src/runtime/client/client.js +1595 -0
  60. package/src/runtime/client/fetcher.js +107 -0
  61. package/src/runtime/client/parse.js +60 -0
  62. package/src/runtime/client/singletons.js +21 -0
  63. package/src/runtime/client/start.js +37 -0
  64. package/src/runtime/client/types.d.ts +84 -0
  65. package/src/runtime/client/utils.js +159 -0
  66. package/src/runtime/components/error.svelte +16 -0
  67. package/{assets → src/runtime}/components/layout.svelte +0 -0
  68. package/src/runtime/control.js +98 -0
  69. package/src/runtime/env/dynamic/private.js +1 -0
  70. package/src/runtime/env/dynamic/public.js +1 -0
  71. package/src/runtime/env-private.js +6 -0
  72. package/src/runtime/env-public.js +6 -0
  73. package/src/runtime/env.js +6 -0
  74. package/src/runtime/hash.js +20 -0
  75. package/src/runtime/paths.js +11 -0
  76. package/src/runtime/server/cookie.js +166 -0
  77. package/src/runtime/server/data/index.js +131 -0
  78. package/src/runtime/server/endpoint.js +92 -0
  79. package/src/runtime/server/fetch.js +174 -0
  80. package/src/runtime/server/index.js +355 -0
  81. package/src/runtime/server/page/actions.js +256 -0
  82. package/src/runtime/server/page/crypto.js +239 -0
  83. package/src/runtime/server/page/csp.js +250 -0
  84. package/src/runtime/server/page/index.js +304 -0
  85. package/src/runtime/server/page/load_data.js +215 -0
  86. package/src/runtime/server/page/render.js +346 -0
  87. package/src/runtime/server/page/respond_with_error.js +102 -0
  88. package/src/runtime/server/page/serialize_data.js +87 -0
  89. package/src/runtime/server/page/types.d.ts +35 -0
  90. package/src/runtime/server/utils.js +181 -0
  91. package/src/utils/array.js +9 -0
  92. package/src/utils/error.js +22 -0
  93. package/src/utils/escape.js +46 -0
  94. package/src/utils/filesystem.js +142 -0
  95. package/src/utils/functions.js +16 -0
  96. package/src/utils/http.js +72 -0
  97. package/src/utils/misc.js +1 -0
  98. package/src/utils/promises.js +17 -0
  99. package/src/utils/routing.js +130 -0
  100. package/src/utils/unit_test.js +11 -0
  101. package/src/utils/url.js +144 -0
  102. package/svelte-kit.js +1 -1
  103. package/types/ambient.d.ts +431 -0
  104. package/types/index.d.ts +477 -0
  105. package/types/internal.d.ts +380 -0
  106. package/types/private.d.ts +224 -0
  107. package/CHANGELOG.md +0 -496
  108. package/assets/components/error.svelte +0 -13
  109. package/assets/runtime/app/env.js +0 -5
  110. package/assets/runtime/app/navigation.js +0 -44
  111. package/assets/runtime/app/paths.js +0 -1
  112. package/assets/runtime/app/stores.js +0 -93
  113. package/assets/runtime/chunks/utils.js +0 -22
  114. package/assets/runtime/internal/singletons.js +0 -23
  115. package/assets/runtime/internal/start.js +0 -776
  116. package/assets/runtime/paths.js +0 -12
  117. package/dist/chunks/index.js +0 -3537
  118. package/dist/chunks/index2.js +0 -587
  119. package/dist/chunks/index3.js +0 -246
  120. package/dist/chunks/index4.js +0 -568
  121. package/dist/chunks/index5.js +0 -763
  122. package/dist/chunks/index6.js +0 -323
  123. package/dist/chunks/standard.js +0 -99
  124. package/dist/chunks/utils.js +0 -83
  125. package/dist/cli.js +0 -555
  126. package/dist/ssr.js +0 -2604
  127. package/types.d.ts +0 -73
  128. package/types.internal.d.ts +0 -222
@@ -0,0 +1,1595 @@
1
+ import { onMount, tick } from 'svelte';
2
+ import { make_trackable, decode_params, normalize_path } from '../../utils/url.js';
3
+ import { find_anchor, get_base_uri, scroll_state } from './utils.js';
4
+ import {
5
+ lock_fetch,
6
+ unlock_fetch,
7
+ initial_fetch,
8
+ subsequent_fetch,
9
+ native_fetch
10
+ } from './fetcher.js';
11
+ import { parse } from './parse.js';
12
+
13
+ import Root from '__GENERATED__/root.svelte';
14
+ import { nodes, server_loads, dictionary, matchers, hooks } from '__GENERATED__/client-manifest.js';
15
+ import { HttpError, Redirect } from '../control.js';
16
+ import { stores } from './singletons.js';
17
+ import { DATA_SUFFIX } from '../../constants.js';
18
+ import { unwrap_promises } from '../../utils/promises.js';
19
+ import * as devalue from 'devalue';
20
+
21
+ const SCROLL_KEY = 'sveltekit:scroll';
22
+ const INDEX_KEY = 'sveltekit:index';
23
+
24
+ const routes = parse(nodes, server_loads, dictionary, matchers);
25
+
26
+ const default_layout_loader = nodes[0];
27
+ const default_error_loader = nodes[1];
28
+
29
+ // we import the root layout/error nodes eagerly, so that
30
+ // connectivity errors after initialisation don't nuke the app
31
+ default_layout_loader();
32
+ default_error_loader();
33
+
34
+ // We track the scroll position associated with each history entry in sessionStorage,
35
+ // rather than on history.state itself, because when navigation is driven by
36
+ // popstate it's too late to update the scroll position associated with the
37
+ // state we're navigating from
38
+
39
+ /** @typedef {{ x: number, y: number }} ScrollPosition */
40
+ /** @type {Record<number, ScrollPosition>} */
41
+ let scroll_positions = {};
42
+ try {
43
+ scroll_positions = JSON.parse(sessionStorage[SCROLL_KEY]);
44
+ } catch {
45
+ // do nothing
46
+ }
47
+
48
+ /** @param {number} index */
49
+ function update_scroll_positions(index) {
50
+ scroll_positions[index] = scroll_state();
51
+ }
52
+
53
+ // TODO remove for 1.0
54
+ /** @type {Record<string, true>} */
55
+ let warned_about_attributes = {};
56
+
57
+ function check_for_removed_attributes() {
58
+ const attrs = ['prefetch', 'noscroll', 'reload'];
59
+ for (const attr of attrs) {
60
+ if (document.querySelector(`[sveltekit\\:${attr}]`)) {
61
+ if (!warned_about_attributes[attr]) {
62
+ warned_about_attributes[attr] = true;
63
+ console.error(
64
+ `The sveltekit:${attr} attribute has been replaced with data-sveltekit-${attr}`
65
+ );
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ /**
72
+ * @param {{
73
+ * target: Element;
74
+ * base: string;
75
+ * trailing_slash: import('types').TrailingSlash;
76
+ * }} opts
77
+ * @returns {import('./types').Client}
78
+ */
79
+ export function create_client({ target, base, trailing_slash }) {
80
+ /** @type {Array<((url: URL) => boolean)>} */
81
+ const invalidated = [];
82
+
83
+ /** @type {{id: string, promise: Promise<import('./types').NavigationResult | undefined>} | null} */
84
+ let load_cache = null;
85
+
86
+ const callbacks = {
87
+ /** @type {Array<(navigation: import('types').Navigation & { cancel: () => void }) => void>} */
88
+ before_navigate: [],
89
+
90
+ /** @type {Array<(navigation: import('types').Navigation) => void>} */
91
+ after_navigate: []
92
+ };
93
+
94
+ /** @type {import('./types').NavigationState} */
95
+ let current = {
96
+ branch: [],
97
+ error: null,
98
+ // @ts-ignore - we need the initial value to be null
99
+ url: null
100
+ };
101
+
102
+ /** this being true means we SSR'd */
103
+ let hydrated = false;
104
+ let started = false;
105
+ let autoscroll = true;
106
+ let updating = false;
107
+ let force_invalidation = false;
108
+
109
+ /** @type {import('svelte').SvelteComponent} */
110
+ let root;
111
+
112
+ // keeping track of the history index in order to prevent popstate navigation events if needed
113
+ let current_history_index = history.state?.[INDEX_KEY];
114
+
115
+ if (!current_history_index) {
116
+ // we use Date.now() as an offset so that cross-document navigations
117
+ // within the app don't result in data loss
118
+ current_history_index = Date.now();
119
+
120
+ // create initial history entry, so we can return here
121
+ history.replaceState(
122
+ { ...history.state, [INDEX_KEY]: current_history_index },
123
+ '',
124
+ location.href
125
+ );
126
+ }
127
+
128
+ // if we reload the page, or Cmd-Shift-T back to it,
129
+ // recover scroll position
130
+ const scroll = scroll_positions[current_history_index];
131
+ if (scroll) {
132
+ history.scrollRestoration = 'manual';
133
+ scrollTo(scroll.x, scroll.y);
134
+ }
135
+
136
+ let hash_navigating = false;
137
+
138
+ /** @type {import('types').Page} */
139
+ let page;
140
+
141
+ /** @type {{}} */
142
+ let token;
143
+
144
+ /** @type {Promise<void> | null} */
145
+ let pending_invalidate;
146
+
147
+ async function invalidate() {
148
+ // Accept all invalidations as they come, don't swallow any while another invalidation
149
+ // is running because subsequent invalidations may make earlier ones outdated,
150
+ // but batch multiple synchronous invalidations.
151
+ pending_invalidate = pending_invalidate || Promise.resolve();
152
+ await pending_invalidate;
153
+ pending_invalidate = null;
154
+
155
+ const url = new URL(location.href);
156
+ const intent = get_navigation_intent(url, true);
157
+ // Clear prefetch, it might be affected by the invalidation.
158
+ // Also solves an edge case where a prefetch is triggered, the navigation for it
159
+ // was then triggered and is still running while the invalidation kicks in,
160
+ // at which point the invalidation should take over and "win".
161
+ load_cache = null;
162
+ await update(intent, url, []);
163
+ }
164
+
165
+ /**
166
+ * @param {string | URL} url
167
+ * @param {{ noscroll?: boolean; replaceState?: boolean; keepfocus?: boolean; state?: any }} opts
168
+ * @param {string[]} redirect_chain
169
+ * @param {{}} [nav_token]
170
+ */
171
+ async function goto(
172
+ url,
173
+ { noscroll = false, replaceState = false, keepfocus = false, state = {} },
174
+ redirect_chain,
175
+ nav_token
176
+ ) {
177
+ if (typeof url === 'string') {
178
+ url = new URL(url, get_base_uri(document));
179
+ }
180
+
181
+ return navigate({
182
+ url,
183
+ scroll: noscroll ? scroll_state() : null,
184
+ keepfocus,
185
+ redirect_chain,
186
+ details: {
187
+ state,
188
+ replaceState
189
+ },
190
+ nav_token,
191
+ accepted: () => {},
192
+ blocked: () => {},
193
+ type: 'goto'
194
+ });
195
+ }
196
+
197
+ /** @param {URL} url */
198
+ async function prefetch(url) {
199
+ const intent = get_navigation_intent(url, false);
200
+
201
+ if (!intent) {
202
+ throw new Error('Attempted to prefetch a URL that does not belong to this app');
203
+ }
204
+
205
+ load_cache = { id: intent.id, promise: load_route(intent) };
206
+
207
+ return load_cache.promise;
208
+ }
209
+
210
+ /**
211
+ * Returns `true` if update completes, `false` if it is aborted
212
+ * @param {import('./types').NavigationIntent | undefined} intent
213
+ * @param {URL} url
214
+ * @param {string[]} redirect_chain
215
+ * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean, details: { replaceState: boolean, state: any } | null}} [opts]
216
+ * @param {{}} [nav_token] To distinguish between different navigation events and determine the latest. Needed for example for redirects to keep the original token
217
+ * @param {() => void} [callback]
218
+ */
219
+ async function update(intent, url, redirect_chain, opts, nav_token = {}, callback) {
220
+ token = nav_token;
221
+ let navigation_result = intent && (await load_route(intent));
222
+
223
+ if (!navigation_result) {
224
+ navigation_result = await server_fallback(
225
+ url,
226
+ null,
227
+ handle_error(new Error(`Not found: ${url.pathname}`), { url, params: {}, routeId: null }),
228
+ 404
229
+ );
230
+ }
231
+
232
+ // if this is an internal navigation intent, use the normalized
233
+ // URL for the rest of the function
234
+ url = intent?.url || url;
235
+
236
+ // abort if user navigated during update
237
+ if (token !== nav_token) return false;
238
+
239
+ if (navigation_result.type === 'redirect') {
240
+ if (redirect_chain.length > 10 || redirect_chain.includes(url.pathname)) {
241
+ navigation_result = await load_root_error_page({
242
+ status: 500,
243
+ error: handle_error(new Error('Redirect loop'), { url, params: {}, routeId: null }),
244
+ url,
245
+ routeId: null
246
+ });
247
+ } else {
248
+ goto(
249
+ new URL(navigation_result.location, url).href,
250
+ {},
251
+ [...redirect_chain, url.pathname],
252
+ nav_token
253
+ );
254
+ return false;
255
+ }
256
+ } else if (navigation_result.props?.page?.status >= 400) {
257
+ const updated = await stores.updated.check();
258
+ if (updated) {
259
+ await native_navigation(url);
260
+ }
261
+ }
262
+
263
+ // reset invalidation only after a finished navigation. If there are redirects or
264
+ // additional invalidations, they should get the same invalidation treatment
265
+ invalidated.length = 0;
266
+ force_invalidation = false;
267
+
268
+ updating = true;
269
+
270
+ if (opts && opts.details) {
271
+ const { details } = opts;
272
+ const change = details.replaceState ? 0 : 1;
273
+ details.state[INDEX_KEY] = current_history_index += change;
274
+ history[details.replaceState ? 'replaceState' : 'pushState'](details.state, '', url);
275
+ }
276
+
277
+ // reset prefetch synchronously after the history state has been set to avoid race conditions
278
+ load_cache = null;
279
+
280
+ if (started) {
281
+ current = navigation_result.state;
282
+
283
+ if (navigation_result.props.page) {
284
+ navigation_result.props.page.url = url;
285
+ }
286
+
287
+ const post_update = pre_update();
288
+ root.$set(navigation_result.props);
289
+ post_update();
290
+ } else {
291
+ initialize(navigation_result);
292
+ }
293
+
294
+ // opts must be passed if we're navigating
295
+ if (opts) {
296
+ const { scroll, keepfocus } = opts;
297
+
298
+ if (!keepfocus) {
299
+ // Reset page selection and focus
300
+ // We try to mimic browsers' behaviour as closely as possible by targeting the
301
+ // first scrollable region, but unfortunately it's not a perfect match — e.g.
302
+ // shift-tabbing won't immediately cycle up from the end of the page on Chromium
303
+ // See https://html.spec.whatwg.org/multipage/interaction.html#get-the-focusable-area
304
+ const root = document.body;
305
+ const tabindex = root.getAttribute('tabindex');
306
+
307
+ root.tabIndex = -1;
308
+ root.focus({ preventScroll: true });
309
+
310
+ setTimeout(() => {
311
+ getSelection()?.removeAllRanges();
312
+ });
313
+
314
+ // restore `tabindex` as to prevent `root` from stealing input from elements
315
+ if (tabindex !== null) {
316
+ root.setAttribute('tabindex', tabindex);
317
+ } else {
318
+ root.removeAttribute('tabindex');
319
+ }
320
+ }
321
+
322
+ // need to render the DOM before we can scroll to the rendered elements
323
+ await tick();
324
+
325
+ if (autoscroll) {
326
+ const deep_linked = url.hash && document.getElementById(url.hash.slice(1));
327
+ if (scroll) {
328
+ scrollTo(scroll.x, scroll.y);
329
+ } else if (deep_linked) {
330
+ // Here we use `scrollIntoView` on the element instead of `scrollTo`
331
+ // because it natively supports the `scroll-margin` and `scroll-behavior`
332
+ // CSS properties.
333
+ deep_linked.scrollIntoView();
334
+ } else {
335
+ scrollTo(0, 0);
336
+ }
337
+ }
338
+ } else {
339
+ // in this case we're simply invalidating
340
+ await tick();
341
+ }
342
+
343
+ autoscroll = true;
344
+
345
+ if (navigation_result.props.page) {
346
+ page = navigation_result.props.page;
347
+ }
348
+
349
+ if (callback) callback();
350
+
351
+ updating = false;
352
+ }
353
+
354
+ /** @param {import('./types').NavigationFinished} result */
355
+ function initialize(result) {
356
+ current = result.state;
357
+
358
+ const style = document.querySelector('style[data-sveltekit]');
359
+ if (style) style.remove();
360
+
361
+ page = result.props.page;
362
+
363
+ const post_update = pre_update();
364
+ root = new Root({
365
+ target,
366
+ props: { ...result.props, stores },
367
+ hydrate: true
368
+ });
369
+ post_update();
370
+
371
+ /** @type {import('types').Navigation} */
372
+ const navigation = {
373
+ from: null,
374
+ to: add_url_properties('to', {
375
+ params: current.params,
376
+ routeId: current.route?.id ?? null,
377
+ url: new URL(location.href)
378
+ }),
379
+ type: 'load'
380
+ };
381
+ callbacks.after_navigate.forEach((fn) => fn(navigation));
382
+
383
+ started = true;
384
+ }
385
+
386
+ /**
387
+ *
388
+ * @param {{
389
+ * url: URL;
390
+ * params: Record<string, string>;
391
+ * branch: Array<import('./types').BranchNode | undefined>;
392
+ * status: number;
393
+ * error: App.Error | null;
394
+ * route: import('types').CSRRoute | null;
395
+ * form?: Record<string, any> | null;
396
+ * }} opts
397
+ */
398
+ async function get_navigation_result_from_branch({
399
+ url,
400
+ params,
401
+ branch,
402
+ status,
403
+ error,
404
+ route,
405
+ form
406
+ }) {
407
+ const filtered = /** @type {import('./types').BranchNode[] } */ (branch.filter(Boolean));
408
+
409
+ /** @type {import('./types').NavigationFinished} */
410
+ const result = {
411
+ type: 'loaded',
412
+ state: {
413
+ url,
414
+ params,
415
+ branch,
416
+ error,
417
+ route
418
+ },
419
+ props: {
420
+ components: filtered.map((branch_node) => branch_node.node.component)
421
+ }
422
+ };
423
+
424
+ if (form !== undefined) {
425
+ result.props.form = form;
426
+ }
427
+
428
+ let data = {};
429
+ let data_changed = !page;
430
+ for (let i = 0; i < filtered.length; i += 1) {
431
+ const node = filtered[i];
432
+ data = { ...data, ...node.data };
433
+
434
+ // Only set props if the node actually updated. This prevents needless rerenders.
435
+ if (data_changed || !current.branch.some((previous) => previous === node)) {
436
+ result.props[`data_${i}`] = data;
437
+ data_changed = data_changed || Object.keys(node.data ?? {}).length > 0;
438
+ }
439
+ }
440
+ if (!data_changed) {
441
+ // If nothing was added, and the object entries are the same length, this means
442
+ // that nothing was removed either and therefore the data is the same as the previous one.
443
+ // This would be more readable with a separate boolean but that would cost us some bytes.
444
+ data_changed = Object.keys(page.data).length !== Object.keys(data).length;
445
+ }
446
+
447
+ const page_changed =
448
+ !current.url ||
449
+ url.href !== current.url.href ||
450
+ current.error !== error ||
451
+ form !== undefined ||
452
+ data_changed;
453
+
454
+ if (page_changed) {
455
+ result.props.page = {
456
+ error,
457
+ params,
458
+ routeId: route && route.id,
459
+ status,
460
+ url,
461
+ form,
462
+ // The whole page store is updated, but this way the object reference stays the same
463
+ data: data_changed ? data : page.data
464
+ };
465
+
466
+ // TODO remove this for 1.0
467
+ /**
468
+ * @param {string} property
469
+ * @param {string} replacement
470
+ */
471
+ const print_error = (property, replacement) => {
472
+ Object.defineProperty(result.props.page, property, {
473
+ get: () => {
474
+ throw new Error(`$page.${property} has been replaced by $page.url.${replacement}`);
475
+ }
476
+ });
477
+ };
478
+
479
+ print_error('origin', 'origin');
480
+ print_error('path', 'pathname');
481
+ print_error('query', 'searchParams');
482
+ }
483
+
484
+ return result;
485
+ }
486
+
487
+ /**
488
+ * Call the load function of the given node, if it exists.
489
+ * If `server_data` is passed, this is treated as the initial run and the page endpoint is not requested.
490
+ *
491
+ * @param {{
492
+ * loader: import('types').CSRPageNodeLoader;
493
+ * parent: () => Promise<Record<string, any>>;
494
+ * url: URL;
495
+ * params: Record<string, string>;
496
+ * routeId: string | null;
497
+ * server_data_node: import('./types').DataNode | null;
498
+ * }} options
499
+ * @returns {Promise<import('./types').BranchNode>}
500
+ */
501
+ async function load_node({ loader, parent, url, params, routeId, server_data_node }) {
502
+ /** @type {Record<string, any> | null} */
503
+ let data = null;
504
+
505
+ /** @type {import('types').Uses} */
506
+ const uses = {
507
+ dependencies: new Set(),
508
+ params: new Set(),
509
+ parent: false,
510
+ url: false
511
+ };
512
+
513
+ const node = await loader();
514
+
515
+ if (node.shared?.load) {
516
+ /** @param {string[]} deps */
517
+ function depends(...deps) {
518
+ for (const dep of deps) {
519
+ const { href } = new URL(dep, url);
520
+ uses.dependencies.add(href);
521
+ }
522
+ }
523
+
524
+ /** @type {import('types').LoadEvent} */
525
+ const load_input = {
526
+ routeId,
527
+ params: new Proxy(params, {
528
+ get: (target, key) => {
529
+ uses.params.add(/** @type {string} */ (key));
530
+ return target[/** @type {string} */ (key)];
531
+ }
532
+ }),
533
+ data: server_data_node?.data ?? null,
534
+ url: make_trackable(url, () => {
535
+ uses.url = true;
536
+ }),
537
+ async fetch(resource, init) {
538
+ let requested;
539
+
540
+ if (resource instanceof Request) {
541
+ requested = resource.url;
542
+
543
+ // we're not allowed to modify the received `Request` object, so in order
544
+ // to fixup relative urls we create a new equivalent `init` object instead
545
+ init = {
546
+ // the request body must be consumed in memory until browsers
547
+ // implement streaming request bodies and/or the body getter
548
+ body:
549
+ resource.method === 'GET' || resource.method === 'HEAD'
550
+ ? undefined
551
+ : await resource.blob(),
552
+ cache: resource.cache,
553
+ credentials: resource.credentials,
554
+ headers: resource.headers,
555
+ integrity: resource.integrity,
556
+ keepalive: resource.keepalive,
557
+ method: resource.method,
558
+ mode: resource.mode,
559
+ redirect: resource.redirect,
560
+ referrer: resource.referrer,
561
+ referrerPolicy: resource.referrerPolicy,
562
+ signal: resource.signal,
563
+ ...init
564
+ };
565
+ } else {
566
+ requested = resource;
567
+ }
568
+
569
+ // we must fixup relative urls so they are resolved from the target page
570
+ const resolved = new URL(requested, url).href;
571
+ depends(resolved);
572
+
573
+ // prerendered pages may be served from any origin, so `initial_fetch` urls shouldn't be resolved
574
+ return started
575
+ ? subsequent_fetch(resolved, init)
576
+ : initial_fetch(requested, resolved, init);
577
+ },
578
+ setHeaders: () => {}, // noop
579
+ depends,
580
+ parent() {
581
+ uses.parent = true;
582
+ return parent();
583
+ }
584
+ };
585
+
586
+ // TODO remove this for 1.0
587
+ Object.defineProperties(load_input, {
588
+ props: {
589
+ get() {
590
+ throw new Error(
591
+ '@migration task: Replace `props` with `data` stuff https://github.com/sveltejs/kit/discussions/5774#discussioncomment-3292693'
592
+ );
593
+ },
594
+ enumerable: false
595
+ },
596
+ session: {
597
+ get() {
598
+ throw new Error(
599
+ 'session is no longer available. See https://github.com/sveltejs/kit/discussions/5883'
600
+ );
601
+ },
602
+ enumerable: false
603
+ },
604
+ stuff: {
605
+ get() {
606
+ throw new Error(
607
+ '@migration task: Remove stuff https://github.com/sveltejs/kit/discussions/5774#discussioncomment-3292693'
608
+ );
609
+ },
610
+ enumerable: false
611
+ }
612
+ });
613
+
614
+ if (import.meta.env.DEV) {
615
+ try {
616
+ lock_fetch();
617
+ data = (await node.shared.load.call(null, load_input)) ?? null;
618
+ } finally {
619
+ unlock_fetch();
620
+ }
621
+ } else {
622
+ data = (await node.shared.load.call(null, load_input)) ?? null;
623
+ }
624
+ data = data ? await unwrap_promises(data) : null;
625
+ }
626
+
627
+ return {
628
+ node,
629
+ loader,
630
+ server: server_data_node,
631
+ shared: node.shared?.load ? { type: 'data', data, uses } : null,
632
+ data: data ?? server_data_node?.data ?? null
633
+ };
634
+ }
635
+
636
+ /**
637
+ * @param {boolean} url_changed
638
+ * @param {boolean} parent_changed
639
+ * @param {import('types').Uses | undefined} uses
640
+ * @param {Record<string, string>} params
641
+ */
642
+ function has_changed(url_changed, parent_changed, uses, params) {
643
+ if (force_invalidation) return true;
644
+
645
+ if (!uses) return false;
646
+
647
+ if (uses.parent && parent_changed) return true;
648
+ if (uses.url && url_changed) return true;
649
+
650
+ for (const param of uses.params) {
651
+ if (params[param] !== current.params[param]) return true;
652
+ }
653
+
654
+ for (const href of uses.dependencies) {
655
+ if (invalidated.some((fn) => fn(new URL(href)))) return true;
656
+ }
657
+
658
+ return false;
659
+ }
660
+
661
+ /**
662
+ * @param {import('types').ServerDataNode | import('types').ServerDataSkippedNode | null} node
663
+ * @param {import('./types').DataNode | null} [previous]
664
+ * @returns {import('./types').DataNode | null}
665
+ */
666
+ function create_data_node(node, previous) {
667
+ if (node?.type === 'data') {
668
+ return {
669
+ type: 'data',
670
+ data: node.data,
671
+ uses: {
672
+ dependencies: new Set(node.uses.dependencies ?? []),
673
+ params: new Set(node.uses.params ?? []),
674
+ parent: !!node.uses.parent,
675
+ url: !!node.uses.url
676
+ }
677
+ };
678
+ } else if (node?.type === 'skip') {
679
+ return previous ?? null;
680
+ }
681
+ return null;
682
+ }
683
+
684
+ /**
685
+ * @param {import('./types').NavigationIntent} intent
686
+ * @returns {Promise<import('./types').NavigationResult | undefined>}
687
+ */
688
+ async function load_route({ id, invalidating, url, params, route }) {
689
+ if (load_cache?.id === id) {
690
+ return load_cache.promise;
691
+ }
692
+
693
+ const { errors, layouts, leaf } = route;
694
+
695
+ const loaders = [...layouts, leaf];
696
+
697
+ // preload modules to avoid waterfall, but handle rejections
698
+ // so they don't get reported to Sentry et al (we don't need
699
+ // to act on the failures at this point)
700
+ errors.forEach((loader) => loader?.().catch(() => {}));
701
+ loaders.forEach((loader) => loader?.[1]().catch(() => {}));
702
+
703
+ /** @type {import('types').ServerData | null} */
704
+ let server_data = null;
705
+
706
+ const url_changed = current.url ? id !== current.url.pathname + current.url.search : false;
707
+
708
+ const invalid_server_nodes = loaders.reduce((acc, loader, i) => {
709
+ const previous = current.branch[i];
710
+
711
+ const invalid =
712
+ !!loader?.[0] &&
713
+ (previous?.loader !== loader[1] ||
714
+ has_changed(url_changed, acc.some(Boolean), previous.server?.uses, params));
715
+
716
+ acc.push(invalid);
717
+ return acc;
718
+ }, /** @type {boolean[]} */ ([]));
719
+
720
+ if (invalid_server_nodes.some(Boolean)) {
721
+ try {
722
+ server_data = await load_data(url, invalid_server_nodes);
723
+ } catch (error) {
724
+ return load_root_error_page({
725
+ status: 500,
726
+ error: handle_error(error, { url, params, routeId: route.id }),
727
+ url,
728
+ routeId: route.id
729
+ });
730
+ }
731
+
732
+ if (server_data.type === 'redirect') {
733
+ return server_data;
734
+ }
735
+ }
736
+
737
+ const server_data_nodes = server_data?.nodes;
738
+
739
+ let parent_changed = false;
740
+
741
+ const branch_promises = loaders.map(async (loader, i) => {
742
+ if (!loader) return;
743
+
744
+ /** @type {import('./types').BranchNode | undefined} */
745
+ const previous = current.branch[i];
746
+
747
+ const server_data_node = server_data_nodes?.[i];
748
+
749
+ // re-use data from previous load if it's still valid
750
+ const valid =
751
+ (!server_data_node || server_data_node.type === 'skip') &&
752
+ loader[1] === previous?.loader &&
753
+ !has_changed(url_changed, parent_changed, previous.shared?.uses, params);
754
+ if (valid) return previous;
755
+
756
+ parent_changed = true;
757
+
758
+ if (server_data_node?.type === 'error') {
759
+ // rethrow and catch below
760
+ throw server_data_node;
761
+ }
762
+
763
+ return load_node({
764
+ loader: loader[1],
765
+ url,
766
+ params,
767
+ routeId: route.id,
768
+ parent: async () => {
769
+ const data = {};
770
+ for (let j = 0; j < i; j += 1) {
771
+ Object.assign(data, (await branch_promises[j])?.data);
772
+ }
773
+ return data;
774
+ },
775
+ server_data_node: create_data_node(
776
+ // server_data_node is undefined if it wasn't reloaded from the server;
777
+ // and if current loader uses server data, we want to reuse previous data.
778
+ server_data_node === undefined && loader[0] ? { type: 'skip' } : server_data_node ?? null,
779
+ previous?.server
780
+ )
781
+ });
782
+ });
783
+
784
+ // if we don't do this, rejections will be unhandled
785
+ for (const p of branch_promises) p.catch(() => {});
786
+
787
+ /** @type {Array<import('./types').BranchNode | undefined>} */
788
+ const branch = [];
789
+
790
+ for (let i = 0; i < loaders.length; i += 1) {
791
+ if (loaders[i]) {
792
+ try {
793
+ branch.push(await branch_promises[i]);
794
+ } catch (err) {
795
+ if (err instanceof Redirect) {
796
+ return {
797
+ type: 'redirect',
798
+ location: err.location
799
+ };
800
+ }
801
+
802
+ let status = 500;
803
+ /** @type {App.Error} */
804
+ let error;
805
+
806
+ if (server_data_nodes?.includes(/** @type {import('types').ServerErrorNode} */ (err))) {
807
+ // this is the server error rethrown above, reconstruct but don't invoke
808
+ // the client error handler; it should've already been handled on the server
809
+ status = /** @type {import('types').ServerErrorNode} */ (err).status ?? status;
810
+ error = /** @type {import('types').ServerErrorNode} */ (err).error;
811
+ } else if (err instanceof HttpError) {
812
+ status = err.status;
813
+ error = err.body;
814
+ } else {
815
+ error = handle_error(err, { params, url, routeId: route.id });
816
+ }
817
+
818
+ const error_load = await load_nearest_error_page(i, branch, errors);
819
+ if (error_load) {
820
+ return await get_navigation_result_from_branch({
821
+ url,
822
+ params,
823
+ branch: branch.slice(0, error_load.idx).concat(error_load.node),
824
+ status,
825
+ error,
826
+ route
827
+ });
828
+ } else {
829
+ // if we get here, it's because the root `load` function failed,
830
+ // and we need to fall back to the server
831
+ return await server_fallback(url, route.id, error, status);
832
+ }
833
+ }
834
+ } else {
835
+ // push an empty slot so we can rewind past gaps to the
836
+ // layout that corresponds with an +error.svelte page
837
+ branch.push(undefined);
838
+ }
839
+ }
840
+
841
+ return await get_navigation_result_from_branch({
842
+ url,
843
+ params,
844
+ branch,
845
+ status: 200,
846
+ error: null,
847
+ route,
848
+ // Reset `form` on navigation, but not invalidation
849
+ form: invalidating ? undefined : null
850
+ });
851
+ }
852
+
853
+ /**
854
+ * @param {number} i Start index to backtrack from
855
+ * @param {Array<import('./types').BranchNode | undefined>} branch Branch to backtrack
856
+ * @param {Array<import('types').CSRPageNodeLoader | undefined>} errors All error pages for this branch
857
+ * @returns {Promise<{idx: number; node: import('./types').BranchNode} | undefined>}
858
+ */
859
+ async function load_nearest_error_page(i, branch, errors) {
860
+ while (i--) {
861
+ if (errors[i]) {
862
+ let j = i;
863
+ while (!branch[j]) j -= 1;
864
+ try {
865
+ return {
866
+ idx: j + 1,
867
+ node: {
868
+ node: await /** @type {import('types').CSRPageNodeLoader } */ (errors[i])(),
869
+ loader: /** @type {import('types').CSRPageNodeLoader } */ (errors[i]),
870
+ data: {},
871
+ server: null,
872
+ shared: null
873
+ }
874
+ };
875
+ } catch (e) {
876
+ continue;
877
+ }
878
+ }
879
+ }
880
+ }
881
+
882
+ /**
883
+ * @param {{
884
+ * status: number;
885
+ * error: App.Error;
886
+ * url: URL;
887
+ * routeId: string | null
888
+ * }} opts
889
+ * @returns {Promise<import('./types').NavigationFinished>}
890
+ */
891
+ async function load_root_error_page({ status, error, url, routeId }) {
892
+ /** @type {Record<string, string>} */
893
+ const params = {}; // error page does not have params
894
+
895
+ const node = await default_layout_loader();
896
+
897
+ /** @type {import('types').ServerDataNode | null} */
898
+ let server_data_node = null;
899
+
900
+ if (node.server) {
901
+ // TODO post-https://github.com/sveltejs/kit/discussions/6124 we can use
902
+ // existing root layout data
903
+ try {
904
+ const server_data = await load_data(url, [true]);
905
+
906
+ if (
907
+ server_data.type !== 'data' ||
908
+ (server_data.nodes[0] && server_data.nodes[0].type !== 'data')
909
+ ) {
910
+ throw 0;
911
+ }
912
+
913
+ server_data_node = server_data.nodes[0] ?? null;
914
+ } catch {
915
+ // at this point we have no choice but to fall back to the server, if it wouldn't
916
+ // bring us right back here, turning this into an endless loop
917
+ if (url.origin !== location.origin || url.pathname !== location.pathname || hydrated) {
918
+ await native_navigation(url);
919
+ }
920
+ }
921
+ }
922
+
923
+ const root_layout = await load_node({
924
+ loader: default_layout_loader,
925
+ url,
926
+ params,
927
+ routeId,
928
+ parent: () => Promise.resolve({}),
929
+ server_data_node: create_data_node(server_data_node)
930
+ });
931
+
932
+ /** @type {import('./types').BranchNode} */
933
+ const root_error = {
934
+ node: await default_error_loader(),
935
+ loader: default_error_loader,
936
+ shared: null,
937
+ server: null,
938
+ data: null
939
+ };
940
+
941
+ return await get_navigation_result_from_branch({
942
+ url,
943
+ params,
944
+ branch: [root_layout, root_error],
945
+ status,
946
+ error,
947
+ route: null
948
+ });
949
+ }
950
+
951
+ /**
952
+ * @param {URL} url
953
+ * @param {boolean} invalidating
954
+ */
955
+ function get_navigation_intent(url, invalidating) {
956
+ if (is_external_url(url)) return;
957
+
958
+ const path = decodeURI(url.pathname.slice(base.length) || '/');
959
+
960
+ for (const route of routes) {
961
+ const params = route.exec(path);
962
+
963
+ if (params) {
964
+ const normalized = new URL(
965
+ url.origin + normalize_path(url.pathname, trailing_slash) + url.search + url.hash
966
+ );
967
+ const id = normalized.pathname + normalized.search;
968
+ /** @type {import('./types').NavigationIntent} */
969
+ const intent = { id, invalidating, route, params: decode_params(params), url: normalized };
970
+ return intent;
971
+ }
972
+ }
973
+ }
974
+
975
+ /** @param {URL} url */
976
+ function is_external_url(url) {
977
+ return url.origin !== location.origin || !url.pathname.startsWith(base);
978
+ }
979
+
980
+ /**
981
+ * @param {{
982
+ * url: URL;
983
+ * scroll: { x: number, y: number } | null;
984
+ * keepfocus: boolean;
985
+ * redirect_chain: string[];
986
+ * details: {
987
+ * replaceState: boolean;
988
+ * state: any;
989
+ * } | null;
990
+ * type: import('types').NavigationType;
991
+ * delta?: number;
992
+ * nav_token?: {};
993
+ * accepted: () => void;
994
+ * blocked: () => void;
995
+ * }} opts
996
+ */
997
+ async function navigate({
998
+ url,
999
+ scroll,
1000
+ keepfocus,
1001
+ redirect_chain,
1002
+ details,
1003
+ type,
1004
+ delta,
1005
+ nav_token,
1006
+ accepted,
1007
+ blocked
1008
+ }) {
1009
+ let should_block = false;
1010
+
1011
+ const intent = get_navigation_intent(url, false);
1012
+
1013
+ /** @type {import('types').Navigation} */
1014
+ const navigation = {
1015
+ from: add_url_properties('from', {
1016
+ params: current.params,
1017
+ routeId: current.route?.id ?? null,
1018
+ url: current.url
1019
+ }),
1020
+ to: add_url_properties('to', {
1021
+ params: intent?.params ?? null,
1022
+ routeId: intent?.route.id ?? null,
1023
+ url
1024
+ }),
1025
+ type
1026
+ };
1027
+
1028
+ if (delta !== undefined) {
1029
+ navigation.delta = delta;
1030
+ }
1031
+
1032
+ const cancellable = {
1033
+ ...navigation,
1034
+ cancel: () => {
1035
+ should_block = true;
1036
+ }
1037
+ };
1038
+
1039
+ callbacks.before_navigate.forEach((fn) => fn(cancellable));
1040
+
1041
+ if (should_block) {
1042
+ blocked();
1043
+ return;
1044
+ }
1045
+
1046
+ update_scroll_positions(current_history_index);
1047
+
1048
+ accepted();
1049
+
1050
+ if (started) {
1051
+ stores.navigating.set(navigation);
1052
+ }
1053
+
1054
+ await update(
1055
+ intent,
1056
+ url,
1057
+ redirect_chain,
1058
+ {
1059
+ scroll,
1060
+ keepfocus,
1061
+ details
1062
+ },
1063
+ nav_token,
1064
+ () => {
1065
+ callbacks.after_navigate.forEach((fn) => fn(navigation));
1066
+ stores.navigating.set(null);
1067
+ }
1068
+ );
1069
+ }
1070
+
1071
+ /**
1072
+ * Does a full page reload if it wouldn't result in an endless loop in the SPA case
1073
+ * @param {URL} url
1074
+ * @param {string | null} routeId
1075
+ * @param {App.Error} error
1076
+ * @param {number} status
1077
+ * @returns {Promise<import('./types').NavigationFinished>}
1078
+ */
1079
+ async function server_fallback(url, routeId, error, status) {
1080
+ if (url.origin === location.origin && url.pathname === location.pathname && !hydrated) {
1081
+ // We would reload the same page we're currently on, which isn't hydrated,
1082
+ // which means no SSR, which means we would end up in an endless loop
1083
+ return await load_root_error_page({
1084
+ status,
1085
+ error,
1086
+ url,
1087
+ routeId
1088
+ });
1089
+ }
1090
+ return await native_navigation(url);
1091
+ }
1092
+
1093
+ /**
1094
+ * Loads `href` the old-fashioned way, with a full page reload.
1095
+ * Returns a `Promise` that never resolves (to prevent any
1096
+ * subsequent work, e.g. history manipulation, from happening)
1097
+ * @param {URL} url
1098
+ */
1099
+ function native_navigation(url) {
1100
+ location.href = url.href;
1101
+ return new Promise(() => {});
1102
+ }
1103
+
1104
+ if (import.meta.hot) {
1105
+ import.meta.hot.on('vite:beforeUpdate', () => {
1106
+ if (current.error) location.reload();
1107
+ });
1108
+ }
1109
+
1110
+ return {
1111
+ after_navigate: (fn) => {
1112
+ onMount(() => {
1113
+ callbacks.after_navigate.push(fn);
1114
+
1115
+ return () => {
1116
+ const i = callbacks.after_navigate.indexOf(fn);
1117
+ callbacks.after_navigate.splice(i, 1);
1118
+ };
1119
+ });
1120
+ },
1121
+
1122
+ before_navigate: (fn) => {
1123
+ onMount(() => {
1124
+ callbacks.before_navigate.push(fn);
1125
+
1126
+ return () => {
1127
+ const i = callbacks.before_navigate.indexOf(fn);
1128
+ callbacks.before_navigate.splice(i, 1);
1129
+ };
1130
+ });
1131
+ },
1132
+
1133
+ disable_scroll_handling: () => {
1134
+ if (import.meta.env.DEV && started && !updating) {
1135
+ throw new Error('Can only disable scroll handling during navigation');
1136
+ }
1137
+
1138
+ if (updating || !started) {
1139
+ autoscroll = false;
1140
+ }
1141
+ },
1142
+
1143
+ goto: (href, opts = {}) => goto(href, opts, []),
1144
+
1145
+ invalidate: (resource) => {
1146
+ if (resource === undefined) {
1147
+ // TODO remove for 1.0
1148
+ throw new Error(
1149
+ '`invalidate()` (with no arguments) has been replaced by `invalidateAll()`'
1150
+ );
1151
+ }
1152
+
1153
+ if (typeof resource === 'function') {
1154
+ invalidated.push(resource);
1155
+ } else {
1156
+ const { href } = new URL(resource, location.href);
1157
+ invalidated.push((url) => url.href === href);
1158
+ }
1159
+
1160
+ return invalidate();
1161
+ },
1162
+
1163
+ invalidateAll: () => {
1164
+ force_invalidation = true;
1165
+ return invalidate();
1166
+ },
1167
+
1168
+ prefetch: async (href) => {
1169
+ const url = new URL(href, get_base_uri(document));
1170
+ await prefetch(url);
1171
+ },
1172
+
1173
+ // TODO rethink this API
1174
+ prefetch_routes: async (pathnames) => {
1175
+ const matching = pathnames
1176
+ ? routes.filter((route) => pathnames.some((pathname) => route.exec(pathname)))
1177
+ : routes;
1178
+
1179
+ const promises = matching.map((r) => {
1180
+ return Promise.all([...r.layouts, r.leaf].map((load) => load?.[1]()));
1181
+ });
1182
+
1183
+ await Promise.all(promises);
1184
+ },
1185
+
1186
+ apply_action: async (result) => {
1187
+ if (result.type === 'error') {
1188
+ const url = new URL(location.href);
1189
+
1190
+ const { branch, route } = current;
1191
+ if (!route) return;
1192
+
1193
+ const error_load = await load_nearest_error_page(
1194
+ current.branch.length,
1195
+ branch,
1196
+ route.errors
1197
+ );
1198
+ if (error_load) {
1199
+ const navigation_result = await get_navigation_result_from_branch({
1200
+ url,
1201
+ params: current.params,
1202
+ branch: branch.slice(0, error_load.idx).concat(error_load.node),
1203
+ status: 500, // TODO might not be 500?
1204
+ error: result.error,
1205
+ route
1206
+ });
1207
+
1208
+ current = navigation_result.state;
1209
+
1210
+ const post_update = pre_update();
1211
+ root.$set(navigation_result.props);
1212
+ post_update();
1213
+ }
1214
+ } else if (result.type === 'redirect') {
1215
+ goto(result.location, {}, []);
1216
+ } else {
1217
+ /** @type {Record<string, any>} */
1218
+ const props = {
1219
+ form: result.data,
1220
+ page: { ...page, form: result.data, status: result.status }
1221
+ };
1222
+ const post_update = pre_update();
1223
+ root.$set(props);
1224
+ post_update();
1225
+ }
1226
+ },
1227
+
1228
+ _start_router: () => {
1229
+ history.scrollRestoration = 'manual';
1230
+
1231
+ // Adopted from Nuxt.js
1232
+ // Reset scrollRestoration to auto when leaving page, allowing page reload
1233
+ // and back-navigation from other pages to use the browser to restore the
1234
+ // scrolling position.
1235
+ addEventListener('beforeunload', (e) => {
1236
+ let should_block = false;
1237
+
1238
+ /** @type {import('types').Navigation & { cancel: () => void }} */
1239
+ const navigation = {
1240
+ from: add_url_properties('from', {
1241
+ params: current.params,
1242
+ routeId: current.route?.id ?? null,
1243
+ url: current.url
1244
+ }),
1245
+ to: null,
1246
+ type: 'unload',
1247
+ cancel: () => (should_block = true)
1248
+ };
1249
+
1250
+ callbacks.before_navigate.forEach((fn) => fn(navigation));
1251
+
1252
+ if (should_block) {
1253
+ e.preventDefault();
1254
+ e.returnValue = '';
1255
+ } else {
1256
+ history.scrollRestoration = 'auto';
1257
+ }
1258
+ });
1259
+
1260
+ addEventListener('visibilitychange', () => {
1261
+ if (document.visibilityState === 'hidden') {
1262
+ update_scroll_positions(current_history_index);
1263
+
1264
+ try {
1265
+ sessionStorage[SCROLL_KEY] = JSON.stringify(scroll_positions);
1266
+ } catch {
1267
+ // do nothing
1268
+ }
1269
+ }
1270
+ });
1271
+
1272
+ /** @param {Event} event */
1273
+ const trigger_prefetch = (event) => {
1274
+ const { url, options } = find_anchor(event);
1275
+ if (url && options.prefetch) {
1276
+ if (is_external_url(url)) return;
1277
+ prefetch(url);
1278
+ }
1279
+ };
1280
+
1281
+ /** @type {NodeJS.Timeout} */
1282
+ let mousemove_timeout;
1283
+
1284
+ /** @param {MouseEvent|TouchEvent} event */
1285
+ const handle_mousemove = (event) => {
1286
+ clearTimeout(mousemove_timeout);
1287
+ mousemove_timeout = setTimeout(() => {
1288
+ // event.composedPath(), which is used in find_anchor, will be empty if the event is read in a timeout
1289
+ // add a layer of indirection to address that
1290
+ event.target?.dispatchEvent(
1291
+ new CustomEvent('sveltekit:trigger_prefetch', { bubbles: true })
1292
+ );
1293
+ }, 20);
1294
+ };
1295
+
1296
+ addEventListener('touchstart', trigger_prefetch);
1297
+ addEventListener('mousemove', handle_mousemove);
1298
+ addEventListener('sveltekit:trigger_prefetch', trigger_prefetch);
1299
+
1300
+ /** @param {MouseEvent} event */
1301
+ addEventListener('click', (event) => {
1302
+ // Adapted from https://github.com/visionmedia/page.js
1303
+ // MIT license https://github.com/visionmedia/page.js#license
1304
+ if (event.button || event.which !== 1) return;
1305
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
1306
+ if (event.defaultPrevented) return;
1307
+
1308
+ const { a, url, options } = find_anchor(event);
1309
+ if (!a || !url) return;
1310
+
1311
+ const is_svg_a_element = a instanceof SVGAElement;
1312
+
1313
+ // Ignore URL protocols that differ to the current one and are not http(s) (e.g. `mailto:`, `tel:`, `myapp:`, etc.)
1314
+ // This may be wrong when the protocol is x: and the link goes to y:.. which should be treated as an external
1315
+ // navigation, but it's not clear how to handle that case and it's not likely to come up in practice.
1316
+ // MEMO: Without this condition, firefox will open mailer twice.
1317
+ // See:
1318
+ // - https://github.com/sveltejs/kit/issues/4045
1319
+ // - https://github.com/sveltejs/kit/issues/5725
1320
+ // - https://github.com/sveltejs/kit/issues/6496
1321
+ if (
1322
+ !is_svg_a_element &&
1323
+ url.protocol !== location.protocol &&
1324
+ !(url.protocol === 'https:' || url.protocol === 'http:')
1325
+ )
1326
+ return;
1327
+
1328
+ // Ignore if tag has
1329
+ // 1. 'download' attribute
1330
+ // 2. 'rel' attribute includes external
1331
+ const rel = (a.getAttribute('rel') || '').split(/\s+/);
1332
+
1333
+ if (a.hasAttribute('download') || rel.includes('external') || options.reload) {
1334
+ return;
1335
+ }
1336
+
1337
+ // Ignore if <a> has a target
1338
+ if (is_svg_a_element ? a.target.baseVal : a.target) return;
1339
+
1340
+ // Check if new url only differs by hash and use the browser default behavior in that case
1341
+ // This will ensure the `hashchange` event is fired
1342
+ // Removing the hash does a full page navigation in the browser, so make sure a hash is present
1343
+ const [base, hash] = url.href.split('#');
1344
+ if (hash !== undefined && base === location.href.split('#')[0]) {
1345
+ // set this flag to distinguish between navigations triggered by
1346
+ // clicking a hash link and those triggered by popstate
1347
+ // TODO why not update history here directly?
1348
+ hash_navigating = true;
1349
+
1350
+ update_scroll_positions(current_history_index);
1351
+
1352
+ current.url = url;
1353
+ stores.page.set({ ...page, url });
1354
+ stores.page.notify();
1355
+
1356
+ return;
1357
+ }
1358
+
1359
+ navigate({
1360
+ url,
1361
+ scroll: options.noscroll ? scroll_state() : null,
1362
+ keepfocus: false,
1363
+ redirect_chain: [],
1364
+ details: {
1365
+ state: {},
1366
+ replaceState: url.href === location.href
1367
+ },
1368
+ accepted: () => event.preventDefault(),
1369
+ blocked: () => event.preventDefault(),
1370
+ type: 'link'
1371
+ });
1372
+ });
1373
+
1374
+ addEventListener('popstate', (event) => {
1375
+ if (event.state) {
1376
+ // if a popstate-driven navigation is cancelled, we need to counteract it
1377
+ // with history.go, which means we end up back here, hence this check
1378
+ if (event.state[INDEX_KEY] === current_history_index) return;
1379
+
1380
+ const delta = event.state[INDEX_KEY] - current_history_index;
1381
+
1382
+ navigate({
1383
+ url: new URL(location.href),
1384
+ scroll: scroll_positions[event.state[INDEX_KEY]],
1385
+ keepfocus: false,
1386
+ redirect_chain: [],
1387
+ details: null,
1388
+ accepted: () => {
1389
+ current_history_index = event.state[INDEX_KEY];
1390
+ },
1391
+ blocked: () => {
1392
+ history.go(-delta);
1393
+ },
1394
+ type: 'popstate',
1395
+ delta
1396
+ });
1397
+ }
1398
+ });
1399
+
1400
+ addEventListener('hashchange', () => {
1401
+ // if the hashchange happened as a result of clicking on a link,
1402
+ // we need to update history, otherwise we have to leave it alone
1403
+ if (hash_navigating) {
1404
+ hash_navigating = false;
1405
+ history.replaceState(
1406
+ { ...history.state, [INDEX_KEY]: ++current_history_index },
1407
+ '',
1408
+ location.href
1409
+ );
1410
+ }
1411
+ });
1412
+
1413
+ // fix link[rel=icon], because browsers will occasionally try to load relative
1414
+ // URLs after a pushState/replaceState, resulting in a 404 — see
1415
+ // https://github.com/sveltejs/kit/issues/3748#issuecomment-1125980897
1416
+ for (const link of document.querySelectorAll('link')) {
1417
+ if (link.rel === 'icon') link.href = link.href;
1418
+ }
1419
+
1420
+ addEventListener('pageshow', (event) => {
1421
+ // If the user navigates to another site and then uses the back button and
1422
+ // bfcache hits, we need to set navigating to null, the site doesn't know
1423
+ // the navigation away from it was successful.
1424
+ // Info about bfcache here: https://web.dev/bfcache
1425
+ if (event.persisted) {
1426
+ stores.navigating.set(null);
1427
+ }
1428
+ });
1429
+ },
1430
+
1431
+ _hydrate: async ({
1432
+ status,
1433
+ error,
1434
+ node_ids,
1435
+ params,
1436
+ routeId,
1437
+ data: server_data_nodes,
1438
+ form
1439
+ }) => {
1440
+ hydrated = true;
1441
+
1442
+ const url = new URL(location.href);
1443
+
1444
+ /** @type {import('./types').NavigationFinished | undefined} */
1445
+ let result;
1446
+
1447
+ try {
1448
+ const branch_promises = node_ids.map(async (n, i) => {
1449
+ const server_data_node = server_data_nodes[i];
1450
+
1451
+ return load_node({
1452
+ loader: nodes[n],
1453
+ url,
1454
+ params,
1455
+ routeId,
1456
+ parent: async () => {
1457
+ const data = {};
1458
+ for (let j = 0; j < i; j += 1) {
1459
+ Object.assign(data, (await branch_promises[j]).data);
1460
+ }
1461
+ return data;
1462
+ },
1463
+ server_data_node: create_data_node(server_data_node)
1464
+ });
1465
+ });
1466
+
1467
+ result = await get_navigation_result_from_branch({
1468
+ url,
1469
+ params,
1470
+ branch: await Promise.all(branch_promises),
1471
+ status,
1472
+ error,
1473
+ form,
1474
+ route: routes.find((route) => route.id === routeId) ?? null
1475
+ });
1476
+ } catch (error) {
1477
+ if (error instanceof Redirect) {
1478
+ // this is a real edge case — `load` would need to return
1479
+ // a redirect but only in the browser
1480
+ await native_navigation(new URL(error.location, location.href));
1481
+ return;
1482
+ }
1483
+
1484
+ result = await load_root_error_page({
1485
+ status: error instanceof HttpError ? error.status : 500,
1486
+ error: handle_error(error, { url, params, routeId }),
1487
+ url,
1488
+ routeId
1489
+ });
1490
+ }
1491
+
1492
+ initialize(result);
1493
+ }
1494
+ };
1495
+ }
1496
+
1497
+ /**
1498
+ * @param {URL} url
1499
+ * @param {boolean[]} invalid
1500
+ * @returns {Promise<import('types').ServerData>}
1501
+ */
1502
+ async function load_data(url, invalid) {
1503
+ const data_url = new URL(url);
1504
+ data_url.pathname = url.pathname.replace(/\/$/, '') + DATA_SUFFIX;
1505
+
1506
+ const res = await native_fetch(data_url.href, {
1507
+ headers: {
1508
+ 'x-sveltekit-invalidated': invalid.map((x) => (x ? '1' : '')).join(',')
1509
+ }
1510
+ });
1511
+ const server_data = await res.text();
1512
+
1513
+ if (!res.ok) {
1514
+ // error message is a JSON-stringified string which devalue can't handle at the top level
1515
+ throw new Error(JSON.parse(server_data));
1516
+ }
1517
+
1518
+ return devalue.parse(server_data);
1519
+ }
1520
+
1521
+ /**
1522
+ * @param {unknown} error
1523
+ * @param {import('types').NavigationEvent} event
1524
+ * @returns {App.Error}
1525
+ */
1526
+ function handle_error(error, event) {
1527
+ if (error instanceof HttpError) {
1528
+ return error.body;
1529
+ }
1530
+ return (
1531
+ hooks.handleError({ error, event }) ??
1532
+ /** @type {any} */ ({ message: event.routeId != null ? 'Internal Error' : 'Not Found' })
1533
+ );
1534
+ }
1535
+
1536
+ // TODO remove for 1.0
1537
+ const properties = [
1538
+ 'hash',
1539
+ 'href',
1540
+ 'host',
1541
+ 'hostname',
1542
+ 'origin',
1543
+ 'pathname',
1544
+ 'port',
1545
+ 'protocol',
1546
+ 'search',
1547
+ 'searchParams',
1548
+ 'toString',
1549
+ 'toJSON'
1550
+ ];
1551
+
1552
+ /**
1553
+ * @param {'from' | 'to'} type
1554
+ * @param {import('types').NavigationTarget} target
1555
+ */
1556
+ function add_url_properties(type, target) {
1557
+ for (const prop of properties) {
1558
+ Object.defineProperty(target, prop, {
1559
+ get() {
1560
+ throw new Error(
1561
+ `The navigation shape changed - ${type}.${prop} should now be ${type}.url.${prop}`
1562
+ );
1563
+ },
1564
+ enumerable: false
1565
+ });
1566
+ }
1567
+
1568
+ return target;
1569
+ }
1570
+
1571
+ function pre_update() {
1572
+ if (__SVELTEKIT_DEV__) {
1573
+ return () => {
1574
+ check_for_removed_attributes();
1575
+ };
1576
+ }
1577
+
1578
+ return () => {};
1579
+ }
1580
+
1581
+ if (__SVELTEKIT_DEV__) {
1582
+ // Nasty hack to silence harmless warnings the user can do nothing about
1583
+ const warn = console.warn;
1584
+ console.warn = (...args) => {
1585
+ if (
1586
+ args.length === 1 &&
1587
+ /<(Layout|Page)(_[\w$]+)?> was created (with unknown|without expected) prop '(data|form)'/.test(
1588
+ args[0]
1589
+ )
1590
+ ) {
1591
+ return;
1592
+ }
1593
+ warn(...args);
1594
+ };
1595
+ }