@sveltejs/kit 1.0.0-next.44 → 1.0.0-next.440

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