@sveltejs/kit 1.0.0-next.51 → 1.0.0-next.511

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