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