@sveltejs/kit 1.0.0-next.99 → 1.0.1

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