@sveltejs/kit 1.0.0-next.19 → 1.0.0-next.193

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 (77) hide show
  1. package/README.md +12 -9
  2. package/assets/components/error.svelte +19 -3
  3. package/assets/runtime/app/env.js +20 -0
  4. package/assets/runtime/app/navigation.js +45 -13
  5. package/assets/runtime/app/paths.js +1 -2
  6. package/assets/runtime/app/stores.js +15 -9
  7. package/assets/runtime/chunks/utils.js +13 -0
  8. package/assets/runtime/env.js +8 -0
  9. package/assets/runtime/internal/singletons.js +5 -11
  10. package/assets/runtime/internal/start.js +954 -350
  11. package/assets/runtime/paths.js +13 -0
  12. package/dist/chunks/cert.js +29254 -0
  13. package/dist/chunks/constants.js +8 -0
  14. package/dist/chunks/error.js +21 -0
  15. package/dist/chunks/http.js +22 -0
  16. package/dist/chunks/index.js +4745 -0
  17. package/dist/chunks/index2.js +700 -0
  18. package/dist/chunks/index3.js +1065 -0
  19. package/dist/chunks/index4.js +393 -0
  20. package/dist/chunks/index5.js +413 -0
  21. package/dist/chunks/index6.js +15560 -0
  22. package/dist/{standard.js → chunks/standard.js} +16 -18
  23. package/dist/cli.js +936 -67
  24. package/dist/hooks.js +28 -0
  25. package/dist/install-fetch.js +6070 -0
  26. package/dist/node.js +51 -0
  27. package/dist/ssr.js +1791 -0
  28. package/package.json +89 -54
  29. package/svelte-kit.js +2 -0
  30. package/types/ambient-modules.d.ts +173 -0
  31. package/types/app.d.ts +21 -0
  32. package/types/config.d.ts +96 -0
  33. package/types/endpoint.d.ts +18 -0
  34. package/types/helper.d.ts +41 -0
  35. package/types/hooks.d.ts +36 -0
  36. package/types/index.d.ts +17 -0
  37. package/types/internal.d.ts +219 -0
  38. package/types/page.d.ts +77 -0
  39. package/CHANGELOG.md +0 -270
  40. package/assets/runtime/app/navigation.js.map +0 -1
  41. package/assets/runtime/app/paths.js.map +0 -1
  42. package/assets/runtime/app/stores.js.map +0 -1
  43. package/assets/runtime/internal/singletons.js.map +0 -1
  44. package/assets/runtime/internal/start.js.map +0 -1
  45. package/assets/runtime/utils-85ebcc60.js +0 -18
  46. package/assets/runtime/utils-85ebcc60.js.map +0 -1
  47. package/dist/api.js +0 -44
  48. package/dist/api.js.map +0 -1
  49. package/dist/build.js +0 -246
  50. package/dist/build.js.map +0 -1
  51. package/dist/cli.js.map +0 -1
  52. package/dist/colors.js +0 -37
  53. package/dist/colors.js.map +0 -1
  54. package/dist/create_app.js +0 -578
  55. package/dist/create_app.js.map +0 -1
  56. package/dist/index.js +0 -12009
  57. package/dist/index.js.map +0 -1
  58. package/dist/index2.js +0 -544
  59. package/dist/index2.js.map +0 -1
  60. package/dist/index3.js +0 -71
  61. package/dist/index3.js.map +0 -1
  62. package/dist/index4.js +0 -466
  63. package/dist/index4.js.map +0 -1
  64. package/dist/index5.js +0 -717
  65. package/dist/index5.js.map +0 -1
  66. package/dist/index6.js +0 -713
  67. package/dist/index6.js.map +0 -1
  68. package/dist/logging.js +0 -43
  69. package/dist/logging.js.map +0 -1
  70. package/dist/package.js +0 -406
  71. package/dist/package.js.map +0 -1
  72. package/dist/renderer.js +0 -2391
  73. package/dist/renderer.js.map +0 -1
  74. package/dist/standard.js.map +0 -1
  75. package/dist/utils.js +0 -54
  76. package/dist/utils.js.map +0 -1
  77. package/svelte-kit +0 -3
@@ -1,12 +1,9 @@
1
1
  import Root from '../../generated/root.svelte';
2
- import { pages, ignore, layout } from '../../generated/manifest.js';
3
- import { f as find_anchor, g as get_base_uri } from '../utils-85ebcc60.js';
2
+ import { fallback, routes } from '../../generated/manifest.js';
3
+ import { g as get_base_uri } from '../chunks/utils.js';
4
4
  import { writable } from 'svelte/store';
5
- import { init, set_paths } from './singletons.js';
6
-
7
- function which(event) {
8
- return event.which === null ? event.button : event.which;
9
- }
5
+ import { init } from './singletons.js';
6
+ import { set_paths } from '../paths.js';
10
7
 
