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