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