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