@sveltejs/kit 1.0.0-next.5 → 1.0.0-next.500

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