@sveltejs/kit 1.0.0-next.49 → 1.0.0-next.490
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/package.json +93 -66
- package/scripts/special-types/$env+dynamic+private.md +10 -0
- package/scripts/special-types/$env+dynamic+public.md +8 -0
- package/scripts/special-types/$env+static+private.md +19 -0
- package/scripts/special-types/$env+static+public.md +7 -0
- package/scripts/special-types/$lib.md +5 -0
- package/src/cli.js +112 -0
- package/src/constants.js +7 -0
- package/src/core/adapt/builder.js +206 -0
- package/src/core/adapt/index.js +31 -0
- package/src/core/config/default-error.html +56 -0
- package/src/core/config/index.js +110 -0
- package/src/core/config/options.js +504 -0
- package/src/core/config/types.d.ts +1 -0
- package/src/core/env.js +121 -0
- package/src/core/generate_manifest/index.js +92 -0
- package/src/core/prerender/crawl.js +198 -0
- package/src/core/prerender/entities.js +2252 -0
- package/src/core/prerender/prerender.js +431 -0
- package/src/core/prerender/queue.js +80 -0
- package/src/core/sync/create_manifest_data/index.js +467 -0
- package/src/core/sync/create_manifest_data/types.d.ts +37 -0
- package/src/core/sync/sync.js +59 -0
- package/src/core/sync/utils.js +33 -0
- package/src/core/sync/write_ambient.js +53 -0
- package/src/core/sync/write_client_manifest.js +106 -0
- package/src/core/sync/write_matchers.js +25 -0
- package/src/core/sync/write_root.js +91 -0
- package/src/core/sync/write_tsconfig.js +195 -0
- package/src/core/sync/write_types/index.js +678 -0
- package/src/core/utils.js +70 -0
- package/src/exports/hooks/index.js +1 -0
- package/src/exports/hooks/sequence.js +44 -0
- package/src/exports/index.js +45 -0
- package/src/exports/node/index.js +168 -0
- package/src/exports/node/polyfills.js +41 -0
- package/src/exports/vite/build/build_server.js +372 -0
- package/src/exports/vite/build/build_service_worker.js +90 -0
- package/src/exports/vite/build/utils.js +162 -0
- package/src/exports/vite/dev/index.js +576 -0
- package/src/exports/vite/graph_analysis/index.js +277 -0
- package/src/exports/vite/graph_analysis/types.d.ts +5 -0
- package/src/exports/vite/graph_analysis/utils.js +30 -0
- package/src/exports/vite/index.js +598 -0
- package/src/exports/vite/preview/index.js +189 -0
- package/src/exports/vite/types.d.ts +3 -0
- package/src/exports/vite/utils.js +157 -0
- package/src/runtime/app/env.js +1 -0
- package/src/runtime/app/environment.js +11 -0
- package/src/runtime/app/forms.js +108 -0
- package/src/runtime/app/navigation.js +23 -0
- package/src/runtime/app/paths.js +1 -0
- package/src/runtime/app/stores.js +102 -0
- package/src/runtime/client/ambient.d.ts +26 -0
- package/src/runtime/client/client.js +1581 -0
- package/src/runtime/client/fetcher.js +107 -0
- package/src/runtime/client/parse.js +60 -0
- package/src/runtime/client/singletons.js +21 -0
- package/src/runtime/client/start.js +37 -0
- package/src/runtime/client/types.d.ts +85 -0
- package/src/runtime/client/utils.js +159 -0
- package/src/runtime/components/error.svelte +16 -0
- package/{assets → src/runtime}/components/layout.svelte +0 -0
- package/src/runtime/control.js +98 -0
- package/src/runtime/env/dynamic/private.js +1 -0
- package/src/runtime/env/dynamic/public.js +1 -0
- package/src/runtime/env-private.js +6 -0
- package/src/runtime/env-public.js +6 -0
- package/src/runtime/env.js +6 -0
- package/src/runtime/hash.js +16 -0
- package/src/runtime/paths.js +11 -0
- package/src/runtime/server/cookie.js +115 -0
- package/src/runtime/server/data/index.js +136 -0
- package/src/runtime/server/endpoint.js +83 -0
- package/src/runtime/server/index.js +337 -0
- package/src/runtime/server/page/actions.js +243 -0
- package/src/runtime/server/page/crypto.js +239 -0
- package/src/runtime/server/page/csp.js +249 -0
- package/src/runtime/server/page/fetch.js +288 -0
- package/src/runtime/server/page/index.js +304 -0
- package/src/runtime/server/page/load_data.js +124 -0
- package/src/runtime/server/page/render.js +342 -0
- package/src/runtime/server/page/respond_with_error.js +104 -0
- package/src/runtime/server/page/serialize_data.js +87 -0
- package/src/runtime/server/page/types.d.ts +41 -0
- package/src/runtime/server/utils.js +179 -0
- package/src/utils/array.js +9 -0
- package/src/utils/error.js +22 -0
- package/src/utils/escape.js +46 -0
- package/src/utils/filesystem.js +137 -0
- package/src/utils/functions.js +16 -0
- package/src/utils/http.js +55 -0
- package/src/utils/misc.js +1 -0
- package/src/utils/routing.js +117 -0
- package/src/utils/unit_test.js +11 -0
- package/src/utils/url.js +142 -0
- package/svelte-kit.js +1 -1
- package/types/ambient.d.ts +426 -0
- package/types/index.d.ts +428 -0
- package/types/internal.d.ts +378 -0
- package/types/private.d.ts +209 -0
- package/CHANGELOG.md +0 -476
- package/assets/components/error.svelte +0 -13
- package/assets/runtime/app/env.js +0 -5
- package/assets/runtime/app/navigation.js +0 -44
- package/assets/runtime/app/paths.js +0 -1
- package/assets/runtime/app/stores.js +0 -93
- package/assets/runtime/chunks/utils.js +0 -22
- package/assets/runtime/internal/singletons.js +0 -23
- package/assets/runtime/internal/start.js +0 -773
- package/assets/runtime/paths.js +0 -12
- package/dist/chunks/index.js +0 -3517
- package/dist/chunks/index2.js +0 -587
- package/dist/chunks/index3.js +0 -246
- package/dist/chunks/index4.js +0 -530
- package/dist/chunks/index5.js +0 -763
- package/dist/chunks/index6.js +0 -322
- package/dist/chunks/standard.js +0 -99
- package/dist/chunks/utils.js +0 -83
- package/dist/cli.js +0 -553
- package/dist/ssr.js +0 -2584
- package/types.d.ts +0 -32
|
@@ -0,0 +1,1581 @@
|
|
|
1
|
+
import { onMount, tick } from 'svelte';
|
|
2
|
+
import { normalize_error } from '../../utils/error.js';
|
|
3
|
+
import { make_trackable, decode_params, normalize_path } from '../../utils/url.js';
|
|
4
|
+
import { find_anchor, get_base_uri, scroll_state } from './utils.js';
|
|
5
|
+
import { lock_fetch, unlock_fetch, initial_fetch, subsequent_fetch } from './fetcher.js';
|
|
6
|
+
import { parse } from './parse.js';
|
|
7
|
+
|
|
8
|
+
import Root from '__GENERATED__/root.svelte';
|
|
9
|
+
import { nodes, server_loads, dictionary, matchers, hooks } from '__GENERATED__/client-manifest.js';
|
|
10
|
+
import { HttpError, Redirect } from '../control.js';
|
|
11
|
+
import { stores } from './singletons.js';
|
|
12
|
+
import { DATA_SUFFIX } from '../../constants.js';
|
|
13
|
+
|
|
14
|
+
const SCROLL_KEY = 'sveltekit:scroll';
|
|
15
|
+
const INDEX_KEY = 'sveltekit:index';
|
|
16
|
+
|
|
17
|
+
const routes = parse(nodes, server_loads, dictionary, matchers);
|
|
18
|
+
|
|
19
|
+
const default_layout_loader = nodes[0];
|
|
20
|
+
const default_error_loader = nodes[1];
|
|
21
|
+
|
|
22
|
+
// we import the root layout/error nodes eagerly, so that
|
|
23
|
+
// connectivity errors after initialisation don't nuke the app
|
|
24
|
+
default_layout_loader();
|
|
25
|
+
default_error_loader();
|
|
26
|
+
|
|
27
|
+
// We track the scroll position associated with each history entry in sessionStorage,
|
|
28
|
+
// rather than on history.state itself, because when navigation is driven by
|
|
29
|
+
// popstate it's too late to update the scroll position associated with the
|
|
30
|
+
// state we're navigating from
|
|
31
|
+
|
|
32
|
+
/** @typedef {{ x: number, y: number }} ScrollPosition */
|
|
33
|
+
/** @type {Record<number, ScrollPosition>} */
|
|
34
|
+
let scroll_positions = {};
|
|
35
|
+
try {
|
|
36
|
+
scroll_positions = JSON.parse(sessionStorage[SCROLL_KEY]);
|
|
37
|
+
} catch {
|
|
38
|
+
// do nothing
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** @param {number} index */
|
|
42
|
+
function update_scroll_positions(index) {
|
|
43
|
+
scroll_positions[index] = scroll_state();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// TODO remove for 1.0
|
|
47
|
+
/** @type {Record<string, true>} */
|
|
48
|
+
let warned_about_attributes = {};
|
|
49
|
+
|
|
50
|
+
function check_for_removed_attributes() {
|
|
51
|
+
const attrs = ['prefetch', 'noscroll', 'reload'];
|
|
52
|
+
for (const attr of attrs) {
|
|
53
|
+
if (document.querySelector(`[sveltekit\\:${attr}]`)) {
|
|
54
|
+
if (!warned_about_attributes[attr]) {
|
|
55
|
+
warned_about_attributes[attr] = true;
|
|
56
|
+
console.error(
|
|
57
|
+
`The sveltekit:${attr} attribute has been replaced with data-sveltekit-${attr}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {{
|
|
66
|
+
* target: Element;
|
|
67
|
+
* base: string;
|
|
68
|
+
* trailing_slash: import('types').TrailingSlash;
|
|
69
|
+
* }} opts
|
|
70
|
+
* @returns {import('./types').Client}
|
|
71
|
+
*/
|
|
72
|
+
export function create_client({ target, base, trailing_slash }) {
|
|
73
|
+
/** @type {Array<((url: URL) => boolean)>} */
|
|
74
|
+
const invalidated = [];
|
|
75
|
+
|
|
76
|
+
/** @type {{id: string | null, promise: Promise<import('./types').NavigationResult | undefined> | null}} */
|
|
77
|
+
const load_cache = {
|
|
78
|
+
id: null,
|
|
79
|
+
promise: null
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const callbacks = {
|
|
83
|
+
/** @type {Array<(navigation: import('types').Navigation & { cancel: () => void }) => void>} */
|
|
84
|
+
before_navigate: [],
|
|
85
|
+
|
|
86
|
+
/** @type {Array<(navigation: import('types').Navigation) => void>} */
|
|
87
|
+
after_navigate: []
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** @type {import('./types').NavigationState} */
|
|
91
|
+
let current = {
|
|
92
|
+
branch: [],
|
|
93
|
+
error: null,
|
|
94
|
+
session_id: 0,
|
|
95
|
+
// @ts-ignore - we need the initial value to be null
|
|
96
|
+
url: null
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
let started = false;
|
|
100
|
+
let autoscroll = true;
|
|
101
|
+
let updating = false;
|
|
102
|
+
let session_id = 1;
|
|
103
|
+
|
|
104
|
+
/** @type {Promise<void> | null} */
|
|
105
|
+
let invalidating = null;
|
|
106
|
+
let force_invalidation = false;
|
|
107
|
+
|
|
108
|
+
/** @type {import('svelte').SvelteComponent} */
|
|
109
|
+
let root;
|
|
110
|
+
|
|
111
|
+
// keeping track of the history index in order to prevent popstate navigation events if needed
|
|
112
|
+
let current_history_index = history.state?.[INDEX_KEY];
|
|
113
|
+
|
|
114
|
+
if (!current_history_index) {
|
|
115
|
+
// we use Date.now() as an offset so that cross-document navigations
|
|
116
|
+
// within the app don't result in data loss
|
|
117
|
+
current_history_index = Date.now();
|
|
118
|
+
|
|
119
|
+
// create initial history entry, so we can return here
|
|
120
|
+
history.replaceState(
|
|
121
|
+
{ ...history.state, [INDEX_KEY]: current_history_index },
|
|
122
|
+
'',
|
|
123
|
+
location.href
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// if we reload the page, or Cmd-Shift-T back to it,
|
|
128
|
+
// recover scroll position
|
|
129
|
+
const scroll = scroll_positions[current_history_index];
|
|
130
|
+
if (scroll) {
|
|
131
|
+
history.scrollRestoration = 'manual';
|
|
132
|
+
scrollTo(scroll.x, scroll.y);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let hash_navigating = false;
|
|
136
|
+
|
|
137
|
+
/** @type {import('types').Page} */
|
|
138
|
+
let page;
|
|
139
|
+
|
|
140
|
+
/** @type {{}} */
|
|
141
|
+
let token;
|
|
142
|
+
|
|
143
|
+
function invalidate() {
|
|
144
|
+
if (!invalidating) {
|
|
145
|
+
const url = new URL(location.href);
|
|
146
|
+
|
|
147
|
+
invalidating = Promise.resolve().then(async () => {
|
|
148
|
+
const intent = get_navigation_intent(url, true);
|
|
149
|
+
await update(intent, url, []);
|
|
150
|
+
|
|
151
|
+
invalidating = null;
|
|
152
|
+
force_invalidation = false;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return invalidating;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @param {string | URL} url
|
|
161
|
+
* @param {{ noscroll?: boolean; replaceState?: boolean; keepfocus?: boolean; state?: any }} opts
|
|
162
|
+
* @param {string[]} redirect_chain
|
|
163
|
+
*/
|
|
164
|
+
async function goto(
|
|
165
|
+
url,
|
|
166
|
+
{ noscroll = false, replaceState = false, keepfocus = false, state = {} },
|
|
167
|
+
redirect_chain
|
|
168
|
+
) {
|
|
169
|
+
if (typeof url === 'string') {
|
|
170
|
+
url = new URL(url, get_base_uri(document));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return navigate({
|
|
174
|
+
url,
|
|
175
|
+
scroll: noscroll ? scroll_state() : null,
|
|
176
|
+
keepfocus,
|
|
177
|
+
redirect_chain,
|
|
178
|
+
details: {
|
|
179
|
+
state,
|
|
180
|
+
replaceState
|
|
181
|
+
},
|
|
182
|
+
accepted: () => {},
|
|
183
|
+
blocked: () => {},
|
|
184
|
+
type: 'goto'
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** @param {URL} url */
|
|
189
|
+
async function prefetch(url) {
|
|
190
|
+
const intent = get_navigation_intent(url, false);
|
|
191
|
+
|
|
192
|
+
if (!intent) {
|
|
193
|
+
throw new Error('Attempted to prefetch a URL that does not belong to this app');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
load_cache.promise = load_route(intent);
|
|
197
|
+
load_cache.id = intent.id;
|
|
198
|
+
|
|
199
|
+
return load_cache.promise;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Returns `true` if update completes, `false` if it is aborted
|
|
204
|
+
* @param {import('./types').NavigationIntent | undefined} intent
|
|
205
|
+
* @param {URL} url
|
|
206
|
+
* @param {string[]} redirect_chain
|
|
207
|
+
* @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean, details: { replaceState: boolean, state: any } | null}} [opts]
|
|
208
|
+
* @param {() => void} [callback]
|
|
209
|
+
*/
|
|
210
|
+
async function update(intent, url, redirect_chain, opts, callback) {
|
|
211
|
+
const current_token = (token = {});
|
|
212
|
+
let navigation_result = intent && (await load_route(intent));
|
|
213
|
+
|
|
214
|
+
if (
|
|
215
|
+
!navigation_result &&
|
|
216
|
+
url.origin === location.origin &&
|
|
217
|
+
url.pathname === location.pathname
|
|
218
|
+
) {
|
|
219
|
+
// this could happen in SPA fallback mode if the user navigated to
|
|
220
|
+
// `/non-existent-page`. if we fall back to reloading the page, it
|
|
221
|
+
// will create an infinite loop. so whereas we normally handle
|
|
222
|
+
// unknown routes by going to the server, in this special case
|
|
223
|
+
// we render a client-side error page instead
|
|
224
|
+
navigation_result = await load_root_error_page({
|
|
225
|
+
status: 404,
|
|
226
|
+
error: new Error(`Not found: ${url.pathname}`),
|
|
227
|
+
url,
|
|
228
|
+
routeId: null
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!navigation_result) {
|
|
233
|
+
await native_navigation(url);
|
|
234
|
+
return false; // unnecessary, but TypeScript prefers it this way
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// if this is an internal navigation intent, use the normalized
|
|
238
|
+
// URL for the rest of the function
|
|
239
|
+
url = intent?.url || url;
|
|
240
|
+
|
|
241
|
+
// abort if user navigated during update
|
|
242
|
+
if (token !== current_token) return false;
|
|
243
|
+
|
|
244
|
+
invalidated.length = 0;
|
|
245
|
+
|
|
246
|
+
if (navigation_result.type === 'redirect') {
|
|
247
|
+
if (redirect_chain.length > 10 || redirect_chain.includes(url.pathname)) {
|
|
248
|
+
navigation_result = await load_root_error_page({
|
|
249
|
+
status: 500,
|
|
250
|
+
error: new Error('Redirect loop'),
|
|
251
|
+
url,
|
|
252
|
+
routeId: null
|
|
253
|
+
});
|
|
254
|
+
} else {
|
|
255
|
+
goto(new URL(navigation_result.location, url).href, {}, [...redirect_chain, url.pathname]);
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
} else if (navigation_result.props?.page?.status >= 400) {
|
|
259
|
+
const updated = await stores.updated.check();
|
|
260
|
+
if (updated) {
|
|
261
|
+
await native_navigation(url);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
updating = true;
|
|
266
|
+
|
|
267
|
+
if (opts && opts.details) {
|
|
268
|
+
const { details } = opts;
|
|
269
|
+
const change = details.replaceState ? 0 : 1;
|
|
270
|
+
details.state[INDEX_KEY] = current_history_index += change;
|
|
271
|
+
history[details.replaceState ? 'replaceState' : 'pushState'](details.state, '', url);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (started) {
|
|
275
|
+
current = navigation_result.state;
|
|
276
|
+
|
|
277
|
+
if (navigation_result.props.page) {
|
|
278
|
+
navigation_result.props.page.url = url;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const post_update = pre_update();
|
|
282
|
+
root.$set(navigation_result.props);
|
|
283
|
+
post_update();
|
|
284
|
+
} else {
|
|
285
|
+
initialize(navigation_result);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// opts must be passed if we're navigating
|
|
289
|
+
if (opts) {
|
|
290
|
+
const { scroll, keepfocus } = opts;
|
|
291
|
+
|
|
292
|
+
if (!keepfocus) {
|
|
293
|
+
// Reset page selection and focus
|
|
294
|
+
// We try to mimic browsers' behaviour as closely as possible by targeting the
|
|
295
|
+
// first scrollable region, but unfortunately it's not a perfect match — e.g.
|
|
296
|
+
// shift-tabbing won't immediately cycle up from the end of the page on Chromium
|
|
297
|
+
// See https://html.spec.whatwg.org/multipage/interaction.html#get-the-focusable-area
|
|
298
|
+
const root = document.body;
|
|
299
|
+
const tabindex = root.getAttribute('tabindex');
|
|
300
|
+
|
|
301
|
+
root.tabIndex = -1;
|
|
302
|
+
root.focus({ preventScroll: true });
|
|
303
|
+
|
|
304
|
+
setTimeout(() => {
|
|
305
|
+
getSelection()?.removeAllRanges();
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// restore `tabindex` as to prevent `root` from stealing input from elements
|
|
309
|
+
if (tabindex !== null) {
|
|
310
|
+
root.setAttribute('tabindex', tabindex);
|
|
311
|
+
} else {
|
|
312
|
+
root.removeAttribute('tabindex');
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// need to render the DOM before we can scroll to the rendered elements
|
|
317
|
+
await tick();
|
|
318
|
+
|
|
319
|
+
if (autoscroll) {
|
|
320
|
+
const deep_linked = url.hash && document.getElementById(url.hash.slice(1));
|
|
321
|
+
if (scroll) {
|
|
322
|
+
scrollTo(scroll.x, scroll.y);
|
|
323
|
+
} else if (deep_linked) {
|
|
324
|
+
// Here we use `scrollIntoView` on the element instead of `scrollTo`
|
|
325
|
+
// because it natively supports the `scroll-margin` and `scroll-behavior`
|
|
326
|
+
// CSS properties.
|
|
327
|
+
deep_linked.scrollIntoView();
|
|
328
|
+
} else {
|
|
329
|
+
scrollTo(0, 0);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
// in this case we're simply invalidating
|
|
334
|
+
await tick();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
load_cache.promise = null;
|
|
338
|
+
load_cache.id = null;
|
|
339
|
+
autoscroll = true;
|
|
340
|
+
|
|
341
|
+
if (navigation_result.props.page) {
|
|
342
|
+
page = navigation_result.props.page;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (callback) callback();
|
|
346
|
+
|
|
347
|
+
updating = false;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** @param {import('./types').NavigationFinished} result */
|
|
351
|
+
function initialize(result) {
|
|
352
|
+
current = result.state;
|
|
353
|
+
|
|
354
|
+
const style = document.querySelector('style[data-sveltekit]');
|
|
355
|
+
if (style) style.remove();
|
|
356
|
+
|
|
357
|
+
page = result.props.page;
|
|
358
|
+
|
|
359
|
+
const post_update = pre_update();
|
|
360
|
+
root = new Root({
|
|
361
|
+
target,
|
|
362
|
+
props: { ...result.props, stores },
|
|
363
|
+
hydrate: true
|
|
364
|
+
});
|
|
365
|
+
post_update();
|
|
366
|
+
|
|
367
|
+
/** @type {import('types').Navigation} */
|
|
368
|
+
const navigation = {
|
|
369
|
+
from: null,
|
|
370
|
+
to: add_url_properties('to', {
|
|
371
|
+
params: current.params,
|
|
372
|
+
routeId: current.route?.id ?? null,
|
|
373
|
+
url: new URL(location.href)
|
|
374
|
+
}),
|
|
375
|
+
type: 'load'
|
|
376
|
+
};
|
|
377
|
+
callbacks.after_navigate.forEach((fn) => fn(navigation));
|
|
378
|
+
|
|
379
|
+
started = true;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
*
|
|
384
|
+
* @param {{
|
|
385
|
+
* url: URL;
|
|
386
|
+
* params: Record<string, string>;
|
|
387
|
+
* branch: Array<import('./types').BranchNode | undefined>;
|
|
388
|
+
* status: number;
|
|
389
|
+
* error: App.PageError | null;
|
|
390
|
+
* route: import('types').CSRRoute | null;
|
|
391
|
+
* form?: Record<string, any> | null;
|
|
392
|
+
* }} opts
|
|
393
|
+
*/
|
|
394
|
+
async function get_navigation_result_from_branch({
|
|
395
|
+
url,
|
|
396
|
+
params,
|
|
397
|
+
branch,
|
|
398
|
+
status,
|
|
399
|
+
error,
|
|
400
|
+
route,
|
|
401
|
+
form
|
|
402
|
+
}) {
|
|
403
|
+
const filtered = /** @type {import('./types').BranchNode[] } */ (branch.filter(Boolean));
|
|
404
|
+
|
|
405
|
+
/** @type {import('./types').NavigationFinished} */
|
|
406
|
+
const result = {
|
|
407
|
+
type: 'loaded',
|
|
408
|
+
state: {
|
|
409
|
+
url,
|
|
410
|
+
params,
|
|
411
|
+
branch,
|
|
412
|
+
error,
|
|
413
|
+
route,
|
|
414
|
+
session_id
|
|
415
|
+
},
|
|
416
|
+
props: {
|
|
417
|
+
components: filtered.map((branch_node) => branch_node.node.component)
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
if (form !== undefined) {
|
|
422
|
+
result.props.form = form;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
let data = {};
|
|
426
|
+
let data_changed = !page;
|
|
427
|
+
for (let i = 0; i < filtered.length; i += 1) {
|
|
428
|
+
const node = filtered[i];
|
|
429
|
+
data = { ...data, ...node.data };
|
|
430
|
+
|
|
431
|
+
// Only set props if the node actually updated. This prevents needless rerenders.
|
|
432
|
+
if (data_changed || !current.branch.some((previous) => previous === node)) {
|
|
433
|
+
result.props[`data_${i}`] = data;
|
|
434
|
+
data_changed = data_changed || Object.keys(node.data ?? {}).length > 0;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (!data_changed) {
|
|
438
|
+
// If nothing was added, and the object entries are the same length, this means
|
|
439
|
+
// that nothing was removed either and therefore the data is the same as the previous one.
|
|
440
|
+
// This would be more readable with a separate boolean but that would cost us some bytes.
|
|
441
|
+
data_changed = Object.keys(page.data).length !== Object.keys(data).length;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const page_changed =
|
|
445
|
+
!current.url || url.href !== current.url.href || current.error !== error || data_changed;
|
|
446
|
+
|
|
447
|
+
if (page_changed) {
|
|
448
|
+
result.props.page = {
|
|
449
|
+
error,
|
|
450
|
+
params,
|
|
451
|
+
routeId: route && route.id,
|
|
452
|
+
status,
|
|
453
|
+
url,
|
|
454
|
+
// The whole page store is updated, but this way the object reference stays the same
|
|
455
|
+
data: data_changed ? data : page.data
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// TODO remove this for 1.0
|
|
459
|
+
/**
|
|
460
|
+
* @param {string} property
|
|
461
|
+
* @param {string} replacement
|
|
462
|
+
*/
|
|
463
|
+
const print_error = (property, replacement) => {
|
|
464
|
+
Object.defineProperty(result.props.page, property, {
|
|
465
|
+
get: () => {
|
|
466
|
+
throw new Error(`$page.${property} has been replaced by $page.url.${replacement}`);
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
print_error('origin', 'origin');
|
|
472
|
+
print_error('path', 'pathname');
|
|
473
|
+
print_error('query', 'searchParams');
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return result;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Call the load function of the given node, if it exists.
|
|
481
|
+
* If `server_data` is passed, this is treated as the initial run and the page endpoint is not requested.
|
|
482
|
+
*
|
|
483
|
+
* @param {{
|
|
484
|
+
* loader: import('types').CSRPageNodeLoader;
|
|
485
|
+
* parent: () => Promise<Record<string, any>>;
|
|
486
|
+
* url: URL;
|
|
487
|
+
* params: Record<string, string>;
|
|
488
|
+
* routeId: string | null;
|
|
489
|
+
* server_data_node: import('./types').DataNode | null;
|
|
490
|
+
* }} options
|
|
491
|
+
* @returns {Promise<import('./types').BranchNode>}
|
|
492
|
+
*/
|
|
493
|
+
async function load_node({ loader, parent, url, params, routeId, server_data_node }) {
|
|
494
|
+
/** @type {Record<string, any> | null} */
|
|
495
|
+
let data = null;
|
|
496
|
+
|
|
497
|
+
/** @type {import('types').Uses} */
|
|
498
|
+
const uses = {
|
|
499
|
+
dependencies: new Set(),
|
|
500
|
+
params: new Set(),
|
|
501
|
+
parent: false,
|
|
502
|
+
url: false
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
const node = await loader();
|
|
506
|
+
|
|
507
|
+
if (node.shared?.load) {
|
|
508
|
+
/** @param {string[]} deps */
|
|
509
|
+
function depends(...deps) {
|
|
510
|
+
for (const dep of deps) {
|
|
511
|
+
const { href } = new URL(dep, url);
|
|
512
|
+
uses.dependencies.add(href);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** @type {Record<string, string>} */
|
|
517
|
+
const uses_params = {};
|
|
518
|
+
for (const key in params) {
|
|
519
|
+
Object.defineProperty(uses_params, key, {
|
|
520
|
+
get() {
|
|
521
|
+
uses.params.add(key);
|
|
522
|
+
return params[key];
|
|
523
|
+
},
|
|
524
|
+
enumerable: true
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** @type {import('types').LoadEvent} */
|
|
529
|
+
const load_input = {
|
|
530
|
+
routeId,
|
|
531
|
+
params: uses_params,
|
|
532
|
+
data: server_data_node?.data ?? null,
|
|
533
|
+
url: make_trackable(url, () => {
|
|
534
|
+
uses.url = true;
|
|
535
|
+
}),
|
|
536
|
+
async fetch(resource, init) {
|
|
537
|
+
let requested;
|
|
538
|
+
|
|
539
|
+
if (typeof resource === 'string') {
|
|
540
|
+
requested = resource;
|
|
541
|
+
} else {
|
|
542
|
+
requested = resource.url;
|
|
543
|
+
|
|
544
|
+
// we're not allowed to modify the received `Request` object, so in order
|
|
545
|
+
// to fixup relative urls we create a new equivalent `init` object instead
|
|
546
|
+
init = {
|
|
547
|
+
// the request body must be consumed in memory until browsers
|
|
548
|
+
// implement streaming request bodies and/or the body getter
|
|
549
|
+
body:
|
|
550
|
+
resource.method === 'GET' || resource.method === 'HEAD'
|
|
551
|
+
? undefined
|
|
552
|
+
: await resource.blob(),
|
|
553
|
+
cache: resource.cache,
|
|
554
|
+
credentials: resource.credentials,
|
|
555
|
+
headers: resource.headers,
|
|
556
|
+
integrity: resource.integrity,
|
|
557
|
+
keepalive: resource.keepalive,
|
|
558
|
+
method: resource.method,
|
|
559
|
+
mode: resource.mode,
|
|
560
|
+
redirect: resource.redirect,
|
|
561
|
+
referrer: resource.referrer,
|
|
562
|
+
referrerPolicy: resource.referrerPolicy,
|
|
563
|
+
signal: resource.signal,
|
|
564
|
+
...init
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// we must fixup relative urls so they are resolved from the target page
|
|
569
|
+
const resolved = new URL(requested, url).href;
|
|
570
|
+
depends(resolved);
|
|
571
|
+
|
|
572
|
+
// prerendered pages may be served from any origin, so `initial_fetch` urls shouldn't be resolved
|
|
573
|
+
return started
|
|
574
|
+
? subsequent_fetch(resolved, init)
|
|
575
|
+
: initial_fetch(requested, resolved, init);
|
|
576
|
+
},
|
|
577
|
+
setHeaders: () => {}, // noop
|
|
578
|
+
depends,
|
|
579
|
+
parent() {
|
|
580
|
+
uses.parent = true;
|
|
581
|
+
return parent();
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
// TODO remove this for 1.0
|
|
586
|
+
Object.defineProperties(load_input, {
|
|
587
|
+
props: {
|
|
588
|
+
get() {
|
|
589
|
+
throw new Error(
|
|
590
|
+
'@migration task: Replace `props` with `data` stuff https://github.com/sveltejs/kit/discussions/5774#discussioncomment-3292693'
|
|
591
|
+
);
|
|
592
|
+
},
|
|
593
|
+
enumerable: false
|
|
594
|
+
},
|
|
595
|
+
session: {
|
|
596
|
+
get() {
|
|
597
|
+
throw new Error(
|
|
598
|
+
'session is no longer available. See https://github.com/sveltejs/kit/discussions/5883'
|
|
599
|
+
);
|
|
600
|
+
},
|
|
601
|
+
enumerable: false
|
|
602
|
+
},
|
|
603
|
+
stuff: {
|
|
604
|
+
get() {
|
|
605
|
+
throw new Error(
|
|
606
|
+
'@migration task: Remove stuff https://github.com/sveltejs/kit/discussions/5774#discussioncomment-3292693'
|
|
607
|
+
);
|
|
608
|
+
},
|
|
609
|
+
enumerable: false
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
if (import.meta.env.DEV) {
|
|
614
|
+
try {
|
|
615
|
+
lock_fetch();
|
|
616
|
+
data = (await node.shared.load.call(null, load_input)) ?? null;
|
|
617
|
+
} finally {
|
|
618
|
+
unlock_fetch();
|
|
619
|
+
}
|
|
620
|
+
} else {
|
|
621
|
+
data = (await node.shared.load.call(null, load_input)) ?? null;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return {
|
|
626
|
+
node,
|
|
627
|
+
loader,
|
|
628
|
+
server: server_data_node,
|
|
629
|
+
shared: node.shared?.load ? { type: 'data', data, uses } : null,
|
|
630
|
+
data: data ?? server_data_node?.data ?? null
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* @param {import('types').Uses | undefined} uses
|
|
636
|
+
* @param {boolean} parent_changed
|
|
637
|
+
* @param {{ url: boolean, params: string[] }} changed
|
|
638
|
+
*/
|
|
639
|
+
function has_changed(changed, parent_changed, uses) {
|
|
640
|
+
if (force_invalidation) return true;
|
|
641
|
+
|
|
642
|
+
if (!uses) return false;
|
|
643
|
+
|
|
644
|
+
if (uses.parent && parent_changed) return true;
|
|
645
|
+
if (changed.url && uses.url) return true;
|
|
646
|
+
|
|
647
|
+
for (const param of changed.params) {
|
|
648
|
+
if (uses.params.has(param)) return true;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
for (const href of uses.dependencies) {
|
|
652
|
+
if (invalidated.some((fn) => fn(new URL(href)))) return true;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* @param {import('types').ServerDataNode | import('types').ServerDataSkippedNode | null} node
|
|
660
|
+
* @param {import('./types').DataNode | null} [previous]
|
|
661
|
+
* @returns {import('./types').DataNode | null}
|
|
662
|
+
*/
|
|
663
|
+
function create_data_node(node, previous) {
|
|
664
|
+
if (node?.type === 'data') {
|
|
665
|
+
return {
|
|
666
|
+
type: 'data',
|
|
667
|
+
data: node.data,
|
|
668
|
+
uses: {
|
|
669
|
+
dependencies: new Set(node.uses.dependencies ?? []),
|
|
670
|
+
params: new Set(node.uses.params ?? []),
|
|
671
|
+
parent: !!node.uses.parent,
|
|
672
|
+
url: !!node.uses.url
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
} else if (node?.type === 'skip') {
|
|
676
|
+
return previous ?? null;
|
|
677
|
+
}
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* @param {import('./types').NavigationIntent} intent
|
|
683
|
+
* @returns {Promise<import('./types').NavigationResult | undefined>}
|
|
684
|
+
*/
|
|
685
|
+
async function load_route({ id, invalidating, url, params, route }) {
|
|
686
|
+
if (load_cache.id === id && load_cache.promise) {
|
|
687
|
+
return load_cache.promise;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const { errors, layouts, leaf } = route;
|
|
691
|
+
|
|
692
|
+
const changed = current.url && {
|
|
693
|
+
url: id !== current.url.pathname + current.url.search,
|
|
694
|
+
params: Object.keys(params).filter((key) => current.params[key] !== params[key])
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
const loaders = [...layouts, leaf];
|
|
698
|
+
|
|
699
|
+
// preload modules to avoid waterfall, but handle rejections
|
|
700
|
+
// so they don't get reported to Sentry et al (we don't need
|
|
701
|
+
// to act on the failures at this point)
|
|
702
|
+
errors.forEach((loader) => loader?.().catch(() => {}));
|
|
703
|
+
loaders.forEach((loader) => loader?.[1]().catch(() => {}));
|
|
704
|
+
|
|
705
|
+
/** @type {import('types').ServerData | null} */
|
|
706
|
+
let server_data = null;
|
|
707
|
+
|
|
708
|
+
const invalid_server_nodes = loaders.reduce((acc, loader, i) => {
|
|
709
|
+
const previous = current.branch[i];
|
|
710
|
+
const invalid =
|
|
711
|
+
!!loader?.[0] &&
|
|
712
|
+
(previous?.loader !== loader[1] ||
|
|
713
|
+
has_changed(changed, acc.some(Boolean), previous.server?.uses));
|
|
714
|
+
|
|
715
|
+
acc.push(invalid);
|
|
716
|
+
return acc;
|
|
717
|
+
}, /** @type {boolean[]} */ ([]));
|
|
718
|
+
|
|
719
|
+
if (invalid_server_nodes.some(Boolean)) {
|
|
720
|
+
try {
|
|
721
|
+
server_data = await load_data(url, invalid_server_nodes);
|
|
722
|
+
} catch (error) {
|
|
723
|
+
return load_root_error_page({
|
|
724
|
+
status: 500,
|
|
725
|
+
error: /** @type {Error} */ (error),
|
|
726
|
+
url,
|
|
727
|
+
routeId: route.id
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
if (server_data.type === 'redirect') {
|
|
732
|
+
return server_data;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const server_data_nodes = server_data?.nodes;
|
|
737
|
+
|
|
738
|
+
let parent_changed = false;
|
|
739
|
+
|
|
740
|
+
const branch_promises = loaders.map(async (loader, i) => {
|
|
741
|
+
if (!loader) return;
|
|
742
|
+
|
|
743
|
+
/** @type {import('./types').BranchNode | undefined} */
|
|
744
|
+
const previous = current.branch[i];
|
|
745
|
+
|
|
746
|
+
const server_data_node = server_data_nodes?.[i];
|
|
747
|
+
|
|
748
|
+
// re-use data from previous load if it's still valid
|
|
749
|
+
const valid =
|
|
750
|
+
(!server_data_node || server_data_node.type === 'skip') &&
|
|
751
|
+
loader[1] === previous?.loader &&
|
|
752
|
+
!has_changed(changed, parent_changed, previous.shared?.uses);
|
|
753
|
+
if (valid) return previous;
|
|
754
|
+
|
|
755
|
+
parent_changed = true;
|
|
756
|
+
|
|
757
|
+
if (server_data_node?.type === 'error') {
|
|
758
|
+
// rethrow and catch below
|
|
759
|
+
throw server_data_node;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
return load_node({
|
|
763
|
+
loader: loader[1],
|
|
764
|
+
url,
|
|
765
|
+
params,
|
|
766
|
+
routeId: route.id,
|
|
767
|
+
parent: async () => {
|
|
768
|
+
const data = {};
|
|
769
|
+
for (let j = 0; j < i; j += 1) {
|
|
770
|
+
Object.assign(data, (await branch_promises[j])?.data);
|
|
771
|
+
}
|
|
772
|
+
return data;
|
|
773
|
+
},
|
|
774
|
+
server_data_node: create_data_node(
|
|
775
|
+
// server_data_node is undefined if it wasn't reloaded from the server;
|
|
776
|
+
// and if current loader uses server data, we want to reuse previous data.
|
|
777
|
+
server_data_node === undefined && loader[0] ? { type: 'skip' } : server_data_node ?? null,
|
|
778
|
+
previous?.server
|
|
779
|
+
)
|
|
780
|
+
});
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
// if we don't do this, rejections will be unhandled
|
|
784
|
+
for (const p of branch_promises) p.catch(() => {});
|
|
785
|
+
|
|
786
|
+
/** @type {Array<import('./types').BranchNode | undefined>} */
|
|
787
|
+
const branch = [];
|
|
788
|
+
|
|
789
|
+
for (let i = 0; i < loaders.length; i += 1) {
|
|
790
|
+
if (loaders[i]) {
|
|
791
|
+
try {
|
|
792
|
+
branch.push(await branch_promises[i]);
|
|
793
|
+
} catch (err) {
|
|
794
|
+
if (err instanceof Redirect) {
|
|
795
|
+
return {
|
|
796
|
+
type: 'redirect',
|
|
797
|
+
location: err.location
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
let status = 500;
|
|
802
|
+
/** @type {App.PageError} */
|
|
803
|
+
let error;
|
|
804
|
+
|
|
805
|
+
if (server_data_nodes?.includes(/** @type {import('types').ServerErrorNode} */ (err))) {
|
|
806
|
+
// this is the server error rethrown above, reconstruct but don't invoke
|
|
807
|
+
// the client error handler; it should've already been handled on the server
|
|
808
|
+
status = /** @type {import('types').ServerErrorNode} */ (err).status ?? status;
|
|
809
|
+
error = /** @type {import('types').ServerErrorNode} */ (err).error;
|
|
810
|
+
} else if (err instanceof HttpError) {
|
|
811
|
+
status = err.status;
|
|
812
|
+
error = err.body;
|
|
813
|
+
} else {
|
|
814
|
+
error = handle_error(err, { params, url, routeId: route.id });
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
while (i--) {
|
|
818
|
+
if (errors[i]) {
|
|
819
|
+
/** @type {import('./types').BranchNode | undefined} */
|
|
820
|
+
let error_loaded;
|
|
821
|
+
|
|
822
|
+
let j = i;
|
|
823
|
+
while (!branch[j]) j -= 1;
|
|
824
|
+
try {
|
|
825
|
+
error_loaded = {
|
|
826
|
+
node: await /** @type {import('types').CSRPageNodeLoader } */ (errors[i])(),
|
|
827
|
+
loader: /** @type {import('types').CSRPageNodeLoader } */ (errors[i]),
|
|
828
|
+
data: {},
|
|
829
|
+
server: null,
|
|
830
|
+
shared: null
|
|
831
|
+
};
|
|
832
|
+
|
|
833
|
+
return await get_navigation_result_from_branch({
|
|
834
|
+
url,
|
|
835
|
+
params,
|
|
836
|
+
branch: branch.slice(0, j + 1).concat(error_loaded),
|
|
837
|
+
status,
|
|
838
|
+
error,
|
|
839
|
+
route
|
|
840
|
+
});
|
|
841
|
+
} catch (e) {
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// if we get here, it's because the root `load` function failed,
|
|
848
|
+
// and we need to fall back to the server
|
|
849
|
+
await native_navigation(url);
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
} else {
|
|
853
|
+
// push an empty slot so we can rewind past gaps to the
|
|
854
|
+
// layout that corresponds with an +error.svelte page
|
|
855
|
+
branch.push(undefined);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
return await get_navigation_result_from_branch({
|
|
860
|
+
url,
|
|
861
|
+
params,
|
|
862
|
+
branch,
|
|
863
|
+
status: 200,
|
|
864
|
+
error: null,
|
|
865
|
+
route,
|
|
866
|
+
// Reset `form` on navigation, but not invalidation
|
|
867
|
+
form: invalidating ? undefined : null
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* @param {{
|
|
873
|
+
* status: number;
|
|
874
|
+
* error: HttpError | Error;
|
|
875
|
+
* url: URL;
|
|
876
|
+
* routeId: string | null
|
|
877
|
+
* }} opts
|
|
878
|
+
* @returns {Promise<import('./types').NavigationFinished>}
|
|
879
|
+
*/
|
|
880
|
+
async function load_root_error_page({ status, error, url, routeId }) {
|
|
881
|
+
/** @type {Record<string, string>} */
|
|
882
|
+
const params = {}; // error page does not have params
|
|
883
|
+
|
|
884
|
+
const node = await default_layout_loader();
|
|
885
|
+
|
|
886
|
+
/** @type {import('types').ServerDataNode | null} */
|
|
887
|
+
let server_data_node = null;
|
|
888
|
+
|
|
889
|
+
if (node.server) {
|
|
890
|
+
// TODO post-https://github.com/sveltejs/kit/discussions/6124 we can use
|
|
891
|
+
// existing root layout data
|
|
892
|
+
try {
|
|
893
|
+
const server_data = await load_data(url, [true]);
|
|
894
|
+
|
|
895
|
+
if (
|
|
896
|
+
server_data.type !== 'data' ||
|
|
897
|
+
(server_data.nodes[0] && server_data.nodes[0].type !== 'data')
|
|
898
|
+
) {
|
|
899
|
+
throw 0;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
server_data_node = server_data.nodes[0] ?? null;
|
|
903
|
+
} catch {
|
|
904
|
+
// at this point we have no choice but to fall back to the server
|
|
905
|
+
await native_navigation(url);
|
|
906
|
+
|
|
907
|
+
// @ts-expect-error
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
const root_layout = await load_node({
|
|
913
|
+
loader: default_layout_loader,
|
|
914
|
+
url,
|
|
915
|
+
params,
|
|
916
|
+
routeId,
|
|
917
|
+
parent: () => Promise.resolve({}),
|
|
918
|
+
server_data_node: create_data_node(server_data_node)
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
/** @type {import('./types').BranchNode} */
|
|
922
|
+
const root_error = {
|
|
923
|
+
node: await default_error_loader(),
|
|
924
|
+
loader: default_error_loader,
|
|
925
|
+
shared: null,
|
|
926
|
+
server: null,
|
|
927
|
+
data: null
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
return await get_navigation_result_from_branch({
|
|
931
|
+
url,
|
|
932
|
+
params,
|
|
933
|
+
branch: [root_layout, root_error],
|
|
934
|
+
status,
|
|
935
|
+
error:
|
|
936
|
+
error instanceof HttpError
|
|
937
|
+
? error.body
|
|
938
|
+
: handle_error(error, { url, params, routeId: null }),
|
|
939
|
+
route: null
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* @param {URL} url
|
|
945
|
+
* @param {boolean} invalidating
|
|
946
|
+
*/
|
|
947
|
+
function get_navigation_intent(url, invalidating) {
|
|
948
|
+
if (is_external_url(url)) return;
|
|
949
|
+
|
|
950
|
+
const path = decodeURI(url.pathname.slice(base.length) || '/');
|
|
951
|
+
|
|
952
|
+
for (const route of routes) {
|
|
953
|
+
const params = route.exec(path);
|
|
954
|
+
|
|
955
|
+
if (params) {
|
|
956
|
+
const normalized = new URL(
|
|
957
|
+
url.origin + normalize_path(url.pathname, trailing_slash) + url.search + url.hash
|
|
958
|
+
);
|
|
959
|
+
const id = normalized.pathname + normalized.search;
|
|
960
|
+
/** @type {import('./types').NavigationIntent} */
|
|
961
|
+
const intent = { id, invalidating, route, params: decode_params(params), url: normalized };
|
|
962
|
+
return intent;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/** @param {URL} url */
|
|
968
|
+
function is_external_url(url) {
|
|
969
|
+
return url.origin !== location.origin || !url.pathname.startsWith(base);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* @param {{
|
|
974
|
+
* url: URL;
|
|
975
|
+
* scroll: { x: number, y: number } | null;
|
|
976
|
+
* keepfocus: boolean;
|
|
977
|
+
* redirect_chain: string[];
|
|
978
|
+
* details: {
|
|
979
|
+
* replaceState: boolean;
|
|
980
|
+
* state: any;
|
|
981
|
+
* } | null;
|
|
982
|
+
* type: import('types').NavigationType;
|
|
983
|
+
* delta?: number;
|
|
984
|
+
* accepted: () => void;
|
|
985
|
+
* blocked: () => void;
|
|
986
|
+
* }} opts
|
|
987
|
+
*/
|
|
988
|
+
async function navigate({
|
|
989
|
+
url,
|
|
990
|
+
scroll,
|
|
991
|
+
keepfocus,
|
|
992
|
+
redirect_chain,
|
|
993
|
+
details,
|
|
994
|
+
type,
|
|
995
|
+
delta,
|
|
996
|
+
accepted,
|
|
997
|
+
blocked
|
|
998
|
+
}) {
|
|
999
|
+
let should_block = false;
|
|
1000
|
+
|
|
1001
|
+
const intent = get_navigation_intent(url, false);
|
|
1002
|
+
|
|
1003
|
+
/** @type {import('types').Navigation} */
|
|
1004
|
+
const navigation = {
|
|
1005
|
+
from: add_url_properties('from', {
|
|
1006
|
+
params: current.params,
|
|
1007
|
+
routeId: current.route?.id ?? null,
|
|
1008
|
+
url: current.url
|
|
1009
|
+
}),
|
|
1010
|
+
to: add_url_properties('to', {
|
|
1011
|
+
params: intent?.params ?? null,
|
|
1012
|
+
routeId: intent?.route.id ?? null,
|
|
1013
|
+
url
|
|
1014
|
+
}),
|
|
1015
|
+
type
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
if (delta !== undefined) {
|
|
1019
|
+
navigation.delta = delta;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
const cancellable = {
|
|
1023
|
+
...navigation,
|
|
1024
|
+
cancel: () => {
|
|
1025
|
+
should_block = true;
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
callbacks.before_navigate.forEach((fn) => fn(cancellable));
|
|
1030
|
+
|
|
1031
|
+
if (should_block) {
|
|
1032
|
+
blocked();
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
update_scroll_positions(current_history_index);
|
|
1037
|
+
|
|
1038
|
+
accepted();
|
|
1039
|
+
|
|
1040
|
+
if (started) {
|
|
1041
|
+
stores.navigating.set(navigation);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
await update(
|
|
1045
|
+
intent,
|
|
1046
|
+
url,
|
|
1047
|
+
redirect_chain,
|
|
1048
|
+
{
|
|
1049
|
+
scroll,
|
|
1050
|
+
keepfocus,
|
|
1051
|
+
details
|
|
1052
|
+
},
|
|
1053
|
+
() => {
|
|
1054
|
+
callbacks.after_navigate.forEach((fn) => fn(navigation));
|
|
1055
|
+
stores.navigating.set(null);
|
|
1056
|
+
}
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Loads `href` the old-fashioned way, with a full page reload.
|
|
1062
|
+
* Returns a `Promise` that never resolves (to prevent any
|
|
1063
|
+
* subsequent work, e.g. history manipulation, from happening)
|
|
1064
|
+
* @param {URL} url
|
|
1065
|
+
*/
|
|
1066
|
+
function native_navigation(url) {
|
|
1067
|
+
location.href = url.href;
|
|
1068
|
+
return new Promise(() => {});
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
if (import.meta.hot) {
|
|
1072
|
+
import.meta.hot.on('vite:beforeUpdate', () => {
|
|
1073
|
+
if (current.error) location.reload();
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
return {
|
|
1078
|
+
after_navigate: (fn) => {
|
|
1079
|
+
onMount(() => {
|
|
1080
|
+
callbacks.after_navigate.push(fn);
|
|
1081
|
+
|
|
1082
|
+
return () => {
|
|
1083
|
+
const i = callbacks.after_navigate.indexOf(fn);
|
|
1084
|
+
callbacks.after_navigate.splice(i, 1);
|
|
1085
|
+
};
|
|
1086
|
+
});
|
|
1087
|
+
},
|
|
1088
|
+
|
|
1089
|
+
before_navigate: (fn) => {
|
|
1090
|
+
onMount(() => {
|
|
1091
|
+
callbacks.before_navigate.push(fn);
|
|
1092
|
+
|
|
1093
|
+
return () => {
|
|
1094
|
+
const i = callbacks.before_navigate.indexOf(fn);
|
|
1095
|
+
callbacks.before_navigate.splice(i, 1);
|
|
1096
|
+
};
|
|
1097
|
+
});
|
|
1098
|
+
},
|
|
1099
|
+
|
|
1100
|
+
disable_scroll_handling: () => {
|
|
1101
|
+
if (import.meta.env.DEV && started && !updating) {
|
|
1102
|
+
throw new Error('Can only disable scroll handling during navigation');
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
if (updating || !started) {
|
|
1106
|
+
autoscroll = false;
|
|
1107
|
+
}
|
|
1108
|
+
},
|
|
1109
|
+
|
|
1110
|
+
goto: (href, opts = {}) => goto(href, opts, []),
|
|
1111
|
+
|
|
1112
|
+
invalidate: (resource) => {
|
|
1113
|
+
if (resource === undefined) {
|
|
1114
|
+
// TODO remove for 1.0
|
|
1115
|
+
throw new Error(
|
|
1116
|
+
'`invalidate()` (with no arguments) has been replaced by `invalidateAll()`'
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
if (typeof resource === 'function') {
|
|
1121
|
+
invalidated.push(resource);
|
|
1122
|
+
} else {
|
|
1123
|
+
const { href } = new URL(resource, location.href);
|
|
1124
|
+
invalidated.push((url) => url.href === href);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
return invalidate();
|
|
1128
|
+
},
|
|
1129
|
+
|
|
1130
|
+
invalidateAll: () => {
|
|
1131
|
+
force_invalidation = true;
|
|
1132
|
+
return invalidate();
|
|
1133
|
+
},
|
|
1134
|
+
|
|
1135
|
+
prefetch: async (href) => {
|
|
1136
|
+
const url = new URL(href, get_base_uri(document));
|
|
1137
|
+
await prefetch(url);
|
|
1138
|
+
},
|
|
1139
|
+
|
|
1140
|
+
// TODO rethink this API
|
|
1141
|
+
prefetch_routes: async (pathnames) => {
|
|
1142
|
+
const matching = pathnames
|
|
1143
|
+
? routes.filter((route) => pathnames.some((pathname) => route.exec(pathname)))
|
|
1144
|
+
: routes;
|
|
1145
|
+
|
|
1146
|
+
const promises = matching.map((r) => {
|
|
1147
|
+
return Promise.all([...r.layouts, r.leaf].map((load) => load?.[1]()));
|
|
1148
|
+
});
|
|
1149
|
+
|
|
1150
|
+
await Promise.all(promises);
|
|
1151
|
+
},
|
|
1152
|
+
|
|
1153
|
+
apply_action: async (result) => {
|
|
1154
|
+
if (result.type === 'error') {
|
|
1155
|
+
const url = new URL(location.href);
|
|
1156
|
+
|
|
1157
|
+
const { branch, route } = current;
|
|
1158
|
+
if (!route) return;
|
|
1159
|
+
|
|
1160
|
+
let i = current.branch.length;
|
|
1161
|
+
|
|
1162
|
+
while (i--) {
|
|
1163
|
+
if (route.errors[i]) {
|
|
1164
|
+
/** @type {import('./types').BranchNode | undefined} */
|
|
1165
|
+
let error_loaded;
|
|
1166
|
+
|
|
1167
|
+
let j = i;
|
|
1168
|
+
while (!branch[j]) j -= 1;
|
|
1169
|
+
try {
|
|
1170
|
+
error_loaded = {
|
|
1171
|
+
node: await /** @type {import('types').CSRPageNodeLoader } */ (route.errors[i])(),
|
|
1172
|
+
loader: /** @type {import('types').CSRPageNodeLoader } */ (route.errors[i]),
|
|
1173
|
+
data: {},
|
|
1174
|
+
server: null,
|
|
1175
|
+
shared: null
|
|
1176
|
+
};
|
|
1177
|
+
|
|
1178
|
+
const navigation_result = await get_navigation_result_from_branch({
|
|
1179
|
+
url,
|
|
1180
|
+
params: current.params,
|
|
1181
|
+
branch: branch.slice(0, j + 1).concat(error_loaded),
|
|
1182
|
+
status: 500, // TODO might not be 500?
|
|
1183
|
+
error: result.error,
|
|
1184
|
+
route
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
current = navigation_result.state;
|
|
1188
|
+
|
|
1189
|
+
const post_update = pre_update();
|
|
1190
|
+
root.$set(navigation_result.props);
|
|
1191
|
+
post_update();
|
|
1192
|
+
|
|
1193
|
+
return;
|
|
1194
|
+
} catch (e) {
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
} else if (result.type === 'redirect') {
|
|
1200
|
+
goto(result.location, {}, []);
|
|
1201
|
+
} else {
|
|
1202
|
+
/** @type {Record<string, any>} */
|
|
1203
|
+
const props = { form: result.data };
|
|
1204
|
+
|
|
1205
|
+
if (result.status !== page.status) {
|
|
1206
|
+
props.page = {
|
|
1207
|
+
...page,
|
|
1208
|
+
status: result.status
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
const post_update = pre_update();
|
|
1213
|
+
root.$set(props);
|
|
1214
|
+
post_update();
|
|
1215
|
+
}
|
|
1216
|
+
},
|
|
1217
|
+
|
|
1218
|
+
_start_router: () => {
|
|
1219
|
+
history.scrollRestoration = 'manual';
|
|
1220
|
+
|
|
1221
|
+
// Adopted from Nuxt.js
|
|
1222
|
+
// Reset scrollRestoration to auto when leaving page, allowing page reload
|
|
1223
|
+
// and back-navigation from other pages to use the browser to restore the
|
|
1224
|
+
// scrolling position.
|
|
1225
|
+
addEventListener('beforeunload', (e) => {
|
|
1226
|
+
let should_block = false;
|
|
1227
|
+
|
|
1228
|
+
/** @type {import('types').Navigation & { cancel: () => void }} */
|
|
1229
|
+
const navigation = {
|
|
1230
|
+
from: add_url_properties('from', {
|
|
1231
|
+
params: current.params,
|
|
1232
|
+
routeId: current.route?.id ?? null,
|
|
1233
|
+
url: current.url
|
|
1234
|
+
}),
|
|
1235
|
+
to: null,
|
|
1236
|
+
type: 'unload',
|
|
1237
|
+
cancel: () => (should_block = true)
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
callbacks.before_navigate.forEach((fn) => fn(navigation));
|
|
1241
|
+
|
|
1242
|
+
if (should_block) {
|
|
1243
|
+
e.preventDefault();
|
|
1244
|
+
e.returnValue = '';
|
|
1245
|
+
} else {
|
|
1246
|
+
history.scrollRestoration = 'auto';
|
|
1247
|
+
}
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
addEventListener('visibilitychange', () => {
|
|
1251
|
+
if (document.visibilityState === 'hidden') {
|
|
1252
|
+
update_scroll_positions(current_history_index);
|
|
1253
|
+
|
|
1254
|
+
try {
|
|
1255
|
+
sessionStorage[SCROLL_KEY] = JSON.stringify(scroll_positions);
|
|
1256
|
+
} catch {
|
|
1257
|
+
// do nothing
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
/** @param {Event} event */
|
|
1263
|
+
const trigger_prefetch = (event) => {
|
|
1264
|
+
const { url, options } = find_anchor(event);
|
|
1265
|
+
if (url && options.prefetch) {
|
|
1266
|
+
if (is_external_url(url)) return;
|
|
1267
|
+
prefetch(url);
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
|
|
1271
|
+
/** @type {NodeJS.Timeout} */
|
|
1272
|
+
let mousemove_timeout;
|
|
1273
|
+
|
|
1274
|
+
/** @param {MouseEvent|TouchEvent} event */
|
|
1275
|
+
const handle_mousemove = (event) => {
|
|
1276
|
+
clearTimeout(mousemove_timeout);
|
|
1277
|
+
mousemove_timeout = setTimeout(() => {
|
|
1278
|
+
// event.composedPath(), which is used in find_anchor, will be empty if the event is read in a timeout
|
|
1279
|
+
// add a layer of indirection to address that
|
|
1280
|
+
event.target?.dispatchEvent(
|
|
1281
|
+
new CustomEvent('sveltekit:trigger_prefetch', { bubbles: true })
|
|
1282
|
+
);
|
|
1283
|
+
}, 20);
|
|
1284
|
+
};
|
|
1285
|
+
|
|
1286
|
+
addEventListener('touchstart', trigger_prefetch);
|
|
1287
|
+
addEventListener('mousemove', handle_mousemove);
|
|
1288
|
+
addEventListener('sveltekit:trigger_prefetch', trigger_prefetch);
|
|
1289
|
+
|
|
1290
|
+
/** @param {MouseEvent} event */
|
|
1291
|
+
addEventListener('click', (event) => {
|
|
1292
|
+
// Adapted from https://github.com/visionmedia/page.js
|
|
1293
|
+
// MIT license https://github.com/visionmedia/page.js#license
|
|
1294
|
+
if (event.button || event.which !== 1) return;
|
|
1295
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
1296
|
+
if (event.defaultPrevented) return;
|
|
1297
|
+
|
|
1298
|
+
const { a, url, options } = find_anchor(event);
|
|
1299
|
+
if (!a || !url) return;
|
|
1300
|
+
|
|
1301
|
+
const is_svg_a_element = a instanceof SVGAElement;
|
|
1302
|
+
|
|
1303
|
+
// Ignore non-HTTP URL protocols (e.g. `mailto:`, `tel:`, `myapp:`, etc.)
|
|
1304
|
+
// MEMO: Without this condition, firefox will open mailer twice.
|
|
1305
|
+
// See:
|
|
1306
|
+
// - https://github.com/sveltejs/kit/issues/4045
|
|
1307
|
+
// - https://github.com/sveltejs/kit/issues/5725
|
|
1308
|
+
if (!is_svg_a_element && !(url.protocol === 'https:' || url.protocol === 'http:')) return;
|
|
1309
|
+
|
|
1310
|
+
// Ignore if tag has
|
|
1311
|
+
// 1. 'download' attribute
|
|
1312
|
+
// 2. 'rel' attribute includes external
|
|
1313
|
+
const rel = (a.getAttribute('rel') || '').split(/\s+/);
|
|
1314
|
+
|
|
1315
|
+
if (a.hasAttribute('download') || rel.includes('external') || options.reload) {
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// Ignore if <a> has a target
|
|
1320
|
+
if (is_svg_a_element ? a.target.baseVal : a.target) return;
|
|
1321
|
+
|
|
1322
|
+
// Check if new url only differs by hash and use the browser default behavior in that case
|
|
1323
|
+
// This will ensure the `hashchange` event is fired
|
|
1324
|
+
// Removing the hash does a full page navigation in the browser, so make sure a hash is present
|
|
1325
|
+
const [base, hash] = url.href.split('#');
|
|
1326
|
+
if (hash !== undefined && base === location.href.split('#')[0]) {
|
|
1327
|
+
// set this flag to distinguish between navigations triggered by
|
|
1328
|
+
// clicking a hash link and those triggered by popstate
|
|
1329
|
+
// TODO why not update history here directly?
|
|
1330
|
+
hash_navigating = true;
|
|
1331
|
+
|
|
1332
|
+
update_scroll_positions(current_history_index);
|
|
1333
|
+
|
|
1334
|
+
current.url = url;
|
|
1335
|
+
stores.page.set({ ...page, url });
|
|
1336
|
+
stores.page.notify();
|
|
1337
|
+
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
navigate({
|
|
1342
|
+
url,
|
|
1343
|
+
scroll: options.noscroll ? scroll_state() : null,
|
|
1344
|
+
keepfocus: false,
|
|
1345
|
+
redirect_chain: [],
|
|
1346
|
+
details: {
|
|
1347
|
+
state: {},
|
|
1348
|
+
replaceState: url.href === location.href
|
|
1349
|
+
},
|
|
1350
|
+
accepted: () => event.preventDefault(),
|
|
1351
|
+
blocked: () => event.preventDefault(),
|
|
1352
|
+
type: 'link'
|
|
1353
|
+
});
|
|
1354
|
+
});
|
|
1355
|
+
|
|
1356
|
+
addEventListener('popstate', (event) => {
|
|
1357
|
+
if (event.state) {
|
|
1358
|
+
// if a popstate-driven navigation is cancelled, we need to counteract it
|
|
1359
|
+
// with history.go, which means we end up back here, hence this check
|
|
1360
|
+
if (event.state[INDEX_KEY] === current_history_index) return;
|
|
1361
|
+
|
|
1362
|
+
const delta = event.state[INDEX_KEY] - current_history_index;
|
|
1363
|
+
|
|
1364
|
+
navigate({
|
|
1365
|
+
url: new URL(location.href),
|
|
1366
|
+
scroll: scroll_positions[event.state[INDEX_KEY]],
|
|
1367
|
+
keepfocus: false,
|
|
1368
|
+
redirect_chain: [],
|
|
1369
|
+
details: null,
|
|
1370
|
+
accepted: () => {
|
|
1371
|
+
current_history_index = event.state[INDEX_KEY];
|
|
1372
|
+
},
|
|
1373
|
+
blocked: () => {
|
|
1374
|
+
history.go(-delta);
|
|
1375
|
+
},
|
|
1376
|
+
type: 'popstate',
|
|
1377
|
+
delta
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
addEventListener('hashchange', () => {
|
|
1383
|
+
// if the hashchange happened as a result of clicking on a link,
|
|
1384
|
+
// we need to update history, otherwise we have to leave it alone
|
|
1385
|
+
if (hash_navigating) {
|
|
1386
|
+
hash_navigating = false;
|
|
1387
|
+
history.replaceState(
|
|
1388
|
+
{ ...history.state, [INDEX_KEY]: ++current_history_index },
|
|
1389
|
+
'',
|
|
1390
|
+
location.href
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
});
|
|
1394
|
+
|
|
1395
|
+
// fix link[rel=icon], because browsers will occasionally try to load relative
|
|
1396
|
+
// URLs after a pushState/replaceState, resulting in a 404 — see
|
|
1397
|
+
// https://github.com/sveltejs/kit/issues/3748#issuecomment-1125980897
|
|
1398
|
+
for (const link of document.querySelectorAll('link')) {
|
|
1399
|
+
if (link.rel === 'icon') link.href = link.href;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
addEventListener('pageshow', (event) => {
|
|
1403
|
+
// If the user navigates to another site and then uses the back button and
|
|
1404
|
+
// bfcache hits, we need to set navigating to null, the site doesn't know
|
|
1405
|
+
// the navigation away from it was successful.
|
|
1406
|
+
// Info about bfcache here: https://web.dev/bfcache
|
|
1407
|
+
if (event.persisted) {
|
|
1408
|
+
stores.navigating.set(null);
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1411
|
+
},
|
|
1412
|
+
|
|
1413
|
+
_hydrate: async ({
|
|
1414
|
+
status,
|
|
1415
|
+
error,
|
|
1416
|
+
node_ids,
|
|
1417
|
+
params,
|
|
1418
|
+
routeId,
|
|
1419
|
+
data: server_data_nodes,
|
|
1420
|
+
form
|
|
1421
|
+
}) => {
|
|
1422
|
+
const url = new URL(location.href);
|
|
1423
|
+
|
|
1424
|
+
/** @type {import('./types').NavigationFinished | undefined} */
|
|
1425
|
+
let result;
|
|
1426
|
+
|
|
1427
|
+
try {
|
|
1428
|
+
const branch_promises = node_ids.map(async (n, i) => {
|
|
1429
|
+
const server_data_node = server_data_nodes[i];
|
|
1430
|
+
|
|
1431
|
+
return load_node({
|
|
1432
|
+
loader: nodes[n],
|
|
1433
|
+
url,
|
|
1434
|
+
params,
|
|
1435
|
+
routeId,
|
|
1436
|
+
parent: async () => {
|
|
1437
|
+
const data = {};
|
|
1438
|
+
for (let j = 0; j < i; j += 1) {
|
|
1439
|
+
Object.assign(data, (await branch_promises[j]).data);
|
|
1440
|
+
}
|
|
1441
|
+
return data;
|
|
1442
|
+
},
|
|
1443
|
+
server_data_node: create_data_node(server_data_node)
|
|
1444
|
+
});
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1447
|
+
result = await get_navigation_result_from_branch({
|
|
1448
|
+
url,
|
|
1449
|
+
params,
|
|
1450
|
+
branch: await Promise.all(branch_promises),
|
|
1451
|
+
status,
|
|
1452
|
+
error,
|
|
1453
|
+
form,
|
|
1454
|
+
route: routes.find((route) => route.id === routeId) ?? null
|
|
1455
|
+
});
|
|
1456
|
+
} catch (e) {
|
|
1457
|
+
const error = normalize_error(e);
|
|
1458
|
+
|
|
1459
|
+
if (error instanceof Redirect) {
|
|
1460
|
+
// this is a real edge case — `load` would need to return
|
|
1461
|
+
// a redirect but only in the browser
|
|
1462
|
+
await native_navigation(new URL(/** @type {Redirect} */ (e).location, location.href));
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
result = await load_root_error_page({
|
|
1467
|
+
status: error instanceof HttpError ? error.status : 500,
|
|
1468
|
+
error,
|
|
1469
|
+
url,
|
|
1470
|
+
routeId
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
initialize(result);
|
|
1475
|
+
}
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
let data_id = 1;
|
|
1480
|
+
|
|
1481
|
+
/**
|
|
1482
|
+
* @param {URL} url
|
|
1483
|
+
* @param {boolean[]} invalid
|
|
1484
|
+
* @returns {Promise<import('types').ServerData>}
|
|
1485
|
+
*/
|
|
1486
|
+
async function load_data(url, invalid) {
|
|
1487
|
+
const data_url = new URL(url);
|
|
1488
|
+
data_url.pathname = url.pathname.replace(/\/$/, '') + DATA_SUFFIX;
|
|
1489
|
+
data_url.searchParams.set('__invalid', invalid.map((x) => (x ? 'y' : 'n')).join(''));
|
|
1490
|
+
data_url.searchParams.set('__id', String(data_id++));
|
|
1491
|
+
|
|
1492
|
+
// The __data.js file is generated by the server and looks like
|
|
1493
|
+
// `window.__sveltekit_data = ${devalue(data)}`. We do this instead
|
|
1494
|
+
// of `export const data` because modules are cached indefinitely,
|
|
1495
|
+
// and that would cause memory leaks.
|
|
1496
|
+
//
|
|
1497
|
+
// The data is read and deleted in the same tick as the promise
|
|
1498
|
+
// resolves, so it's not vulnerable to race conditions
|
|
1499
|
+
await import(/* @vite-ignore */ data_url.href);
|
|
1500
|
+
|
|
1501
|
+
// @ts-expect-error
|
|
1502
|
+
const server_data = window.__sveltekit_data;
|
|
1503
|
+
|
|
1504
|
+
// @ts-expect-error
|
|
1505
|
+
delete window.__sveltekit_data;
|
|
1506
|
+
|
|
1507
|
+
return server_data;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
/**
|
|
1511
|
+
* @param {unknown} error
|
|
1512
|
+
* @param {import('types').NavigationEvent} event
|
|
1513
|
+
* @returns {App.PageError}
|
|
1514
|
+
*/
|
|
1515
|
+
function handle_error(error, event) {
|
|
1516
|
+
return (
|
|
1517
|
+
hooks.handleError({ error, event }) ??
|
|
1518
|
+
/** @type {any} */ ({ message: event.routeId ? 'Internal Error' : 'Not Found' })
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// TODO remove for 1.0
|
|
1523
|
+
const properties = [
|
|
1524
|
+
'hash',
|
|
1525
|
+
'href',
|
|
1526
|
+
'host',
|
|
1527
|
+
'hostname',
|
|
1528
|
+
'origin',
|
|
1529
|
+
'pathname',
|
|
1530
|
+
'port',
|
|
1531
|
+
'protocol',
|
|
1532
|
+
'search',
|
|
1533
|
+
'searchParams',
|
|
1534
|
+
'toString',
|
|
1535
|
+
'toJSON'
|
|
1536
|
+
];
|
|
1537
|
+
|
|
1538
|
+
/**
|
|
1539
|
+
* @param {'from' | 'to'} type
|
|
1540
|
+
* @param {import('types').NavigationTarget} target
|
|
1541
|
+
*/
|
|
1542
|
+
function add_url_properties(type, target) {
|
|
1543
|
+
for (const prop of properties) {
|
|
1544
|
+
Object.defineProperty(target, prop, {
|
|
1545
|
+
get() {
|
|
1546
|
+
throw new Error(
|
|
1547
|
+
`The navigation shape changed - ${type}.${prop} should now be ${type}.url.${prop}`
|
|
1548
|
+
);
|
|
1549
|
+
},
|
|
1550
|
+
enumerable: false
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
return target;
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
function pre_update() {
|
|
1558
|
+
if (__SVELTEKIT_DEV__) {
|
|
1559
|
+
return () => {
|
|
1560
|
+
check_for_removed_attributes();
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
return () => {};
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
if (__SVELTEKIT_DEV__) {
|
|
1568
|
+
// Nasty hack to silence harmless warnings the user can do nothing about
|
|
1569
|
+
const warn = console.warn;
|
|
1570
|
+
console.warn = (...args) => {
|
|
1571
|
+
if (
|
|
1572
|
+
args.length === 1 &&
|
|
1573
|
+
/<(Layout|Page)(_[\w$]+)?> was created (with unknown|without expected) prop '(data|form)'/.test(
|
|
1574
|
+
args[0]
|
|
1575
|
+
)
|
|
1576
|
+
) {
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
warn(...args);
|
|
1580
|
+
};
|
|
1581
|
+
}
|