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