11
8
  function scroll_state() {
12
9
  return {
@@ -15,26 +12,57 @@ function scroll_state() {
15
12
  };
16
13
  }
17
14
 
15
+ /**
16
+ * @param {Node | null} node
17
+ * @returns {HTMLAnchorElement | SVGAElement | null}
18
+ */
19
+ function find_anchor(node) {
20
+ while (node && node.nodeName.toUpperCase() !== 'A') node = node.parentNode; // SVG <a> elements have a lowercase name
21
+ return /** @type {HTMLAnchorElement | SVGAElement} */ (node);
22
+ }
23
+
24
+ /**
25
+ * @param {HTMLAnchorElement | SVGAElement} node
26
+ * @returns {URL}
27
+ */
28
+ function get_href(node) {
29
+ return node instanceof SVGAElement
30
+ ? new URL(node.href.baseVal, document.baseURI)
31
+ : new URL(node.href);
32
+ }
33
+
18
34
  class Router {
19
- constructor({ base, host, pages, ignore }) {
35
+ /**
36
+ * @param {{
37
+ * base: string;
38
+ * routes: import('types/internal').CSRRoute[];
39
+ * trailing_slash: import('types/internal').TrailingSlash;
40
+ * renderer: import('./renderer').Renderer
41
+ * }} opts
42
+ */
43
+ constructor({ base, routes, trailing_slash, renderer }) {
20
44
  this.base = base;
21
- this.host = host;
22
- this.pages = pages;
23
- this.ignore = ignore;
24
-
25
- this.history = window.history || {
26
- pushState: () => {},
27
- replaceState: () => {},
28
- scrollRestoration: 'auto'
29
- };
30
- }
45
+ this.routes = routes;
46
+ this.trailing_slash = trailing_slash;
47
+ /** Keeps tracks of multiple navigations caused by redirects during rendering */
48
+ this.navigating = 0;
31
49
 
32
- init({ renderer }) {
50
+ /** @type {import('./renderer').Renderer} */
33
51
  this.renderer = renderer;
34
52
  renderer.router = this;
35
53
 
36
- if ('scrollRestoration' in this.history) {
37
- this.history.scrollRestoration = 'manual';
54
+ this.enabled = true;
55
+
56
+ // make it possible to reset focus
57
+ document.body.setAttribute('tabindex', '-1');
58
+
59
+ // create initial history entry, so we can return here
60
+ history.replaceState(history.state || {}, '', location.href);
61
+ }
62
+
63
+ init_listeners() {
64
+ if ('scrollRestoration' in history) {
65
+ history.scrollRestoration = 'manual';
38
66
  }
39
67
 
40
68
  // Adopted from Nuxt.js
@@ -42,16 +70,18 @@ class Router {
42
70
  // and back-navigation from other pages to use the browser to restore the
43
71
  // scrolling position.
44
72
  addEventListener('beforeunload', () => {
45
- this.history.scrollRestoration = 'auto';
73
+ history.scrollRestoration = 'auto';
46
74
  });
47
75
 
48
76
  // Setting scrollRestoration to manual again when returning to this page.
49
77
  addEventListener('load', () => {
50
- this.history.scrollRestoration = 'manual';
78
+ history.scrollRestoration = 'manual';
51
79
  });
52
80
 
53
81
  // There's no API to capture the scroll location right before the user
54
82
  // hits the back/forward button, so we listen for scroll events
83
+
84
+ /** @type {NodeJS.Timeout} */
55
85
  let scroll_timer;
56
86
  addEventListener('scroll', () => {
57
87
  clearTimeout(scroll_timer);
@@ -62,145 +92,305 @@ class Router {
62
92
  ...(history.state || {}),
63
93
  'sveltekit:scroll': scroll_state()
64
94
  };
65
- history.replaceState(new_state, document.title, window.location);
95
+ history.replaceState(new_state, document.title, window.location.href);
66
96
  }, 50);
67
97
  });
68
98
 
99
+ /** @param {MouseEvent|TouchEvent} event */
100
+ const trigger_prefetch = (event) => {
101
+ const a = find_anchor(/** @type {Node} */ (event.target));
102
+ if (a && a.href && a.hasAttribute('sveltekit:prefetch')) {
103
+ this.prefetch(get_href(a));
104
+ }
105
+ };
106
+
107
+ /** @type {NodeJS.Timeout} */
108
+ let mousemove_timeout;
109
+
110
+ /** @param {MouseEvent|TouchEvent} event */
111
+ const handle_mousemove = (event) => {
112
+ clearTimeout(mousemove_timeout);
113
+ mousemove_timeout = setTimeout(() => {
114
+ trigger_prefetch(event);
115
+ }, 20);
116
+ };
117
+
118
+ addEventListener('touchstart', trigger_prefetch);
119
+ addEventListener('mousemove', handle_mousemove);
120
+
121
+ /** @param {MouseEvent} event */
69
122
  addEventListener('click', (event) => {
123
+ if (!this.enabled) return;
124
+
70
125
  // Adapted from https://github.com/visionmedia/page.js
71
126
  // MIT license https://github.com/visionmedia/page.js#license
72
- if (which(event) !== 1) return;
127
+ if (event.button || event.which !== 1) return;
73
128
  if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
74
129
  if (event.defaultPrevented) return;
75
130
 
76
- const a = find_anchor(event.target);
131
+ const a = find_anchor(/** @type {Node} */ (event.target));
77
132
  if (!a) return;
78
133
 
79
134
  if (!a.href) return;
80
135
 
81
- // check if link is inside an svg
82
- // in this case, both href and target are always inside an object
83
- const svg = typeof a.href === 'object' && a.href.constructor.name === 'SVGAnimatedString';
84
- const href = String(svg ? a.href.baseVal : a.href);
85
-
86
- if (href === location.href) {
136
+ const url = get_href(a);
137
+ const url_string = url.toString();
138
+ if (url_string === location.href) {
87
139
  if (!location.hash) event.preventDefault();
88
140
  return;
89
141
  }
90
142
 
91
143
  // Ignore if tag has
92
144
  // 1. 'download' attribute
93
- // 2. rel='external' attribute
94
- if (a.hasAttribute('download') || a.getAttribute('rel') === 'external') return;
145
+ // 2. 'rel' attribute includes external
146
+ const rel = (a.getAttribute('rel') || '').split(/\s+/);
147
+
148
+ if (a.hasAttribute('download') || (rel && rel.includes('external'))) {
149
+ return;
150
+ }
95
151
 
96
152
  // Ignore if <a> has a target
97
- if (svg ? a.target.baseVal : a.target) return;
153
+ if (a instanceof SVGAElement ? a.target.baseVal : a.target) return;
98
154
 
99
- const url = new URL(href);
155
+ if (!this.owns(url)) return;
100
156
 
101
- // Don't handle hash changes
102
- if (url.pathname === location.pathname && url.search === location.search) return;
157
+ const noscroll = a.hasAttribute('sveltekit:noscroll');
103
158
 
104
- const selected = this.select(url);
105
- if (selected) {
106
- const noscroll = a.hasAttribute('sveltekit:noscroll');
107
- this.renderer.notify(selected);
108
- this.history.pushState({}, '', url.href);
109
- this.navigate(selected, noscroll ? scroll_state() : false, url.hash);
110
- event.preventDefault();
159
+ const i1 = url_string.indexOf('#');
160
+ const i2 = location.href.indexOf('#');
161
+ const u1 = i1 >= 0 ? url_string.substring(0, i1) : url_string;
162
+ const u2 = i2 >= 0 ? location.href.substring(0, i2) : location.href;
163
+ history.pushState({}, '', url.href);
164
+ if (u1 === u2) {
165
+ window.dispatchEvent(new HashChangeEvent('hashchange'));
111
166
  }
167
+ this._navigate(url, noscroll ? scroll_state() : null, false, [], url.hash);
168
+ event.preventDefault();
112
169
  });
113
170
 
114
171
  addEventListener('popstate', (event) => {
115
- if (event.state) {
172
+ if (event.state && this.enabled) {
116
173
  const url = new URL(location.href);
117
- const selected = this.select(url);
118
- if (selected) {
119
- this.navigate(selected, event.state['sveltekit:scroll']);
120
- } else {
121
- // eslint-disable-next-line
122
- location.href = location.href; // nosonar
123
- }
174
+ this._navigate(url, event.state['sveltekit:scroll'], false, []);
124
175
  }
125
176
  });
177
+ }
126
178
 
127
- // make it possible to reset focus
128
- document.body.setAttribute('tabindex', '-1');
179
+ /** @param {URL} url */
180
+ owns(url) {
181
+ return url.origin === location.origin && url.pathname.startsWith(this.base);
182
+ }
129
183
 
130
- // load current page
131
- this.history.replaceState({}, '', location.href);
184
+ /**
185
+ * @param {URL} url
186
+ * @returns {import('./types').NavigationInfo | undefined}
187
+ */
188
+ parse(url) {
189
+ if (this.owns(url)) {
190
+ const path = url.pathname.slice(this.base.length) || '/';
132
191
 
133
- const selected = this.select(new URL(location.href));
134
- if (selected) return this.renderer.start(selected);
135
- }
192
+ const decoded_path = decodeURI(path);
193
+ const routes = this.routes.filter(([pattern]) => pattern.test(decoded_path));
194
+
195
+ const query = new URLSearchParams(url.search);
196
+ const id = `${path}?${query}`;
136
197
 
137
- select(url) {
138
- if (url.origin !== location.origin) return null;
139
- if (!url.pathname.startsWith(this.base)) return null;
198
+ return { id, routes, path, decoded_path, query };
199
+ }
200
+ }
140
201
 
141
- let path = url.pathname.slice(this.base.length);
202
+ /**
203
+ * @typedef {Parameters<typeof import('$app/navigation').goto>} GotoParams
204
+ *
205
+ * @param {GotoParams[0]} href
206
+ * @param {GotoParams[1]} opts
207
+ * @param {string[]} chain
208
+ */
209
+ async goto(
210
+ href,
211
+ { noscroll = false, replaceState = false, keepfocus = false, state = {} } = {},
212
+ chain
213
+ ) {
214
+ const url = new URL(href, get_base_uri(document));
142
215
 
143
- if (path === '') {
144
- path = '/';
216
+ if (this.enabled && this.owns(url)) {
217
+ history[replaceState ? 'replaceState' : 'pushState'](state, '', href);
218
+ return this._navigate(url, noscroll ? scroll_state() : null, keepfocus, chain, url.hash);
145
219
  }
146
220
 
147
- // avoid accidental clashes between server routes and page routes
148
- if (this.ignore.some((pattern) => pattern.test(path))) return;
221
+ location.href = url.href;
222
+ return new Promise(() => {
223
+ /* never resolves */
224
+ });
225
+ }
149
226
 
150
- for (const route of this.pages) {
151
- const match = route.pattern.exec(path);
227
+ enable() {
228
+ this.enabled = true;
229
+ }
152
230
 
153
- if (match) {
154
- const query = new URLSearchParams(url.search);
155
- const params = route.params(match);
231
+ disable() {
232
+ this.enabled = false;
233
+ }
156
234
 
157
- const page = { host: this.host, path, query, params };
235
+ /**
236
+ * @param {URL} url
237
+ * @returns {Promise<import('./types').NavigationResult>}
238
+ */
239
+ async prefetch(url) {
240
+ const info = this.parse(url);
158
241
 
159
- return { href: url.href, route, match, page };
160
- }
242
+ if (!info) {
243
+ throw new Error('Attempted to prefetch a URL that does not belong to this app');
161
244
  }
245
+
246
+ return this.renderer.load(info);
162
247
  }
163
248
 
164
- async goto(href, { noscroll = false, replaceState = false } = {}) {
165
- const url = new URL(href, get_base_uri(document));
166
- const selected = this.select(url);
249
+ /**
250
+ * @param {URL} url
251
+ * @param {{ x: number, y: number }?} scroll
252
+ * @param {boolean} keepfocus
253
+ * @param {string[]} chain
254
+ * @param {string} [hash]
255
+ */
256
+ async _navigate(url, scroll, keepfocus, chain, hash) {
257
+ const info = this.parse(url);
258
+
259
+ if (!info) {
260
+ throw new Error('Attempted to navigate to a URL that does not belong to this app');
261
+ }
167
262
 
168
- if (selected) {
169
- this.renderer.notify(selected);
263
+ if (!this.navigating) {
264
+ dispatchEvent(new CustomEvent('sveltekit:navigation-start'));
265
+ }
266
+ this.navigating++;
170
267
 
171
- // TODO shouldn't need to pass the hash here
172
- this.history[replaceState ? 'replaceState' : 'pushState']({}, '', href);
173
- return this.navigate(selected, noscroll ? scroll_state() : false, url.hash);
268
+ // remove trailing slashes
269
+ if (info.path !== '/') {
270
+ const has_trailing_slash = info.path.endsWith('/');
271
+
272
+ const incorrect =
273
+ (has_trailing_slash && this.trailing_slash === 'never') ||
274
+ (!has_trailing_slash &&
275
+ this.trailing_slash === 'always' &&
276
+ !(info.path.split('/').pop() || '').includes('.'));
277
+
278
+ if (incorrect) {
279
+ info.path = has_trailing_slash ? info.path.slice(0, -1) : info.path + '/';
280
+ history.replaceState({}, '', `${this.base}${info.path}${location.search}`);
281
+ }
174
282
  }
175
283
 
176
- location.href = href;
177
- return new Promise(() => {
178
- /* never resolves */
179
- });
284
+ await this.renderer.handle_navigation(info, chain, false, { hash, scroll, keepfocus });
285
+
286
+ this.navigating--;
287
+ if (!this.navigating) {
288
+ dispatchEvent(new CustomEvent('sveltekit:navigation-end'));
289
+ }
180
290
  }
291
+ }
181
292
 
182
- async navigate(selected, scroll, hash) {
183
- // remove trailing slashes
184
- if (location.pathname.endsWith('/') && location.pathname !== '/') {
185
- history.replaceState({}, '', `${location.pathname.slice(0, -1)}${location.search}`);
293
+ /**
294
+ * @param {unknown} err
295
+ * @return {Error}
296
+ */
297
+ function coalesce_to_error(err) {
298
+ return err instanceof Error ||
299
+ (err && /** @type {any} */ (err).name && /** @type {any} */ (err).message)
300
+ ? /** @type {Error} */ (err)
301
+ : new Error(JSON.stringify(err));
302
+ }
303
+
304
+ /**
305
+ * Hash using djb2
306
+ * @param {import('types/hooks').StrictBody} value
307
+ */
308
+ function hash(value) {
309
+ let hash = 5381;
310
+ let i = value.length;
311
+
312
+ if (typeof value === 'string') {
313
+ while (i) hash = (hash * 33) ^ value.charCodeAt(--i);
314
+ } else {
315
+ while (i) hash = (hash * 33) ^ value[--i];
316
+ }
317
+
318
+ return (hash >>> 0).toString(36);
319
+ }
320
+
321
+ /**
322
+ * @param {import('types/page').LoadOutput} loaded
323
+ * @returns {import('types/internal').NormalizedLoadOutput}
324
+ */
325
+ function normalize(loaded) {
326
+ const has_error_status =
327
+ loaded.status && loaded.status >= 400 && loaded.status <= 599 && !loaded.redirect;
328
+ if (loaded.error || has_error_status) {
329
+ const status = loaded.status;
330
+
331
+ if (!loaded.error && has_error_status) {
332
+ return {
333
+ status: status || 500,
334
+ error: new Error()
335
+ };
186
336
  }
187
337
 
188
- await this.renderer.render(selected);
338
+ const error = typeof loaded.error === 'string' ? new Error(loaded.error) : loaded.error;
189
339
 
190
- document.body.focus();
340
+ if (!(error instanceof Error)) {
341
+ return {
342
+ status: 500,
343
+ error: new Error(
344
+ `"error" property returned from load() must be a string or instance of Error, received type "${typeof error}"`
345
+ )
346
+ };
347
+ }
191
348
 
192
- const deep_linked = hash && document.getElementById(hash.slice(1));
193
- if (scroll) {
194
- scrollTo(scroll.x, scroll.y);
195
- } else if (deep_linked) {
196
- // scroll is an element id (from a hash), we need to compute y
197
- scrollTo(0, deep_linked.getBoundingClientRect().top + scrollY);
198
- } else {
199
- scrollTo(0, 0);
349
+ if (!status || status < 400 || status > 599) {
350
+ console.warn('"error" returned from load() without a valid status code — defaulting to 500');
351
+ return { status: 500, error };
352
+ }
353
+
354
+ return { status, error };
355
+ }
356
+
357
+ if (loaded.redirect) {
358
+ if (!loaded.status || Math.floor(loaded.status / 100) !== 3) {
359
+ return {
360
+ status: 500,
361
+ error: new Error(
362
+ '"redirect" property returned from load() must be accompanied by a 3xx status code'
363
+ )
364
+ };
365
+ }
366
+
367
+ if (typeof loaded.redirect !== 'string') {
368
+ return {
369
+ status: 500,
370
+ error: new Error('"redirect" property returned from load() must be a string')
371
+ };
200
372
  }
201
373
  }
374
+
375
+ // TODO remove before 1.0
376
+ if (/** @type {any} */ (loaded).context) {
377
+ throw new Error(
378
+ 'You are returning "context" from a load function. ' +
379
+ '"context" was renamed to "stuff", please adjust your code accordingly.'
380
+ );
381
+ }
382
+
383
+ return /** @type {import('types/internal').NormalizedLoadOutput} */ (loaded);
202
384
  }
203
385
 
386
+ /**
387
+ * @typedef {import('types/internal').CSRComponent} CSRComponent
388
+ *
389
+ * @typedef {Partial<import('types/page').Page>} Page
390
+ * @typedef {{ from: Page; to: Page }} Navigating
391
+ */
392
+
393
+ /** @param {any} value */
204
394
  function page_store(value) {
205
395
  const store = writable(value);
206
396
  let ready = true;
@@ -210,12 +400,15 @@ function page_store(value) {
210
400
  store.update((val) => val);
211
401
  }
212
402
 
403
+ /** @param {any} new_value */
213
404
  function set(new_value) {
214
405
  ready = false;
215
406
  store.set(new_value);
216
407
  }
217
408
 
409
+ /** @param {(value: any) => void} run */
218
410
  function subscribe(run) {
411
+ /** @type {any} */
219
412
  let old_value;
220
413
  return store.subscribe((new_value) => {
221
414
  if (old_value === undefined || (ready && new_value !== old_value)) {
@@ -227,38 +420,74 @@ function page_store(value) {
227
420
  return { notify, set, subscribe };
228
421
  }
229
422
 
423
+ /**
424
+ * @param {RequestInfo} resource
425
+ * @param {RequestInit} [opts]
426
+ */
427
+ function initial_fetch(resource, opts) {
428
+ const url = typeof resource === 'string' ? resource : resource.url;
429
+
430
+ let selector = `script[data-type="svelte-data"][data-url=${JSON.stringify(url)}]`;
431
+
432
+ if (opts && typeof opts.body === 'string') {
433
+ selector += `[data-body="${hash(opts.body)}"]`;
434
+ }
435
+
436
+ const script = document.querySelector(selector);
437
+ if (script && script.textContent) {
438
+ const { body, ...init } = JSON.parse(script.textContent);
439
+ return Promise.resolve(new Response(body, init));
440
+ }
441
+
442
+ return fetch(resource, opts);
443
+ }
444
+
230
445
  class Renderer {
231
- constructor({ Root, layout, target, error, status, preloaded, session }) {
446
+ /**
447
+ * @param {{
448
+ * Root: CSRComponent;
449
+ * fallback: [CSRComponent, CSRComponent];
450
+ * target: Node;
451
+ * session: any;
452
+ * host: string;
453
+ * }} opts
454
+ */
455
+ constructor({ Root, fallback, target, session, host }) {
232
456
  this.Root = Root;
233
- this.layout = layout;
234
- this.layout_loader = () => layout;
457
+ this.fallback = fallback;
458
+ this.host = host;
459
+
460
+ /** @type {import('./router').Router | undefined} */
461
+ this.router;
235
462
 
236
- // TODO ideally we wouldn't need to store these...
237
463
  this.target = target;
238
464
 
239
- this.initial = {
240
- preloaded,
241
- error,
242
- status
243
- };
465
+ this.started = false;
466
+
467
+ this.session_id = 1;
468
+ this.invalid = new Set();
469
+ this.invalidating = null;
244
470
 
471
+ /** @type {import('./types').NavigationState} */
245
472
  this.current = {
473
+ // @ts-ignore - we need the initial value to be null
246
474
  page: null,
247
- query: null,
248
- session_changed: false,
249
- nodes: []
475
+ session_id: 0,
476
+ branch: []
250
477
  };
251
478
 
252
- this.caches = new Map();
479
+ /** @type {Map<string, import('./types').NavigationResult>} */
480
+ this.cache = new Map();
253
481
 
254
- this.prefetching = {
255
- href: null,
482
+ /** @type {{id: string | null, promise: Promise<import('./types').NavigationResult> | null}} */
483
+ this.loading = {
484
+ id: null,
256
485
  promise: null
257
486
  };
258
487
 
259
488
  this.stores = {
260
489
  page: page_store({}),
261
- navigating: writable(null),
490
+ navigating: writable(/** @type {Navigating | null} */ (null)),
262
491
  session: writable(session)
263
492
  };
264
493
 
@@ -266,326 +495,701 @@ class Renderer {
266
495
 
267
496
  this.root = null;
268
497
 
269
- const trigger_prefetch = (event) => {
270
- const a = find_anchor(event.target);
271
-
272
- if (a && a.hasAttribute('sveltekit:prefetch')) {
273
- this.prefetch(new URL(a.href));
274
- }
275
- };
276
-
277
- let mousemove_timeout;
278
- const handle_mousemove = (event) => {
279
- clearTimeout(mousemove_timeout);
280
- mousemove_timeout = setTimeout(() => {
281
- trigger_prefetch(event);
282
- }, 20);
283
- };
284
-
285
- addEventListener('touchstart', trigger_prefetch);
286
- addEventListener('mousemove', handle_mousemove);
287
-
288
498
  let ready = false;
289
499
  this.stores.session.subscribe(async (value) => {
290
500
  this.$session = value;
291
501
 
292
- if (!ready) return;
293
- this.current.session_changed = true;
502
+ if (!ready || !this.router) return;
503
+ this.session_id += 1;
294
504
 
295
- const selected = this.router.select(new URL(location.href));
296
- this.render(selected);
505
+ const info = this.router.parse(new URL(location.href));
506
+ if (info) this.update(info, [], true);
297
507
  });
298
508
  ready = true;
299
509
  }
300
510
 
301
- async start(selected) {
302
- const props = {
303
- stores: this.stores,
304
- error: this.initial.error,
305
- status: this.initial.status,
306
- page: selected.page
307
- };
511
+ /**
512
+ * @param {{
513
+ * status: number;
514
+ * error: Error;
515
+ * nodes: Array<Promise<CSRComponent>>;
516
+ * page: import('types/page').Page;
517
+ * }} selected
518
+ */
519
+ async start({ status, error, nodes, page }) {
520
+ /** @type {Array<import('./types').BranchNode | undefined>} */
521
+ const branch = [];
522
+
523
+ /** @type {Record<string, any>} */
524
+ let stuff = {};
525
+
526
+ /** @type {import('./types').NavigationResult | undefined} */
527
+ let result;
528
+
529
+ let error_args;
530
+
531
+ try {
532
+ for (let i = 0; i < nodes.length; i += 1) {
533
+ const is_leaf = i === nodes.length - 1;
534
+
535
+ const node = await this._load_node({
536
+ module: await nodes[i],
537
+ page,
538
+ stuff,
539
+ status: is_leaf ? status : undefined,
540
+ error: is_leaf ? error : undefined
541
+ });
542
+
543
+ branch.push(node);
544
+
545
+ if (node && node.loaded) {
546
+ if (node.loaded.error) {
547
+ if (error) throw node.loaded.error;
548
+ error_args = {
549
+ status: node.loaded.status,
550
+ error: node.loaded.error,
551
+ path: page.path,
552
+ query: page.query
553
+ };
554
+ } else if (node.loaded.stuff) {
555
+ stuff = {
556
+ ...stuff,
557
+ ...node.loaded.stuff
558
+ };
559
+ }
560
+ }
561
+ }
562
+
563
+ result = error_args
564
+ ? await this._load_error(error_args)
565
+ : await this._get_navigation_result_from_branch({ page, branch });
566
+ } catch (e) {
567
+ if (error) throw e;
568
+
569
+ result = await this._load_error({
570
+ status: 500,
571
+ error: coalesce_to_error(e),
572
+ path: page.path,
573
+ query: page.query
574
+ });
575
+ }
576
+
577
+ if (result.redirect) {
578
+ // this is a real edge case — `load` would need to return
579
+ // a redirect but only in the browser
580
+ location.href = new URL(result.redirect, location.href).href;
581
+ return;
582
+ }
583
+
584
+ this._init(result);
585
+ }
586
+
587
+ /**
588
+ * @param {import('./types').NavigationInfo} info
589
+ * @param {string[]} chain
590
+ * @param {boolean} no_cache
591
+ * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean}} [opts]
592
+ */
593
+ async handle_navigation(info, chain, no_cache, opts) {
594
+ if (this.started) {
595
+ this.stores.navigating.set({
596
+ from: {
597
+ path: this.current.page.path,
598
+ query: this.current.page.query
599
+ },
600
+ to: {
601
+ path: info.path,
602
+ query: info.query
603
+ }
604
+ });
605
+ }
606
+
607
+ await this.update(info, chain, no_cache, opts);
608
+ }
609
+
610
+ /**
611
+ * @param {import('./types').NavigationInfo} info
612
+ * @param {string[]} chain
613
+ * @param {boolean} no_cache
614
+ * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean}} [opts]
615
+ */
616
+ async update(info, chain, no_cache, opts) {
617
+ const token = (this.token = {});
618
+ let navigation_result = await this._get_navigation_result(info, no_cache);
619
+
620
+ // abort if user navigated during update
621
+ if (token !== this.token) return;
622
+
623
+ this.invalid.clear();
624
+
625
+ if (navigation_result.redirect) {
626
+ if (chain.length > 10 || chain.includes(info.path)) {
627
+ navigation_result = await this._load_error({
628
+ status: 500,
629
+ error: new Error('Redirect loop'),
630
+ path: info.path,
631
+ query: info.query
632
+ });
633
+ } else {
634
+ if (this.router) {
635
+ this.router.goto(navigation_result.redirect, { replaceState: true }, [
636
+ ...chain,
637
+ info.path
638
+ ]);
639
+ } else {
640
+ location.href = new URL(navigation_result.redirect, location.href).href;
641
+ }
308
642
 
309
- if (this.initial.error) {
310
- props.components = [this.layout.default];
643
+ return;
644
+ }
645
+ }
646
+
647
+ if (navigation_result.reload) {
648
+ location.reload();
649
+ } else if (this.started) {
650
+ this.current = navigation_result.state;
651
+
652
+ this.root.$set(navigation_result.props);
653
+ this.stores.navigating.set(null);
311
654
  } else {
312
- const hydrated = await this.hydrate(selected);
655
+ this._init(navigation_result);
656
+ }
657
+
658
+ if (!opts?.keepfocus) {
659
+ document.body.focus();
660
+ }
313
661
 
314
- if (hydrated.redirect) {
315
- throw new Error('TODO client-side redirects');
662
+ await 0;
663
+
664
+ // After `await 0`, the onMount() function in the component executed.
665
+ // If there was no scrolling happening (checked via pageYOffset),
666
+ // continue on our custom scroll handling
667
+ if (pageYOffset === 0 && opts) {
668
+ const { hash, scroll } = opts;
669
+
670
+ const deep_linked = hash && document.getElementById(hash.slice(1));
671
+ if (scroll) {
672
+ scrollTo(scroll.x, scroll.y);
673
+ } else if (deep_linked) {
674
+ // Here we use `scrollIntoView` on the element instead of `scrollTo`
675
+ // because it natively supports the `scroll-margin` and `scroll-behavior`
676
+ // CSS properties.
677
+ deep_linked.scrollIntoView();
678
+ } else {
679
+ scrollTo(0, 0);
316
680
  }
681
+ }
682
+
683
+ this.loading.promise = null;
684
+ this.loading.id = null;
685
+
686
+ if (!this.router) return;
687
+
688
+ const leaf_node = navigation_result.state.branch[navigation_result.state.branch.length - 1];
689
+ if (leaf_node && leaf_node.module.router === false) {
690
+ this.router.disable();
691
+ } else {
692
+ this.router.enable();
693
+ }
694
+ }
317
695
 
318
- Object.assign(props, hydrated.props);
319
- this.current = hydrated.state;
696
+ /**
697
+ * @param {import('./types').NavigationInfo} info
698
+ * @returns {Promise<import('./types').NavigationResult>}
699
+ */
700
+ load(info) {
701
+ this.loading.promise = this._get_navigation_result(info, false);
702
+ this.loading.id = info.id;
703
+
704
+ return this.loading.promise;
705
+ }
706
+
707
+ /** @param {string} href */
708
+ invalidate(href) {
709
+ this.invalid.add(href);
710
+
711
+ if (!this.invalidating) {
712
+ this.invalidating = Promise.resolve().then(async () => {
713
+ const info = this.router && this.router.parse(new URL(location.href));
714
+ if (info) await this.update(info, [], true);
715
+
716
+ this.invalidating = null;
717
+ });
320
718
  }
321
719
 
720
+ return this.invalidating;
721
+ }
722
+
723
+ /** @param {import('./types').NavigationResult} result */
724
+ _init(result) {
725
+ this.current = result.state;
726
+
727
+ const style = document.querySelector('style[data-svelte]');
728
+ if (style) style.remove();
729
+
322
730
  this.root = new this.Root({
323
731
  target: this.target,
324
- props,
732
+ props: {
733
+ stores: this.stores,
734
+ ...result.props
735
+ },
325
736
  hydrate: true
326
737
  });
327
738
 
328
- this.initial = null;
739
+ this.started = true;
329
740
  }
330
741
 
331
- notify(selected) {
332
- this.stores.navigating.set({
333
- from: this.current.page,
334
- to: selected.page
742
+ /**
743
+ * @param {import('./types').NavigationInfo} info
744
+ * @param {boolean} no_cache
745
+ * @returns {Promise<import('./types').NavigationResult>}
746
+ */
747
+ async _get_navigation_result(info, no_cache) {
748
+ if (this.loading.id === info.id && this.loading.promise) {
749
+ return this.loading.promise;
750
+ }
751
+
752
+ for (let i = 0; i < info.routes.length; i += 1) {
753
+ const route = info.routes[i];
754
+
755
+ // load code for subsequent routes immediately, if they are as
756
+ // likely to match the current path/query as the current one
757
+ let j = i + 1;
758
+ while (j < info.routes.length) {
759
+ const next = info.routes[j];
760
+ if (next[0].toString() === route[0].toString()) {
761
+ next[1].forEach((loader) => loader());
762
+ j += 1;
763
+ } else {
764
+ break;
765
+ }
766
+ }
767
+
768
+ const result = await this._load(
769
+ {
770
+ route,
771
+ info
772
+ },
773
+ no_cache
774
+ );
775
+ if (result) return result;
776
+ }
777
+
778
+ return await this._load_error({
779
+ status: 404,
780
+ error: new Error(`Not found: ${info.path}`),
781
+ path: info.path,
782
+ query: info.query
335
783
  });
336
784
  }
337
785
 
338
- async render(selected) {
339
- const token = (this.token = {});
786
+ /**
787
+ *
788
+ * @param {{
789
+ * page: import('types/page').Page;
790
+ * branch: Array<import('./types').BranchNode | undefined>
791
+ * }} opts
792
+ */
793
+ async _get_navigation_result_from_branch({ page, branch }) {
794
+ const filtered = /** @type {import('./types').BranchNode[] } */ (branch.filter(Boolean));
795
+ const redirect = filtered.find((f) => f.loaded && f.loaded.redirect);
796
+
797
+ /** @type {import('./types').NavigationResult} */
798
+ const result = {
799
+ redirect: redirect && redirect.loaded ? redirect.loaded.redirect : undefined,
800
+ state: {
801
+ page,
802
+ branch,
803
+ session_id: this.session_id
804
+ },
805
+ props: {
806
+ components: filtered.map((node) => node.module.default)
807
+ }
808
+ };
340
809
 
341
- const hydrated = await this.hydrate(selected);
810
+ for (let i = 0; i < filtered.length; i += 1) {
811
+ const loaded = filtered[i].loaded;
812
+ result.props[`props_${i}`] = loaded ? await loaded.props : null;
813
+ }
342
814
 
343
- if (this.token === token) {
344
- // check render wasn't aborted
345
- this.current = hydrated.state;
815
+ if (
816
+ !this.current.page ||
817
+ page.path !== this.current.page.path ||
818
+ page.query.toString() !== this.current.page.query.toString()
819
+ ) {
820
+ result.props.page = page;
821
+ }
346
822
 
347
- this.root.$set(hydrated.props);
348
- this.stores.navigating.set(null);
823
+ const leaf = filtered[filtered.length - 1];
824
+ const maxage = leaf.loaded && leaf.loaded.maxage;
825
+
826
+ if (maxage) {
827
+ const key = `${page.path}?${page.query}`;
828
+ let ready = false;
829
+
830
+ const clear = () => {
831
+ if (this.cache.get(key) === result) {
832
+ this.cache.delete(key);
833
+ }
834
+
835
+ unsubscribe();
836
+ clearTimeout(timeout);
837
+ };
838
+
839
+ const timeout = setTimeout(clear, maxage * 1000);
840
+
841
+ const unsubscribe = this.stores.session.subscribe(() => {
842
+ if (ready) clear();
843
+ });
844
+
845
+ ready = true;
846
+
847
+ this.cache.set(key, result);
349
848
  }
849
+
850
+ return result;
350
851
  }
351
852
 
352
- async hydrate({ route, page }) {
353
- const props = {
354
- error: null,
355
- status: 200,
356
- components: []
853
+ /**
854
+ * @param {{
855
+ * status?: number;
856
+ * error?: Error;
857
+ * module: CSRComponent;
858
+ * page: import('types/page').Page;
859
+ * stuff: Record<string, any>;
860
+ * }} options
861
+ * @returns
862
+ */
863
+ async _load_node({ status, error, module, page, stuff }) {
864
+ /** @type {import('./types').BranchNode} */
865
+ const node = {
866
+ module,
867
+ uses: {
868
+ params: new Set(),
869
+ path: false,
870
+ query: false,
871
+ session: false,
872
+ stuff: false,
873
+ dependencies: []
874
+ },
875
+ loaded: null,
876
+ stuff
357
877
  };
358
878
 
359
- const fetcher = (url, opts) => {
360
- if (this.initial) {
361
- const script = document.querySelector(`script[type="svelte-data"][url="${url}"]`);
362
- if (script) {
363
- const { body, ...init } = JSON.parse(script.textContent);
364
- return Promise.resolve(new Response(body, init));
879
+ /** @type {Record<string, string>} */
880
+ const params = {};
881
+ for (const key in page.params) {
882
+ Object.defineProperty(params, key, {
883
+ get() {
884
+ node.uses.params.add(key);
885
+ return page.params[key];
886
+ },
887
+ enumerable: true
888
+ });
889
+ }
890
+
891
+ const session = this.$session;
892
+
893
+ if (module.load) {
894
+ const { started } = this;
895
+
896
+ /** @type {import('types/page').LoadInput | import('types/page').ErrorLoadInput} */
897
+ const load_input = {
898
+ page: {
899
+ host: page.host,
900
+ params,
901
+ get path() {
902
+ node.uses.path = true;
903
+ return page.path;
904
+ },
905
+ get query() {
906
+ node.uses.query = true;
907
+ return page.query;
908
+ }
909
+ },
910
+ get session() {
911
+ node.uses.session = true;
912
+ return session;
913
+ },
914
+ get stuff() {
915
+ node.uses.stuff = true;
916
+ return { ...stuff };
917
+ },
918
+ fetch(resource, info) {
919
+ const url = typeof resource === 'string' ? resource : resource.url;
920
+ const { href } = new URL(url, new URL(page.path, document.baseURI));
921
+ node.uses.dependencies.push(href);
922
+
923
+ return started ? fetch(resource, info) : initial_fetch(resource, info);
365
924
  }
925
+ };
926
+
927
+ if (error) {
928
+ /** @type {import('types/page').ErrorLoadInput} */ (load_input).status = status;
929
+ /** @type {import('types/page').ErrorLoadInput} */ (load_input).error = error;
366
930
  }
367
931
 
368
- return fetch(url, opts);
369
- };
932
+ const loaded = await module.load.call(null, load_input);
370
933
 
371
- const query = page.query.toString();
934
+ // if the page component returns nothing from load, fall through
935
+ if (!loaded) return;
372
936
 
373
- const state = {
374
- page,
375
- query,
376
- session_changed: false,
377
- nodes: []
378
- };
937
+ node.loaded = normalize(loaded);
938
+ if (node.loaded.stuff) node.stuff = node.loaded.stuff;
939
+ }
379
940
 
380
- const component_promises = [this.layout_loader(), ...route.parts.map((loader) => loader())];
381
- const props_promises = [];
941
+ return node;
942
+ }
382
943
 
383
- let context = {};
384
- let redirect;
944
+ /**
945
+ * @param {import('./types').NavigationCandidate} selected
946
+ * @param {boolean} no_cache
947
+ * @returns {Promise<import('./types').NavigationResult | undefined>} undefined if fallthrough
948
+ */
949
+ async _load({ route, info: { path, decoded_path, query } }, no_cache) {
950
+ const key = `${decoded_path}?${query}`;
951
+
952
+ if (!no_cache) {
953
+ const cached = this.cache.get(key);
954
+ if (cached) return cached;
955
+ }
385
956
 
386
- const changed = {
387
- params: Object.keys(page.params).filter((key) => {
388
- return !this.current.page || this.current.page.params[key] !== page.params[key];
389
- }),
390
- query: query !== this.current.query,
391
- session: this.current.session_changed,
392
- context: false
957
+ const [pattern, a, b, get_params] = route;
958
+ const params = get_params
959
+ ? // the pattern is for the route which we've already matched to this path
960
+ get_params(/** @type {RegExpExecArray} */ (pattern.exec(decoded_path)))
961
+ : {};
962
+
963
+ const changed = this.current.page && {
964
+ path: path !== this.current.page.path,
965
+ params: Object.keys(params).filter((key) => this.current.page.params[key] !== params[key]),
966
+ query: query.toString() !== this.current.page.query.toString(),
967
+ session: this.session_id !== this.current.session_id
393
968
  };
394
969
 
395
- try {
396
- for (let i = 0; i < component_promises.length; i += 1) {
397
- const previous = this.current.nodes[i];
970
+ /** @type {import('types/page').Page} */
971
+ const page = { host: this.host, path, query, params };
972
+
973
+ /** @type {Array<import('./types').BranchNode | undefined>} */
974
+ let branch = [];
398
975
 
399
- const { default: component, load } = await component_promises[i];
400
- props.components[i] = component;
976
+ /** @type {Record<string, any>} */
977
+ let stuff = {};
978
+ let stuff_changed = false;
979
+
980
+ /** @type {number | undefined} */
981
+ let status = 200;
982
+
983
+ /** @type {Error | undefined} */
984
+ let error;
985
+
986
+ // preload modules
987
+ a.forEach((loader) => loader());
988
+
989
+ load: for (let i = 0; i < a.length; i += 1) {
990
+ /** @type {import('./types').BranchNode | undefined} */
991
+ let node;
992
+
993
+ try {
994
+ if (!a[i]) continue;
995
+
996
+ const module = await a[i]();
997
+ const previous = this.current.branch[i];
401
998
 
402
999
  const changed_since_last_render =
403
1000
  !previous ||
404
- component !== previous.component ||
1001
+ module !== previous.module ||
1002
+ (changed.path && previous.uses.path) ||
405
1003
  changed.params.some((param) => previous.uses.params.has(param)) ||
406
1004
  (changed.query && previous.uses.query) ||
407
1005
  (changed.session && previous.uses.session) ||
408
- (changed.context && previous.uses.context);
1006
+ previous.uses.dependencies.some((dep) => this.invalid.has(dep)) ||
1007
+ (stuff_changed && previous.uses.stuff);
409
1008
 
410
1009
  if (changed_since_last_render) {
411
- const hash = page.path + query;
412
-
413
- // see if we have some cached data
414
- const cache = this.caches.get(component);
415
- const cached = cache && cache.get(hash);
416
-
417
- let node;
418
- let loaded;
419
-
420
- if (cached && (!changed.context || !cached.node.uses.context)) {
421
- ({ node, loaded } = cached);
422
- } else {
423
- node = {
424
- component,
425
- uses: {
426
- params: new Set(),
427
- query: false,
428
- session: false,
429
- context: false
430
- }
431
- };
432
-
433
- const params = {};
434
- for (const key in page.params) {
435
- Object.defineProperty(params, key, {
436
- get() {
437
- node.uses.params.add(key);
438
- return page.params[key];
439
- },
440
- enumerable: true
441
- });
1010
+ node = await this._load_node({
1011
+ module,
1012
+ page,
1013
+ stuff
1014
+ });
1015
+
1016
+ const is_leaf = i === a.length - 1;
1017
+
1018
+ if (node && node.loaded) {
1019
+ if (node.loaded.error) {
1020
+ status = node.loaded.status;
1021
+ error = node.loaded.error;
442
1022
  }
443
1023
 
444
- const session = this.$session;
445
-
446
- loaded =
447
- load &&
448
- (await load.call(null, {
449
- page: {
450
- ...page,
451
- params,
452
- get query() {
453
- node.uses.query = true;
454
- return page.query;
455
- }
456
- },
457
- get session() {
458
- node.uses.session = true;
459
- return session;
460
- },
461
- get context() {
462
- node.uses.context = true;
463
- return { ...context };
464
- },
465
- fetch: fetcher
466
- }));
467
- }
468
-
469
- if (loaded) {
470
- if (loaded.error) {
471
- const error = new Error(loaded.error.message);
472
- error.status = loaded.error.status;
473
- throw error;
1024
+ if (node.loaded.redirect) {
1025
+ return {
1026
+ redirect: node.loaded.redirect,
1027
+ props: {},
1028
+ state: this.current
1029
+ };
474
1030
  }
475
1031
 
476
- if (loaded.redirect) {
477
- redirect = loaded.redirect;
478
- break;
1032
+ if (node.loaded.stuff) {
1033
+ stuff_changed = true;
479
1034
  }
1035
+ } else if (is_leaf && module.load) {
1036
+ // if the leaf node has a `load` function
1037
+ // that returns nothing, fall through
1038
+ return;
1039
+ }
1040
+ } else {
1041
+ node = previous;
1042
+ }
1043
+ } catch (e) {
1044
+ status = 500;
1045
+ error = coalesce_to_error(e);
1046
+ }
480
1047
 
481
- if (loaded.context) {
482
- changed.context = true;
1048
+ if (error) {
1049
+ while (i--) {
1050
+ if (b[i]) {
1051
+ let error_loaded;
483
1052
 
484
- context = {
485
- ...context,
486
- ...loaded.context
487
- };
1053
+ /** @type {import('./types').BranchNode | undefined} */
1054
+ let node_loaded;
1055
+ let j = i;
1056
+ while (!(node_loaded = branch[j])) {
1057
+ j -= 1;
488
1058
  }
489
1059
 
490
- if (loaded.maxage) {
491
- if (!this.caches.has(component)) {
492
- this.caches.set(component, new Map());
493
- }
494
-
495
- const cache = this.caches.get(component);
496
- const cached = { node, loaded };
497
-
498
- cache.set(hash, cached);
499
-
500
- let ready = false;
501
-
502
- const timeout = setTimeout(() => {
503
- clear();
504
- }, loaded.maxage * 1000);
505
-
506
- const clear = () => {
507
- if (cache.get(hash) === cached) {
508
- cache.delete(hash);
509
- }
510
-
511
- unsubscribe();
512
- clearTimeout(timeout);
513
- };
514
-
515
- const unsubscribe = this.stores.session.subscribe(() => {
516
- if (ready) clear();
1060
+ try {
1061
+ error_loaded = await this._load_node({
1062
+ status,
1063
+ error,
1064
+ module: await b[i](),
1065
+ page,
1066
+ stuff: node_loaded.stuff
517
1067
  });
518
1068
 
519
- ready = true;
520
- }
1069
+ if (error_loaded && error_loaded.loaded && error_loaded.loaded.error) {
1070
+ continue;
1071
+ }
521
1072
 
522
- props_promises[i] = loaded.props;
1073
+ branch = branch.slice(0, j + 1).concat(error_loaded);
1074
+ break load;
1075
+ } catch (e) {
1076
+ continue;
1077
+ }
523
1078
  }
524
-
525
- state.nodes[i] = node;
526
- } else {
527
- state.nodes[i] = previous;
528
1079
  }
529
- }
530
-
531
- const new_props = await Promise.all(props_promises);
532
1080
 
533
- new_props.forEach((p, i) => {
534
- if (p) {
535
- props[`props_${i}`] = p;
1081
+ return await this._load_error({
1082
+ status,
1083
+ error,
1084
+ path,
1085
+ query
1086
+ });
1087
+ } else {
1088
+ if (node && node.loaded && node.loaded.stuff) {
1089
+ stuff = {
1090
+ ...stuff,
1091
+ ...node.loaded.stuff
1092
+ };
536
1093
  }
537
- });
538
1094
 
539
- if (!this.current.page || page.path !== this.current.page.path) {
540
- props.page = page;
1095
+ branch.push(node);
541
1096
  }
542
- } catch (error) {
543
- props.error = error;
544
- props.status = 500;
545
- state.nodes = [];
546
1097
  }
547
1098
 
548
- return { redirect, props, state };
1099
+ return await this._get_navigation_result_from_branch({ page, branch });
549
1100
  }
550
1101
 
551
- async prefetch(url) {
552
- const page = this.router.select(url);
1102
+ /**
1103
+ * @param {{
1104
+ * status?: number;
1105
+ * error: Error;
1106
+ * path: string;
1107
+ * query: URLSearchParams
1108
+ * }} opts
1109
+ */
1110
+ async _load_error({ status, error, path, query }) {
1111
+ const page = {
1112
+ host: this.host,
1113
+ path,
1114
+ query,
1115
+ params: {}
1116
+ };
553
1117
 
554
- if (page) {
555
- if (url.href !== this.prefetching.href) {
556
- this.prefetching = { href: url.href, promise: this.hydrate(page) };
557
- }
1118
+ const node = await this._load_node({
1119
+ module: await this.fallback[0],
1120
+ page,
1121
+ stuff: {}
1122
+ });
558
1123
 
559
- return this.prefetching.promise;
560
- } else {
561
- throw new Error(`Could not prefetch ${url.href}`);
562
- }
1124
+ const branch = [
1125
+ node,
1126
+ await this._load_node({
1127
+ status,
1128
+ error,
1129
+ module: await this.fallback[1],
1130
+ page,
1131
+ stuff: (node && node.loaded && node.loaded.stuff) || {}
1132
+ })
1133
+ ];
1134
+
1135
+ return await this._get_navigation_result_from_branch({ page, branch });
563
1136
  }
564
1137
  }
565
1138
 
566
- async function start({ paths, target, host, session, preloaded, error, status }) {
567
- const router = new Router({
568
- base: paths.base,
569
- host,
570
- pages,
571
- ignore
572
- });
1139
+ // @ts-expect-error - doesn't exist yet. generated by Rollup
1140
+
1141
+ /**
1142
+ * @param {{
1143
+ * paths: {
1144
+ * assets: string;
1145
+ * base: string;
1146
+ * },
1147
+ * target: Node;
1148
+ * session: any;
1149
+ * host: string;
1150
+ * route: boolean;
1151
+ * spa: boolean;
1152
+ * trailing_slash: import('types/internal').TrailingSlash;
1153
+ * hydrate: {
1154
+ * status: number;
1155
+ * error: Error;
1156
+ * nodes: Array<Promise<import('types/internal').CSRComponent>>;
1157
+ * page: import('types/page').Page;
1158
+ * };
1159
+ * }} opts
1160
+ */
1161
+ async function start({ paths, target, session, host, route, spa, trailing_slash, hydrate }) {
1162
+ if (import.meta.env.DEV && !target) {
1163
+ throw new Error('Missing target element. See https://kit.svelte.dev/docs#configuration-target');
1164
+ }
573
1165
 
574
1166
  const renderer = new Renderer({
575
1167
  Root,
576
- layout,
1168
+ fallback,
577
1169
  target,
578
- preloaded,
579
- error,
580
- status,
581
- session
1170
+ session,
1171
+ host
582
1172
  });
583
1173
 
584
- init({ router, renderer });
1174
+ const router = route
1175
+ ? new Router({
1176
+ base: paths.base,
1177
+ routes,
1178
+ trailing_slash,
1179
+ renderer
1180
+ })
1181
+ : null;
1182
+
1183
+ init(router);
585
1184
  set_paths(paths);
586
1185
 
587
- await router.init({ renderer });
1186
+ if (hydrate) await renderer.start(hydrate);
1187
+ if (router) {
1188
+ if (spa) router.goto(location.href, { replaceState: true }, []);
1189
+ router.init_listeners();
1190
+ }
1191
+
1192
+ dispatchEvent(new CustomEvent('sveltekit:start'));
588
1193
  }
589
1194
 
590
1195
  export { start };
591
- //# sourceMappingURL=start.js.map