@sveltejs/kit 1.0.0-next.22 → 1.0.0-next.220
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 +1935 -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 +16 -10
- 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 +991 -356
- 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 +974 -83
- package/dist/hooks.js +28 -0
- package/dist/install-fetch.js +6514 -0
- package/dist/node.js +51 -0
- package/dist/ssr.js +1865 -0
- package/package.json +96 -54
- package/svelte-kit.js +2 -0
- package/types/ambient-modules.d.ts +190 -0
- package/types/app.d.ts +45 -0
- package/types/config.d.ts +165 -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 -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
|
+
}
|
|
129
197
|
|
|
130
|
-
|
|
131
|
-
|
|
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
|
+
}
|
|
214
|
+
|
|
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
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
enable() {
|
|
241
|
+
this.enabled = true;
|
|
135
242
|
}
|
|
136
243
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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 };
|
|
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
|
+
};
|
|
200
381
|
}
|
|
201
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
|
+
);
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
470
|
+
this.started = false;
|
|
471
|
+
|
|
472
|
+
this.session_id = 1;
|
|
473
|
+
this.invalid = new Set();
|
|
474
|
+
this.invalidating = null;
|
|
475
|
+
this.autoscroll = true;
|
|
476
|
+
this.updating = false;
|
|
244
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,724 @@ 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
|
-
status: this.initial.status,
|
|
306
|
-
page: selected.page
|
|
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
|
+
}
|
|
308
523
|
|
|
309
|
-
if (this.
|
|
310
|
-
|
|
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({ url, params, branch, status, error });
|
|
588
|
+
} catch (e) {
|
|
589
|
+
if (error) throw e;
|
|
590
|
+
|
|
591
|
+
result = await this._load_error({
|
|
592
|
+
status: 500,
|
|
593
|
+
error: coalesce_to_error(e),
|
|
594
|
+
url
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (result.redirect) {
|
|
599
|
+
// this is a real edge case — `load` would need to return
|
|
600
|
+
// a redirect but only in the browser
|
|
601
|
+
location.href = new URL(result.redirect, location.href).href;
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
this._init(result);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* @param {import('./types').NavigationInfo} info
|
|
610
|
+
* @param {string[]} chain
|
|
611
|
+
* @param {boolean} no_cache
|
|
612
|
+
* @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean}} [opts]
|
|
613
|
+
*/
|
|
614
|
+
async handle_navigation(info, chain, no_cache, opts) {
|
|
615
|
+
if (this.started) {
|
|
616
|
+
this.stores.navigating.set({
|
|
617
|
+
from: this.current.url,
|
|
618
|
+
to: info.url
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
await this.update(info, chain, no_cache, opts);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* @param {import('./types').NavigationInfo} info
|
|
627
|
+
* @param {string[]} chain
|
|
628
|
+
* @param {boolean} no_cache
|
|
629
|
+
* @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean}} [opts]
|
|
630
|
+
*/
|
|
631
|
+
async update(info, chain, no_cache, opts) {
|
|
632
|
+
const token = (this.token = {});
|
|
633
|
+
let navigation_result = await this._get_navigation_result(info, no_cache);
|
|
634
|
+
|
|
635
|
+
// abort if user navigated during update
|
|
636
|
+
if (token !== this.token) return;
|
|
637
|
+
|
|
638
|
+
this.invalid.clear();
|
|
639
|
+
|
|
640
|
+
if (navigation_result.redirect) {
|
|
641
|
+
if (chain.length > 10 || chain.includes(info.url.pathname)) {
|
|
642
|
+
navigation_result = await this._load_error({
|
|
643
|
+
status: 500,
|
|
644
|
+
error: new Error('Redirect loop'),
|
|
645
|
+
url: info.url
|
|
646
|
+
});
|
|
647
|
+
} else {
|
|
648
|
+
if (this.router) {
|
|
649
|
+
this.router.goto(navigation_result.redirect, { replaceState: true }, [
|
|
650
|
+
...chain,
|
|
651
|
+
info.url.pathname
|
|
652
|
+
]);
|
|
653
|
+
} else {
|
|
654
|
+
location.href = new URL(navigation_result.redirect, location.href).href;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
this.updating = true;
|
|
662
|
+
|
|
663
|
+
if (this.started) {
|
|
664
|
+
this.current = navigation_result.state;
|
|
665
|
+
|
|
666
|
+
this.root.$set(navigation_result.props);
|
|
667
|
+
this.stores.navigating.set(null);
|
|
311
668
|
} else {
|
|
312
|
-
|
|
669
|
+
this._init(navigation_result);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// opts must be passed if we're navigating
|
|
673
|
+
if (opts) {
|
|
674
|
+
const { hash, scroll, keepfocus } = opts;
|
|
675
|
+
|
|
676
|
+
if (!keepfocus) {
|
|
677
|
+
getSelection()?.removeAllRanges();
|
|
678
|
+
document.body.focus();
|
|
679
|
+
}
|
|
313
680
|
|
|
314
|
-
|
|
315
|
-
|
|
681
|
+
// need to render the DOM before we can scroll to the rendered elements
|
|
682
|
+
await tick();
|
|
683
|
+
|
|
684
|
+
if (this.autoscroll) {
|
|
685
|
+
const deep_linked = hash && document.getElementById(hash.slice(1));
|
|
686
|
+
if (scroll) {
|
|
687
|
+
scrollTo(scroll.x, scroll.y);
|
|
688
|
+
} else if (deep_linked) {
|
|
689
|
+
// Here we use `scrollIntoView` on the element instead of `scrollTo`
|
|
690
|
+
// because it natively supports the `scroll-margin` and `scroll-behavior`
|
|
691
|
+
// CSS properties.
|
|
692
|
+
deep_linked.scrollIntoView();
|
|
693
|
+
} else {
|
|
694
|
+
scrollTo(0, 0);
|
|
695
|
+
}
|
|
316
696
|
}
|
|
697
|
+
} else {
|
|
698
|
+
// in this case we're simply invalidating
|
|
699
|
+
await tick();
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
this.loading.promise = null;
|
|
703
|
+
this.loading.id = null;
|
|
704
|
+
this.autoscroll = true;
|
|
705
|
+
this.updating = false;
|
|
706
|
+
|
|
707
|
+
if (!this.router) return;
|
|
708
|
+
|
|
709
|
+
const leaf_node = navigation_result.state.branch[navigation_result.state.branch.length - 1];
|
|
710
|
+
if (leaf_node && leaf_node.module.router === false) {
|
|
711
|
+
this.router.disable();
|
|
712
|
+
} else {
|
|
713
|
+
this.router.enable();
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* @param {import('./types').NavigationInfo} info
|
|
719
|
+
* @returns {Promise<import('./types').NavigationResult>}
|
|
720
|
+
*/
|
|
721
|
+
load(info) {
|
|
722
|
+
this.loading.promise = this._get_navigation_result(info, false);
|
|
723
|
+
this.loading.id = info.id;
|
|
724
|
+
|
|
725
|
+
return this.loading.promise;
|
|
726
|
+
}
|
|
317
727
|
|
|
318
|
-
|
|
319
|
-
|
|
728
|
+
/** @param {string} href */
|
|
729
|
+
invalidate(href) {
|
|
730
|
+
this.invalid.add(href);
|
|
731
|
+
|
|
732
|
+
if (!this.invalidating) {
|
|
733
|
+
this.invalidating = Promise.resolve().then(async () => {
|
|
734
|
+
const info = this.router && this.router.parse(new URL(location.href));
|
|
735
|
+
if (info) await this.update(info, [], true);
|
|
736
|
+
|
|
737
|
+
this.invalidating = null;
|
|
738
|
+
});
|
|
320
739
|
}
|
|
321
740
|
|
|
741
|
+
return this.invalidating;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/** @param {import('./types').NavigationResult} result */
|
|
745
|
+
_init(result) {
|
|
746
|
+
this.current = result.state;
|
|
747
|
+
|
|
748
|
+
const style = document.querySelector('style[data-svelte]');
|
|
749
|
+
if (style) style.remove();
|
|
750
|
+
|
|
322
751
|
this.root = new this.Root({
|
|
323
752
|
target: this.target,
|
|
324
|
-
props
|
|
753
|
+
props: {
|
|
754
|
+
stores: this.stores,
|
|
755
|
+
...result.props
|
|
756
|
+
},
|
|
325
757
|
hydrate: true
|
|
326
758
|
});
|
|
327
759
|
|
|
328
|
-
this.
|
|
760
|
+
this.started = true;
|
|
329
761
|
}
|
|
330
762
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
763
|
+
/**
|
|
764
|
+
* @param {import('./types').NavigationInfo} info
|
|
765
|
+
* @param {boolean} no_cache
|
|
766
|
+
* @returns {Promise<import('./types').NavigationResult>}
|
|
767
|
+
*/
|
|
768
|
+
async _get_navigation_result(info, no_cache) {
|
|
769
|
+
if (this.loading.id === info.id && this.loading.promise) {
|
|
770
|
+
return this.loading.promise;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
for (let i = 0; i < info.routes.length; i += 1) {
|
|
774
|
+
const route = info.routes[i];
|
|
775
|
+
|
|
776
|
+
// load code for subsequent routes immediately, if they are as
|
|
777
|
+
// likely to match the current path/query as the current one
|
|
778
|
+
let j = i + 1;
|
|
779
|
+
while (j < info.routes.length) {
|
|
780
|
+
const next = info.routes[j];
|
|
781
|
+
if (next[0].toString() === route[0].toString()) {
|
|
782
|
+
next[1].forEach((loader) => loader());
|
|
783
|
+
j += 1;
|
|
784
|
+
} else {
|
|
785
|
+
break;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const result = await this._load(
|
|
790
|
+
{
|
|
791
|
+
route,
|
|
792
|
+
info
|
|
793
|
+
},
|
|
794
|
+
no_cache
|
|
795
|
+
);
|
|
796
|
+
if (result) return result;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
return await this._load_error({
|
|
800
|
+
status: 404,
|
|
801
|
+
error: new Error(`Not found: ${info.url.pathname}`),
|
|
802
|
+
url: info.url
|
|
335
803
|
});
|
|
336
804
|
}
|
|
337
805
|
|
|
338
|
-
|
|
339
|
-
|
|
806
|
+
/**
|
|
807
|
+
*
|
|
808
|
+
* @param {{
|
|
809
|
+
* url: URL;
|
|
810
|
+
* params: Record<string, string>;
|
|
811
|
+
* branch: Array<import('./types').BranchNode | undefined>;
|
|
812
|
+
* status: number;
|
|
813
|
+
* error?: Error;
|
|
814
|
+
* }} opts
|
|
815
|
+
*/
|
|
816
|
+
async _get_navigation_result_from_branch({ url, params, branch, status, error }) {
|
|
817
|
+
const filtered = /** @type {import('./types').BranchNode[] } */ (branch.filter(Boolean));
|
|
818
|
+
const redirect = filtered.find((f) => f.loaded && f.loaded.redirect);
|
|
819
|
+
|
|
820
|
+
/** @type {import('./types').NavigationResult} */
|
|
821
|
+
const result = {
|
|
822
|
+
redirect: redirect && redirect.loaded ? redirect.loaded.redirect : undefined,
|
|
823
|
+
state: {
|
|
824
|
+
url,
|
|
825
|
+
params,
|
|
826
|
+
branch,
|
|
827
|
+
session_id: this.session_id
|
|
828
|
+
},
|
|
829
|
+
props: {
|
|
830
|
+
components: filtered.map((node) => node.module.default)
|
|
831
|
+
}
|
|
832
|
+
};
|
|
340
833
|
|
|
341
|
-
|
|
834
|
+
for (let i = 0; i < filtered.length; i += 1) {
|
|
835
|
+
const loaded = filtered[i].loaded;
|
|
836
|
+
result.props[`props_${i}`] = loaded ? await loaded.props : null;
|
|
837
|
+
}
|
|
342
838
|
|
|
343
|
-
if (this.
|
|
344
|
-
|
|
345
|
-
|
|
839
|
+
if (!this.current.url || url.href !== this.current.url.href) {
|
|
840
|
+
result.props.page = { url, params, status, error };
|
|
841
|
+
|
|
842
|
+
// TODO remove this for 1.0
|
|
843
|
+
/**
|
|
844
|
+
* @param {string} property
|
|
845
|
+
* @param {string} replacement
|
|
846
|
+
*/
|
|
847
|
+
const print_error = (property, replacement) => {
|
|
848
|
+
Object.defineProperty(result.props.page, property, {
|
|
849
|
+
get: () => {
|
|
850
|
+
throw new Error(`$page.${property} has been replaced by $page.url.${replacement}`);
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
};
|
|
346
854
|
|
|
347
|
-
|
|
348
|
-
|
|
855
|
+
print_error('origin', 'origin');
|
|
856
|
+
print_error('path', 'pathname');
|
|
857
|
+
print_error('query', 'searchParams');
|
|
349
858
|
}
|
|
350
|
-
}
|
|
351
859
|
|
|
352
|
-
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
860
|
+
const leaf = filtered[filtered.length - 1];
|
|
861
|
+
const maxage = leaf.loaded && leaf.loaded.maxage;
|
|
862
|
+
|
|
863
|
+
if (maxage) {
|
|
864
|
+
const key = url.pathname + url.search; // omit hash
|
|
865
|
+
let ready = false;
|
|
358
866
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (script) {
|
|
363
|
-
const { body, ...init } = JSON.parse(script.textContent);
|
|
364
|
-
return Promise.resolve(new Response(body, init));
|
|
867
|
+
const clear = () => {
|
|
868
|
+
if (this.cache.get(key) === result) {
|
|
869
|
+
this.cache.delete(key);
|
|
365
870
|
}
|
|
366
|
-
}
|
|
367
871
|
|
|
368
|
-
|
|
369
|
-
|
|
872
|
+
unsubscribe();
|
|
873
|
+
clearTimeout(timeout);
|
|
874
|
+
};
|
|
370
875
|
|
|
371
|
-
|
|
876
|
+
const timeout = setTimeout(clear, maxage * 1000);
|
|
372
877
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
session_changed: false,
|
|
377
|
-
nodes: []
|
|
378
|
-
};
|
|
878
|
+
const unsubscribe = this.stores.session.subscribe(() => {
|
|
879
|
+
if (ready) clear();
|
|
880
|
+
});
|
|
379
881
|
|
|
380
|
-
|
|
381
|
-
const props_promises = [];
|
|
882
|
+
ready = true;
|
|
382
883
|
|
|
383
|
-
|
|
384
|
-
|
|
884
|
+
this.cache.set(key, result);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
return result;
|
|
888
|
+
}
|
|
385
889
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
890
|
+
/**
|
|
891
|
+
* @param {{
|
|
892
|
+
* status?: number;
|
|
893
|
+
* error?: Error;
|
|
894
|
+
* module: CSRComponent;
|
|
895
|
+
* url: URL;
|
|
896
|
+
* params: Record<string, string>;
|
|
897
|
+
* stuff: Record<string, any>;
|
|
898
|
+
* }} options
|
|
899
|
+
* @returns
|
|
900
|
+
*/
|
|
901
|
+
async _load_node({ status, error, module, url, params, stuff }) {
|
|
902
|
+
/** @type {import('./types').BranchNode} */
|
|
903
|
+
const node = {
|
|
904
|
+
module,
|
|
905
|
+
uses: {
|
|
906
|
+
params: new Set(),
|
|
907
|
+
url: false,
|
|
908
|
+
session: false,
|
|
909
|
+
stuff: false,
|
|
910
|
+
dependencies: []
|
|
911
|
+
},
|
|
912
|
+
loaded: null,
|
|
913
|
+
stuff
|
|
393
914
|
};
|
|
394
915
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
916
|
+
/** @type {Record<string, string>} */
|
|
917
|
+
const uses_params = {};
|
|
918
|
+
for (const key in params) {
|
|
919
|
+
Object.defineProperty(uses_params, key, {
|
|
920
|
+
get() {
|
|
921
|
+
node.uses.params.add(key);
|
|
922
|
+
return params[key];
|
|
923
|
+
},
|
|
924
|
+
enumerable: true
|
|
925
|
+
});
|
|
926
|
+
}
|
|
398
927
|
|
|
399
|
-
|
|
400
|
-
|
|
928
|
+
const session = this.$session;
|
|
929
|
+
|
|
930
|
+
if (module.load) {
|
|
931
|
+
const { started } = this;
|
|
932
|
+
|
|
933
|
+
/** @type {import('types/page').LoadInput | import('types/page').ErrorLoadInput} */
|
|
934
|
+
const load_input = {
|
|
935
|
+
params: uses_params,
|
|
936
|
+
get url() {
|
|
937
|
+
node.uses.url = true;
|
|
938
|
+
return url;
|
|
939
|
+
},
|
|
940
|
+
get session() {
|
|
941
|
+
node.uses.session = true;
|
|
942
|
+
return session;
|
|
943
|
+
},
|
|
944
|
+
get stuff() {
|
|
945
|
+
node.uses.stuff = true;
|
|
946
|
+
return { ...stuff };
|
|
947
|
+
},
|
|
948
|
+
fetch(resource, info) {
|
|
949
|
+
const requested = typeof resource === 'string' ? resource : resource.url;
|
|
950
|
+
const { href } = new URL(requested, url);
|
|
951
|
+
node.uses.dependencies.push(href);
|
|
952
|
+
|
|
953
|
+
return started ? fetch(resource, info) : initial_fetch(resource, info);
|
|
954
|
+
}
|
|
955
|
+
};
|
|
401
956
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
957
|
+
if (import.meta.env.DEV) {
|
|
958
|
+
// TODO remove this for 1.0
|
|
959
|
+
Object.defineProperty(load_input, 'page', {
|
|
960
|
+
get: () => {
|
|
961
|
+
throw new Error('`page` in `load` functions has been replaced by `url` and `params`');
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
}
|
|
409
965
|
|
|
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
|
-
};
|
|
966
|
+
if (error) {
|
|
967
|
+
/** @type {import('types/page').ErrorLoadInput} */ (load_input).status = status;
|
|
968
|
+
/** @type {import('types/page').ErrorLoadInput} */ (load_input).error = error;
|
|
969
|
+
}
|
|
432
970
|
|
|
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
|
-
}
|
|
971
|
+
const loaded = await module.load.call(null, load_input);
|
|
443
972
|
|
|
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
|
-
}
|
|
973
|
+
// if the page component returns nothing from load, fall through
|
|
974
|
+
if (!loaded) return;
|
|
468
975
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
error.status = loaded.error.status;
|
|
473
|
-
throw error;
|
|
474
|
-
}
|
|
976
|
+
node.loaded = normalize(loaded);
|
|
977
|
+
if (node.loaded.stuff) node.stuff = node.loaded.stuff;
|
|
978
|
+
}
|
|
475
979
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
980
|
+
return node;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* @param {import('./types').NavigationCandidate} selected
|
|
985
|
+
* @param {boolean} no_cache
|
|
986
|
+
* @returns {Promise<import('./types').NavigationResult | undefined>} undefined if fallthrough
|
|
987
|
+
*/
|
|
988
|
+
async _load({ route, info: { url, path } }, no_cache) {
|
|
989
|
+
const key = url.pathname + url.search;
|
|
990
|
+
|
|
991
|
+
if (!no_cache) {
|
|
992
|
+
const cached = this.cache.get(key);
|
|
993
|
+
if (cached) return cached;
|
|
994
|
+
}
|
|
480
995
|
|
|
481
|
-
|
|
482
|
-
|
|
996
|
+
const [pattern, a, b, get_params] = route;
|
|
997
|
+
const params = get_params
|
|
998
|
+
? // the pattern is for the route which we've already matched to this path
|
|
999
|
+
get_params(/** @type {RegExpExecArray} */ (pattern.exec(path)))
|
|
1000
|
+
: {};
|
|
483
1001
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
1002
|
+
const changed = this.current.url && {
|
|
1003
|
+
url: key !== this.current.url.pathname + this.current.url.search,
|
|
1004
|
+
params: Object.keys(params).filter((key) => this.current.params[key] !== params[key]),
|
|
1005
|
+
session: this.session_id !== this.current.session_id
|
|
1006
|
+
};
|
|
489
1007
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
this.caches.set(component, new Map());
|
|
493
|
-
}
|
|
1008
|
+
/** @type {Array<import('./types').BranchNode | undefined>} */
|
|
1009
|
+
let branch = [];
|
|
494
1010
|
|
|
495
|
-
|
|
496
|
-
|
|
1011
|
+
/** @type {Record<string, any>} */
|
|
1012
|
+
let stuff = {};
|
|
1013
|
+
let stuff_changed = false;
|
|
497
1014
|
|
|
498
|
-
|
|
1015
|
+
/** @type {number | undefined} */
|
|
1016
|
+
let status = 200;
|
|
499
1017
|
|
|
500
|
-
|
|
1018
|
+
/** @type {Error | undefined} */
|
|
1019
|
+
let error;
|
|
501
1020
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}, loaded.maxage * 1000);
|
|
1021
|
+
// preload modules
|
|
1022
|
+
a.forEach((loader) => loader());
|
|
505
1023
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}
|
|
1024
|
+
load: for (let i = 0; i < a.length; i += 1) {
|
|
1025
|
+
/** @type {import('./types').BranchNode | undefined} */
|
|
1026
|
+
let node;
|
|
510
1027
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
};
|
|
1028
|
+
try {
|
|
1029
|
+
if (!a[i]) continue;
|
|
514
1030
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
});
|
|
1031
|
+
const module = await a[i]();
|
|
1032
|
+
const previous = this.current.branch[i];
|
|
518
1033
|
|
|
519
|
-
|
|
1034
|
+
const changed_since_last_render =
|
|
1035
|
+
!previous ||
|
|
1036
|
+
module !== previous.module ||
|
|
1037
|
+
(changed.url && previous.uses.url) ||
|
|
1038
|
+
changed.params.some((param) => previous.uses.params.has(param)) ||
|
|
1039
|
+
(changed.session && previous.uses.session) ||
|
|
1040
|
+
previous.uses.dependencies.some((dep) => this.invalid.has(dep)) ||
|
|
1041
|
+
(stuff_changed && previous.uses.stuff);
|
|
1042
|
+
|
|
1043
|
+
if (changed_since_last_render) {
|
|
1044
|
+
node = await this._load_node({
|
|
1045
|
+
module,
|
|
1046
|
+
url,
|
|
1047
|
+
params,
|
|
1048
|
+
stuff
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
const is_leaf = i === a.length - 1;
|
|
1052
|
+
|
|
1053
|
+
if (node && node.loaded) {
|
|
1054
|
+
if (node.loaded.error) {
|
|
1055
|
+
status = node.loaded.status;
|
|
1056
|
+
error = node.loaded.error;
|
|
520
1057
|
}
|
|
521
1058
|
|
|
522
|
-
|
|
523
|
-
|
|
1059
|
+
if (node.loaded.redirect) {
|
|
1060
|
+
return {
|
|
1061
|
+
redirect: node.loaded.redirect,
|
|
1062
|
+
props: {},
|
|
1063
|
+
state: this.current
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
524
1066
|
|
|
525
|
-
|
|
1067
|
+
if (node.loaded.stuff) {
|
|
1068
|
+
stuff_changed = true;
|
|
1069
|
+
}
|
|
1070
|
+
} else if (is_leaf && module.load) {
|
|
1071
|
+
// if the leaf node has a `load` function
|
|
1072
|
+
// that returns nothing, fall through
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
526
1075
|
} else {
|
|
527
|
-
|
|
1076
|
+
node = previous;
|
|
528
1077
|
}
|
|
1078
|
+
} catch (e) {
|
|
1079
|
+
status = 500;
|
|
1080
|
+
error = coalesce_to_error(e);
|
|
529
1081
|
}
|
|
530
1082
|
|
|
531
|
-
|
|
1083
|
+
if (error) {
|
|
1084
|
+
while (i--) {
|
|
1085
|
+
if (b[i]) {
|
|
1086
|
+
let error_loaded;
|
|
1087
|
+
|
|
1088
|
+
/** @type {import('./types').BranchNode | undefined} */
|
|
1089
|
+
let node_loaded;
|
|
1090
|
+
let j = i;
|
|
1091
|
+
while (!(node_loaded = branch[j])) {
|
|
1092
|
+
j -= 1;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
try {
|
|
1096
|
+
error_loaded = await this._load_node({
|
|
1097
|
+
status,
|
|
1098
|
+
error,
|
|
1099
|
+
module: await b[i](),
|
|
1100
|
+
url,
|
|
1101
|
+
params,
|
|
1102
|
+
stuff: node_loaded.stuff
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
if (error_loaded && error_loaded.loaded && error_loaded.loaded.error) {
|
|
1106
|
+
continue;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
branch = branch.slice(0, j + 1).concat(error_loaded);
|
|
1110
|
+
break load;
|
|
1111
|
+
} catch (e) {
|
|
1112
|
+
continue;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
532
1116
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
1117
|
+
return await this._load_error({
|
|
1118
|
+
status,
|
|
1119
|
+
error,
|
|
1120
|
+
url
|
|
1121
|
+
});
|
|
1122
|
+
} else {
|
|
1123
|
+
if (node && node.loaded && node.loaded.stuff) {
|
|
1124
|
+
stuff = {
|
|
1125
|
+
...stuff,
|
|
1126
|
+
...node.loaded.stuff
|
|
1127
|
+
};
|
|
536
1128
|
}
|
|
537
|
-
});
|
|
538
1129
|
|
|
539
|
-
|
|
540
|
-
props.page = page;
|
|
1130
|
+
branch.push(node);
|
|
541
1131
|
}
|
|
542
|
-
} catch (error) {
|
|
543
|
-
props.error = error;
|
|
544
|
-
props.status = 500;
|
|
545
|
-
state.nodes = [];
|
|
546
1132
|
}
|
|
547
1133
|
|
|
548
|
-
return {
|
|
1134
|
+
return await this._get_navigation_result_from_branch({ url, params, branch, status, error });
|
|
549
1135
|
}
|
|
550
1136
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
1137
|
+
/**
|
|
1138
|
+
* @param {{
|
|
1139
|
+
* status: number;
|
|
1140
|
+
* error: Error;
|
|
1141
|
+
* url: URL;
|
|
1142
|
+
* }} opts
|
|
1143
|
+
*/
|
|
1144
|
+
async _load_error({ status, error, url }) {
|
|
1145
|
+
/** @type {Record<string, string>} */
|
|
1146
|
+
const params = {}; // error page does not have params
|
|
1147
|
+
|
|
1148
|
+
const node = await this._load_node({
|
|
1149
|
+
module: await this.fallback[0],
|
|
1150
|
+
url,
|
|
1151
|
+
params,
|
|
1152
|
+
stuff: {}
|
|
1153
|
+
});
|
|
558
1154
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
1155
|
+
const branch = [
|
|
1156
|
+
node,
|
|
1157
|
+
await this._load_node({
|
|
1158
|
+
status,
|
|
1159
|
+
error,
|
|
1160
|
+
module: await this.fallback[1],
|
|
1161
|
+
url,
|
|
1162
|
+
params,
|
|
1163
|
+
stuff: (node && node.loaded && node.loaded.stuff) || {}
|
|
1164
|
+
})
|
|
1165
|
+
];
|
|
1166
|
+
|
|
1167
|
+
return await this._get_navigation_result_from_branch({ url, params, branch, status, error });
|
|
563
1168
|
}
|
|
564
1169
|
}
|
|
565
1170
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
1171
|
+
// @ts-expect-error - doesn't exist yet. generated by Rollup
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* @param {{
|
|
1175
|
+
* paths: {
|
|
1176
|
+
* assets: string;
|
|
1177
|
+
* base: string;
|
|
1178
|
+
* },
|
|
1179
|
+
* target: Node;
|
|
1180
|
+
* session: any;
|
|
1181
|
+
* route: boolean;
|
|
1182
|
+
* spa: boolean;
|
|
1183
|
+
* trailing_slash: import('types/internal').TrailingSlash;
|
|
1184
|
+
* hydrate: {
|
|
1185
|
+
* status: number;
|
|
1186
|
+
* error: Error;
|
|
1187
|
+
* nodes: Array<Promise<import('types/internal').CSRComponent>>;
|
|
1188
|
+
* url: URL;
|
|
1189
|
+
* params: Record<string, string>;
|
|
1190
|
+
* };
|
|
1191
|
+
* }} opts
|
|
1192
|
+
*/
|
|
1193
|
+
async function start({ paths, target, session, route, spa, trailing_slash, hydrate }) {
|
|
1194
|
+
if (import.meta.env.DEV && !target) {
|
|
1195
|
+
throw new Error('Missing target element. See https://kit.svelte.dev/docs#configuration-target');
|
|
1196
|
+
}
|
|
573
1197
|
|
|
574
1198
|
const renderer = new Renderer({
|
|
575
1199
|
Root,
|
|
576
|
-
|
|
1200
|
+
fallback,
|
|
577
1201
|
target,
|
|
578
|
-
preloaded,
|
|
579
|
-
error,
|
|
580
|
-
status,
|
|
581
1202
|
session
|
|
582
1203
|
});
|
|
583
1204
|
|
|
1205
|
+
const router = route
|
|
1206
|
+
? new Router({
|
|
1207
|
+
base: paths.base,
|
|
1208
|
+
routes,
|
|
1209
|
+
trailing_slash,
|
|
1210
|
+
renderer
|
|
1211
|
+
})
|
|
1212
|
+
: null;
|
|
1213
|
+
|
|
584
1214
|
init({ router, renderer });
|
|
585
1215
|
set_paths(paths);
|
|
586
1216
|
|
|
587
|
-
await
|
|
1217
|
+
if (hydrate) await renderer.start(hydrate);
|
|
1218
|
+
if (router) {
|
|
1219
|
+
if (spa) router.goto(location.href, { replaceState: true }, []);
|
|
1220
|
+
router.init_listeners();
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
dispatchEvent(new CustomEvent('sveltekit:start'));
|
|
588
1224
|
}
|
|
589
1225
|
|
|
590
1226
|
export { start };
|
|
591
|
-
//# sourceMappingURL=start.js.map
|