@sveltejs/kit 2.53.4 → 2.55.0
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/package.json +2 -2
- package/src/core/config/options.js +13 -12
- package/src/core/sync/sync.js +1 -1
- package/src/core/sync/write_non_ambient.js +143 -37
- package/src/core/sync/write_root.js +41 -6
- package/src/core/sync/write_server.js +1 -0
- package/src/core/sync/write_types/index.js +1 -5
- package/src/exports/index.js +1 -1
- package/src/exports/public.d.ts +50 -59
- package/src/exports/vite/build/build_server.js +2 -2
- package/src/exports/vite/build/build_service_worker.js +1 -0
- package/src/exports/vite/index.js +3 -2
- package/src/runtime/app/paths/client.js +2 -2
- package/src/runtime/app/paths/public.d.ts +2 -2
- package/src/runtime/app/paths/server.js +1 -1
- package/src/runtime/app/paths/types.d.ts +22 -6
- package/src/runtime/app/state/server.js +1 -1
- package/src/runtime/client/client.js +100 -13
- package/src/runtime/client/remote-functions/form.svelte.js +8 -0
- package/src/runtime/client/state.svelte.js +4 -2
- package/src/runtime/client/types.d.ts +2 -0
- package/src/runtime/server/page/index.js +57 -8
- package/src/runtime/server/page/render.js +46 -5
- package/src/runtime/server/page/respond_with_error.js +1 -0
- package/src/runtime/server/page/serialize_data.js +1 -1
- package/src/types/ambient.d.ts +13 -0
- package/src/types/global-private.d.ts +1 -0
- package/src/types/internal.d.ts +1 -0
- package/src/version.js +1 -1
- package/types/index.d.ts +83 -68
- package/types/index.d.ts.map +4 -1
|
@@ -107,7 +107,7 @@ export function build_server_nodes(
|
|
|
107
107
|
let fonts = [];
|
|
108
108
|
|
|
109
109
|
/** @type {Set<string>} */
|
|
110
|
-
|
|
110
|
+
const eager_assets = new Set();
|
|
111
111
|
|
|
112
112
|
if (node.component && client_manifest) {
|
|
113
113
|
exports.push(
|
|
@@ -215,7 +215,7 @@ export function build_server_nodes(
|
|
|
215
215
|
const filename = basename(file);
|
|
216
216
|
const dest = `${out}/server/stylesheets/${filename}.js`;
|
|
217
217
|
|
|
218
|
-
|
|
218
|
+
const css = /** @type {string} */ (stylesheets_to_inline.get(file));
|
|
219
219
|
|
|
220
220
|
fs.writeFileSync(
|
|
221
221
|
dest,
|
|
@@ -367,7 +367,8 @@ async function kit({ svelte_config }) {
|
|
|
367
367
|
__SVELTEKIT_PATHS_RELATIVE__: s(kit.paths.relative),
|
|
368
368
|
__SVELTEKIT_CLIENT_ROUTING__: s(kit.router.resolution === 'client'),
|
|
369
369
|
__SVELTEKIT_HASH_ROUTING__: s(kit.router.type === 'hash'),
|
|
370
|
-
__SVELTEKIT_SERVER_TRACING_ENABLED__: s(kit.experimental.tracing.server)
|
|
370
|
+
__SVELTEKIT_SERVER_TRACING_ENABLED__: s(kit.experimental.tracing.server),
|
|
371
|
+
__SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__: s(kit.experimental.handleRenderingErrors)
|
|
371
372
|
};
|
|
372
373
|
|
|
373
374
|
if (is_build) {
|
|
@@ -979,7 +980,7 @@ async function kit({ svelte_config }) {
|
|
|
979
980
|
build: {
|
|
980
981
|
rollupOptions: {
|
|
981
982
|
// Vite dependency crawler needs an explicit JS entry point
|
|
982
|
-
//
|
|
983
|
+
// even though server otherwise works without it
|
|
983
984
|
input: `${runtime_directory}/client/entry.js`
|
|
984
985
|
}
|
|
985
986
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @import { Asset, RouteId, Pathname, ResolvedPathname } from '$app/types' */
|
|
1
|
+
/** @import { Asset, RouteId, RouteIdWithSearchOrHash, Pathname, PathnameWithSearchOrHash, ResolvedPathname } from '$app/types' */
|
|
2
2
|
/** @import { ResolveArgs } from './types.js' */
|
|
3
3
|
import { base, assets, hash_routing } from './internal/client.js';
|
|
4
4
|
import { resolve_route } from '../../../utils/routing.js';
|
|
@@ -47,7 +47,7 @@ const pathname_prefix = hash_routing ? '#' : '';
|
|
|
47
47
|
* ```
|
|
48
48
|
* @since 2.26
|
|
49
49
|
*
|
|
50
|
-
* @template {
|
|
50
|
+
* @template {RouteIdWithSearchOrHash | PathnameWithSearchOrHash} T
|
|
51
51
|
* @param {ResolveArgs<T>} args
|
|
52
52
|
* @returns {ResolvedPathname}
|
|
53
53
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RouteIdWithSearchOrHash, PathnameWithSearchOrHash, ResolvedPathname } from '$app/types';
|
|
2
2
|
import { ResolveArgs } from './types.js';
|
|
3
3
|
|
|
4
4
|
export { resolve, asset, match } from './client.js';
|
|
@@ -24,6 +24,6 @@ export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit
|
|
|
24
24
|
/**
|
|
25
25
|
* @deprecated Use [`resolve(...)`](https://svelte.dev/docs/kit/$app-paths#resolve) instead
|
|
26
26
|
*/
|
|
27
|
-
export function resolveRoute<T extends
|
|
27
|
+
export function resolveRoute<T extends RouteIdWithSearchOrHash | PathnameWithSearchOrHash>(
|
|
28
28
|
...args: ResolveArgs<T>
|
|
29
29
|
): ResolvedPathname;
|
|
@@ -35,7 +35,7 @@ export async function match(url) {
|
|
|
35
35
|
const store = try_get_request_store();
|
|
36
36
|
|
|
37
37
|
if (typeof url === 'string') {
|
|
38
|
-
const origin = store?.event.url.origin ?? '
|
|
38
|
+
const origin = store?.event.url.origin ?? 'a://a';
|
|
39
39
|
url = new URL(url, origin);
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
PathnameWithSearchOrHash,
|
|
3
|
+
RouteId,
|
|
4
|
+
RouteIdWithSearchOrHash,
|
|
5
|
+
RouteParams
|
|
6
|
+
} from '$app/types';
|
|
2
7
|
|
|
3
|
-
|
|
4
|
-
?
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
type StripSearchOrHash<T extends string> = T extends `${infer Pathname}?${string}`
|
|
9
|
+
? Pathname
|
|
10
|
+
: T extends `${infer Pathname}#${string}`
|
|
11
|
+
? Pathname
|
|
12
|
+
: T;
|
|
13
|
+
|
|
14
|
+
export type ResolveArgs<T extends RouteIdWithSearchOrHash | PathnameWithSearchOrHash> =
|
|
15
|
+
T extends RouteId
|
|
16
|
+
? RouteParams<T> extends Record<string, never>
|
|
17
|
+
? [route: T]
|
|
18
|
+
: [route: T, params: RouteParams<T>]
|
|
19
|
+
: StripSearchOrHash<T> extends infer U extends RouteId
|
|
20
|
+
? RouteParams<U> extends Record<string, never>
|
|
21
|
+
? [route: T]
|
|
22
|
+
: [route: T, params: RouteParams<U>]
|
|
23
|
+
: [route: T];
|
|
@@ -11,7 +11,7 @@ function context_dev(name) {
|
|
|
11
11
|
return context();
|
|
12
12
|
} catch {
|
|
13
13
|
throw new Error(
|
|
14
|
-
`Can only read '${name}' on the server during rendering (not in e.g. \`load\` functions), as it is bound to the current request via component context. This prevents state from leaking between users
|
|
14
|
+
`Can only read '${name}' on the server during rendering (not in e.g. \`load\` functions), as it is bound to the current request via component context. This prevents state from leaking between users. ` +
|
|
15
15
|
'For more information, see https://svelte.dev/docs/kit/state-management#avoid-shared-state-on-the-server'
|
|
16
16
|
);
|
|
17
17
|
}
|
|
@@ -56,6 +56,14 @@ export { load_css };
|
|
|
56
56
|
const ICON_REL_ATTRIBUTES = new Set(['icon', 'shortcut icon', 'apple-touch-icon']);
|
|
57
57
|
|
|
58
58
|
let errored = false;
|
|
59
|
+
/**
|
|
60
|
+
* Set via transformError, reset and read at the end of navigate.
|
|
61
|
+
* Necessary because a navigation might succeed loading but during rendering
|
|
62
|
+
* an error occurs, at which point the navigation result needs to be overridden with the error result.
|
|
63
|
+
* TODO this is all very hacky, rethink for SvelteKit 3 where we can assume Svelte 5 and do an overhaul of client.js
|
|
64
|
+
* @type {{ error: App.Error, status: number } | null}
|
|
65
|
+
*/
|
|
66
|
+
let rendering_error = null;
|
|
59
67
|
|
|
60
68
|
// We track the scroll position associated with each history entry in sessionStorage,
|
|
61
69
|
// rather than on history.state itself, because when navigation is driven by
|
|
@@ -238,12 +246,14 @@ const on_navigate_callbacks = new Set();
|
|
|
238
246
|
/** @type {Set<(navigation: import('@sveltejs/kit').AfterNavigate) => void>} */
|
|
239
247
|
const after_navigate_callbacks = new Set();
|
|
240
248
|
|
|
241
|
-
/** @type {import('./types.js').NavigationState} */
|
|
249
|
+
/** @type {import('./types.js').NavigationState & { nav: import('@sveltejs/kit').NavigationEvent }} */
|
|
242
250
|
let current = {
|
|
243
251
|
branch: [],
|
|
244
252
|
error: null,
|
|
245
253
|
// @ts-ignore - we need the initial value to be null
|
|
246
|
-
url: null
|
|
254
|
+
url: null,
|
|
255
|
+
// @ts-ignore - we need the initial value to be null
|
|
256
|
+
nav: null
|
|
247
257
|
};
|
|
248
258
|
|
|
249
259
|
/** this being true means we SSR'd */
|
|
@@ -415,7 +425,7 @@ async function _invalidate(include_load_functions = true, reset_page_state = tru
|
|
|
415
425
|
navigation_result.props.page.state = prev_state;
|
|
416
426
|
}
|
|
417
427
|
update(navigation_result.props.page);
|
|
418
|
-
current = navigation_result.state;
|
|
428
|
+
current = { ...navigation_result.state, nav: current.nav };
|
|
419
429
|
reset_invalidation();
|
|
420
430
|
root.$set(navigation_result.props);
|
|
421
431
|
} else {
|
|
@@ -581,7 +591,17 @@ async function _preload_code(url) {
|
|
|
581
591
|
async function initialize(result, target, hydrate) {
|
|
582
592
|
if (DEV && result.state.error && document.querySelector('vite-error-overlay')) return;
|
|
583
593
|
|
|
584
|
-
|
|
594
|
+
/** @type {import('@sveltejs/kit').NavigationEvent} */
|
|
595
|
+
const nav = {
|
|
596
|
+
params: current.params,
|
|
597
|
+
route: { id: current.route?.id ?? null },
|
|
598
|
+
url: new URL(location.href)
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
current = {
|
|
602
|
+
...result.state,
|
|
603
|
+
nav
|
|
604
|
+
};
|
|
585
605
|
|
|
586
606
|
const style = document.querySelector('style[data-sveltekit]');
|
|
587
607
|
if (style) style.remove();
|
|
@@ -593,7 +613,17 @@ async function initialize(result, target, hydrate) {
|
|
|
593
613
|
props: { ...result.props, stores, components },
|
|
594
614
|
hydrate,
|
|
595
615
|
// @ts-ignore Svelte 5 specific: asynchronously instantiate the component, i.e. don't call flushSync
|
|
596
|
-
sync: false
|
|
616
|
+
sync: false,
|
|
617
|
+
// @ts-ignore Svelte 5 specific: transformError allows to transform errors before they are passed to boundaries
|
|
618
|
+
transformError: __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__
|
|
619
|
+
? /** @param {unknown} e */ async (e) => {
|
|
620
|
+
const error = await handle_error(e, current.nav);
|
|
621
|
+
rendering_error = { error, status: get_status(e) };
|
|
622
|
+
page.error = error;
|
|
623
|
+
page.status = rendering_error.status;
|
|
624
|
+
return error;
|
|
625
|
+
}
|
|
626
|
+
: undefined
|
|
597
627
|
});
|
|
598
628
|
|
|
599
629
|
// Wait for a microtask in case svelte experimental async is enabled,
|
|
@@ -607,9 +637,7 @@ async function initialize(result, target, hydrate) {
|
|
|
607
637
|
const navigation = {
|
|
608
638
|
from: null,
|
|
609
639
|
to: {
|
|
610
|
-
|
|
611
|
-
route: { id: current.route?.id ?? null },
|
|
612
|
-
url: new URL(location.href),
|
|
640
|
+
...nav,
|
|
613
641
|
scroll: scroll_positions[current_history_index] ?? scroll_state()
|
|
614
642
|
},
|
|
615
643
|
willUnload: false,
|
|
@@ -629,13 +657,23 @@ async function initialize(result, target, hydrate) {
|
|
|
629
657
|
* url: URL;
|
|
630
658
|
* params: Record<string, string>;
|
|
631
659
|
* branch: Array<import('./types.js').BranchNode | undefined>;
|
|
660
|
+
* errors?: Array<import('types').CSRPageNodeLoader | undefined>;
|
|
632
661
|
* status: number;
|
|
633
662
|
* error: App.Error | null;
|
|
634
663
|
* route: import('types').CSRRoute | null;
|
|
635
664
|
* form?: Record<string, any> | null;
|
|
636
665
|
* }} opts
|
|
637
666
|
*/
|
|
638
|
-
function get_navigation_result_from_branch({
|
|
667
|
+
async function get_navigation_result_from_branch({
|
|
668
|
+
url,
|
|
669
|
+
params,
|
|
670
|
+
branch,
|
|
671
|
+
errors,
|
|
672
|
+
status,
|
|
673
|
+
error,
|
|
674
|
+
route,
|
|
675
|
+
form
|
|
676
|
+
}) {
|
|
639
677
|
/** @type {import('types').TrailingSlash} */
|
|
640
678
|
let slash = 'never';
|
|
641
679
|
|
|
@@ -670,6 +708,32 @@ function get_navigation_result_from_branch({ url, params, branch, status, error,
|
|
|
670
708
|
}
|
|
671
709
|
};
|
|
672
710
|
|
|
711
|
+
if (errors && __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__) {
|
|
712
|
+
let last_idx = -1;
|
|
713
|
+
result.props.errors = await Promise.all(
|
|
714
|
+
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
715
|
+
branch
|
|
716
|
+
.map((b, i) => {
|
|
717
|
+
if (i === 0) return undefined; // root layout wraps root error component, not the other way around
|
|
718
|
+
if (!b) return null;
|
|
719
|
+
|
|
720
|
+
i--;
|
|
721
|
+
// Find the closest error component up to the previous branch
|
|
722
|
+
while (i > last_idx + 1 && !errors[i]) i -= 1;
|
|
723
|
+
last_idx = i;
|
|
724
|
+
return errors[i]?.()
|
|
725
|
+
.then((e) => e.component)
|
|
726
|
+
.catch(() => undefined);
|
|
727
|
+
})
|
|
728
|
+
// filter out indexes where there was no branch, but keep indexes where there was a branch but no error component
|
|
729
|
+
.filter((e) => e !== null)
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (error && __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__) {
|
|
734
|
+
result.props.error = error;
|
|
735
|
+
}
|
|
736
|
+
|
|
673
737
|
if (form !== undefined) {
|
|
674
738
|
result.props.form = form;
|
|
675
739
|
}
|
|
@@ -1201,6 +1265,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1201
1265
|
url,
|
|
1202
1266
|
params,
|
|
1203
1267
|
branch: branch.slice(0, error_load.idx).concat(error_load.node),
|
|
1268
|
+
errors,
|
|
1204
1269
|
status,
|
|
1205
1270
|
error,
|
|
1206
1271
|
route
|
|
@@ -1220,6 +1285,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
|
|
|
1220
1285
|
url,
|
|
1221
1286
|
params,
|
|
1222
1287
|
branch,
|
|
1288
|
+
errors,
|
|
1223
1289
|
status: 200,
|
|
1224
1290
|
error: null,
|
|
1225
1291
|
route,
|
|
@@ -1325,6 +1391,7 @@ async function load_root_error_page({ status, error, url, route }) {
|
|
|
1325
1391
|
branch: [root_layout, root_error],
|
|
1326
1392
|
status,
|
|
1327
1393
|
error,
|
|
1394
|
+
errors: [],
|
|
1328
1395
|
route: null
|
|
1329
1396
|
});
|
|
1330
1397
|
} catch (error) {
|
|
@@ -1732,7 +1799,16 @@ async function navigate({
|
|
|
1732
1799
|
});
|
|
1733
1800
|
}
|
|
1734
1801
|
|
|
1735
|
-
|
|
1802
|
+
// Type-casts are save because we know this resolved a proper SvelteKit route
|
|
1803
|
+
const target = /** @type {import('@sveltejs/kit').NavigationTarget} */ (nav.navigation.to);
|
|
1804
|
+
current = {
|
|
1805
|
+
...navigation_result.state,
|
|
1806
|
+
nav: {
|
|
1807
|
+
params: /** @type {Record<string, any>} */ (target.params),
|
|
1808
|
+
route: target.route,
|
|
1809
|
+
url: target.url
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1736
1812
|
|
|
1737
1813
|
// reset url before updating page store
|
|
1738
1814
|
if (navigation_result.props.page) {
|
|
@@ -1744,7 +1820,12 @@ async function navigate({
|
|
|
1744
1820
|
if (fork) {
|
|
1745
1821
|
commit_promise = fork.commit();
|
|
1746
1822
|
} else {
|
|
1823
|
+
rendering_error = null; // TODO this can break with forks, rethink for SvelteKit 3 where we can assume Svelte 5
|
|
1747
1824
|
root.$set(navigation_result.props);
|
|
1825
|
+
// Check for sync rendering error
|
|
1826
|
+
if (rendering_error) {
|
|
1827
|
+
Object.assign(navigation_result.props.page, rendering_error);
|
|
1828
|
+
}
|
|
1748
1829
|
update(navigation_result.props.page);
|
|
1749
1830
|
|
|
1750
1831
|
commit_promise = svelte.settled?.();
|
|
@@ -1800,6 +1881,10 @@ async function navigate({
|
|
|
1800
1881
|
autoscroll = true;
|
|
1801
1882
|
|
|
1802
1883
|
if (navigation_result.props.page) {
|
|
1884
|
+
// Check for async rendering error
|
|
1885
|
+
if (rendering_error) {
|
|
1886
|
+
Object.assign(navigation_result.props.page, rendering_error);
|
|
1887
|
+
}
|
|
1803
1888
|
Object.assign(page, navigation_result.props.page);
|
|
1804
1889
|
}
|
|
1805
1890
|
|
|
@@ -2401,16 +2486,17 @@ export async function set_nearest_error_page(error, status = 500) {
|
|
|
2401
2486
|
|
|
2402
2487
|
const error_load = await load_nearest_error_page(current.branch.length, branch, route.errors);
|
|
2403
2488
|
if (error_load) {
|
|
2404
|
-
const navigation_result = get_navigation_result_from_branch({
|
|
2489
|
+
const navigation_result = await get_navigation_result_from_branch({
|
|
2405
2490
|
url,
|
|
2406
2491
|
params: current.params,
|
|
2407
2492
|
branch: branch.slice(0, error_load.idx).concat(error_load.node),
|
|
2408
2493
|
status,
|
|
2409
2494
|
error,
|
|
2495
|
+
// do not set errors, we haven't changed the page so the previous ones are still current
|
|
2410
2496
|
route
|
|
2411
2497
|
});
|
|
2412
2498
|
|
|
2413
|
-
current = navigation_result.state;
|
|
2499
|
+
current = { ...navigation_result.state, nav: current.nav };
|
|
2414
2500
|
|
|
2415
2501
|
root.$set(navigation_result.props);
|
|
2416
2502
|
update(navigation_result.props.page);
|
|
@@ -2829,12 +2915,13 @@ async function _hydrate(
|
|
|
2829
2915
|
}
|
|
2830
2916
|
}
|
|
2831
2917
|
|
|
2832
|
-
result = get_navigation_result_from_branch({
|
|
2918
|
+
result = await get_navigation_result_from_branch({
|
|
2833
2919
|
url,
|
|
2834
2920
|
params,
|
|
2835
2921
|
branch,
|
|
2836
2922
|
status,
|
|
2837
2923
|
error,
|
|
2924
|
+
errors: parsed_route?.errors, // TODO load earlier?
|
|
2838
2925
|
form,
|
|
2839
2926
|
route: parsed_route ?? null
|
|
2840
2927
|
});
|
|
@@ -298,6 +298,14 @@ export function form(id) {
|
|
|
298
298
|
return;
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
const target = event.submitter?.hasAttribute('formtarget')
|
|
302
|
+
? /** @type {HTMLButtonElement | HTMLInputElement} */ (event.submitter).formTarget
|
|
303
|
+
: clone(form).target;
|
|
304
|
+
|
|
305
|
+
if (target === '_blank') {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
301
309
|
event.preventDefault();
|
|
302
310
|
|
|
303
311
|
const form_data = new FormData(form, event.submitter);
|
|
@@ -14,6 +14,8 @@ export let updated;
|
|
|
14
14
|
const is_legacy =
|
|
15
15
|
onMount.toString().includes('$$') || /function \w+\(\) \{\}/.test(onMount.toString());
|
|
16
16
|
|
|
17
|
+
const placeholder_url = 'a:';
|
|
18
|
+
|
|
17
19
|
if (is_legacy) {
|
|
18
20
|
page = {
|
|
19
21
|
data: {},
|
|
@@ -23,7 +25,7 @@ if (is_legacy) {
|
|
|
23
25
|
route: { id: null },
|
|
24
26
|
state: {},
|
|
25
27
|
status: -1,
|
|
26
|
-
url: new URL(
|
|
28
|
+
url: new URL(placeholder_url)
|
|
27
29
|
};
|
|
28
30
|
navigating = { current: null };
|
|
29
31
|
updated = { current: false };
|
|
@@ -36,7 +38,7 @@ if (is_legacy) {
|
|
|
36
38
|
route = $state.raw({ id: null });
|
|
37
39
|
state = $state.raw({});
|
|
38
40
|
status = $state.raw(-1);
|
|
39
|
-
url = $state.raw(new URL(
|
|
41
|
+
url = $state.raw(new URL(placeholder_url));
|
|
40
42
|
})();
|
|
41
43
|
|
|
42
44
|
navigating = new (class Navigating {
|
|
@@ -85,9 +85,11 @@ export type NavigationFinished = {
|
|
|
85
85
|
state: NavigationState;
|
|
86
86
|
props: {
|
|
87
87
|
constructors: Array<typeof SvelteComponent>;
|
|
88
|
+
errors?: Array<typeof SvelteComponent | undefined>;
|
|
88
89
|
components?: SvelteComponent[];
|
|
89
90
|
page: Page;
|
|
90
91
|
form?: Record<string, any> | null;
|
|
92
|
+
error?: App.Error;
|
|
91
93
|
[key: `data_${number}`]: Record<string, any>;
|
|
92
94
|
};
|
|
93
95
|
};
|
|
@@ -285,6 +285,11 @@ export async function render_page(
|
|
|
285
285
|
|
|
286
286
|
const layouts = compact(branch.slice(0, j + 1));
|
|
287
287
|
const nodes = new PageNodes(layouts.map((layout) => layout.node));
|
|
288
|
+
const error_branch = layouts.concat({
|
|
289
|
+
node,
|
|
290
|
+
data: null,
|
|
291
|
+
server_data: null
|
|
292
|
+
});
|
|
288
293
|
|
|
289
294
|
return await render_response({
|
|
290
295
|
event,
|
|
@@ -299,11 +304,14 @@ export async function render_page(
|
|
|
299
304
|
},
|
|
300
305
|
status,
|
|
301
306
|
error,
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
+
error_components: await load_error_components(
|
|
308
|
+
options,
|
|
309
|
+
ssr,
|
|
310
|
+
error_branch,
|
|
311
|
+
page,
|
|
312
|
+
manifest
|
|
313
|
+
),
|
|
314
|
+
branch: error_branch,
|
|
307
315
|
fetched,
|
|
308
316
|
data_serializer
|
|
309
317
|
});
|
|
@@ -350,11 +358,11 @@ export async function render_page(
|
|
|
350
358
|
},
|
|
351
359
|
status,
|
|
352
360
|
error: null,
|
|
353
|
-
branch: ssr
|
|
361
|
+
branch: !ssr ? [] : compact(branch),
|
|
354
362
|
action_result,
|
|
355
363
|
fetched,
|
|
356
|
-
data_serializer:
|
|
357
|
-
|
|
364
|
+
data_serializer: !ssr ? server_data_serializer(event, event_state, options) : data_serializer,
|
|
365
|
+
error_components: await load_error_components(options, ssr, branch, page, manifest)
|
|
358
366
|
});
|
|
359
367
|
} catch (e) {
|
|
360
368
|
// a remote function could have thrown a redirect during render
|
|
@@ -376,3 +384,44 @@ export async function render_page(
|
|
|
376
384
|
});
|
|
377
385
|
}
|
|
378
386
|
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
*
|
|
390
|
+
* @param {import('types').SSROptions} options
|
|
391
|
+
* @param {boolean} ssr
|
|
392
|
+
* @param {Array<import('./types.js').Loaded | null>} branch
|
|
393
|
+
* @param {import('types').PageNodeIndexes} page
|
|
394
|
+
* @param {import('@sveltejs/kit').SSRManifest} manifest
|
|
395
|
+
*/
|
|
396
|
+
async function load_error_components(options, ssr, branch, page, manifest) {
|
|
397
|
+
/** @type {Array<import('types').SSRComponent | undefined> | undefined} */
|
|
398
|
+
let error_components;
|
|
399
|
+
|
|
400
|
+
if (options.server_error_boundaries && ssr) {
|
|
401
|
+
let last_idx = -1;
|
|
402
|
+
error_components = await Promise.all(
|
|
403
|
+
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
404
|
+
branch
|
|
405
|
+
.map((b, i) => {
|
|
406
|
+
if (i === 0) return undefined; // root layout wraps root error component, not the other way around
|
|
407
|
+
if (!b) return null;
|
|
408
|
+
|
|
409
|
+
i--;
|
|
410
|
+
// Find the closest error component up to the previous branch
|
|
411
|
+
while (i > last_idx + 1 && page.errors[i] === undefined) i -= 1;
|
|
412
|
+
last_idx = i;
|
|
413
|
+
|
|
414
|
+
const idx = page.errors[i];
|
|
415
|
+
if (idx == null) return undefined;
|
|
416
|
+
|
|
417
|
+
return manifest._.nodes[idx]?.()
|
|
418
|
+
.then((e) => e.component?.())
|
|
419
|
+
.catch(() => undefined);
|
|
420
|
+
})
|
|
421
|
+
// filter out indexes where there was no branch, but keep indexes where there was a branch but no error component
|
|
422
|
+
.filter((e) => e !== null)
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return error_components;
|
|
427
|
+
}
|
|
@@ -15,8 +15,9 @@ import { create_server_routing_response, generate_route_object } from './server_
|
|
|
15
15
|
import { add_resolution_suffix } from '../../pathname.js';
|
|
16
16
|
import { try_get_request_store, with_request_store } from '@sveltejs/kit/internal/server';
|
|
17
17
|
import { text_encoder } from '../../utils.js';
|
|
18
|
-
import { get_global_name } from '../utils.js';
|
|
18
|
+
import { get_global_name, handle_error_and_jsonify } from '../utils.js';
|
|
19
19
|
import { create_remote_key } from '../../shared.js';
|
|
20
|
+
import { get_status } from '../../../utils/error.js';
|
|
20
21
|
|
|
21
22
|
// TODO rename this function/module
|
|
22
23
|
|
|
@@ -40,7 +41,8 @@ const updated = {
|
|
|
40
41
|
* event_state: import('types').RequestState;
|
|
41
42
|
* resolve_opts: import('types').RequiredResolveOptions;
|
|
42
43
|
* action_result?: import('@sveltejs/kit').ActionResult;
|
|
43
|
-
* data_serializer: import('./types.js').ServerDataSerializer
|
|
44
|
+
* data_serializer: import('./types.js').ServerDataSerializer;
|
|
45
|
+
* error_components?: Array<import('types').SSRComponent | undefined>
|
|
44
46
|
* }} opts
|
|
45
47
|
*/
|
|
46
48
|
export async function render_response({
|
|
@@ -56,7 +58,8 @@ export async function render_response({
|
|
|
56
58
|
event_state,
|
|
57
59
|
resolve_opts,
|
|
58
60
|
action_result,
|
|
59
|
-
data_serializer
|
|
61
|
+
data_serializer,
|
|
62
|
+
error_components
|
|
60
63
|
}) {
|
|
61
64
|
if (state.prerendering) {
|
|
62
65
|
if (options.csp.mode === 'nonce') {
|
|
@@ -147,6 +150,13 @@ export async function render_response({
|
|
|
147
150
|
form: form_value
|
|
148
151
|
};
|
|
149
152
|
|
|
153
|
+
if (error_components) {
|
|
154
|
+
if (error) {
|
|
155
|
+
props.error = error;
|
|
156
|
+
}
|
|
157
|
+
props.errors = error_components;
|
|
158
|
+
}
|
|
159
|
+
|
|
150
160
|
let data = {};
|
|
151
161
|
|
|
152
162
|
// props_n (instead of props[n]) makes it easy to avoid
|
|
@@ -176,7 +186,15 @@ export async function render_response({
|
|
|
176
186
|
}
|
|
177
187
|
]
|
|
178
188
|
]),
|
|
179
|
-
csp: csp.script_needs_nonce ? { nonce: csp.nonce } : { hash: csp.script_needs_hash }
|
|
189
|
+
csp: csp.script_needs_nonce ? { nonce: csp.nonce } : { hash: csp.script_needs_hash },
|
|
190
|
+
transformError: error_components
|
|
191
|
+
? /** @param {unknown} e */ async (e) => {
|
|
192
|
+
const transformed = await handle_error_and_jsonify(event, event_state, options, e);
|
|
193
|
+
props.page.error = props.error = error = transformed;
|
|
194
|
+
props.page.status = status = get_status(e);
|
|
195
|
+
return transformed;
|
|
196
|
+
}
|
|
197
|
+
: undefined
|
|
180
198
|
};
|
|
181
199
|
|
|
182
200
|
const fetch = globalThis.fetch;
|
|
@@ -494,7 +512,30 @@ export async function render_response({
|
|
|
494
512
|
if (!info.id) continue;
|
|
495
513
|
|
|
496
514
|
for (const key in cache) {
|
|
497
|
-
|
|
515
|
+
const remote_key = create_remote_key(info.id, key);
|
|
516
|
+
|
|
517
|
+
if (event_state.refreshes?.[remote_key] !== undefined) {
|
|
518
|
+
// This entry was refreshed/set by a command or form action.
|
|
519
|
+
// Always await it so the mutation result is serialized.
|
|
520
|
+
remote[remote_key] = await cache[key];
|
|
521
|
+
} else {
|
|
522
|
+
// Don't block the response on pending remote data - if a query
|
|
523
|
+
// hasn't settled yet, it wasn't awaited in the template (or is behind a pending boundary).
|
|
524
|
+
const result = await Promise.race([
|
|
525
|
+
Promise.resolve(cache[key]).then(
|
|
526
|
+
(v) => /** @type {const} */ ({ settled: true, value: v }),
|
|
527
|
+
(e) => /** @type {const} */ ({ settled: true, error: e })
|
|
528
|
+
),
|
|
529
|
+
new Promise((resolve) => {
|
|
530
|
+
queueMicrotask(() => resolve(/** @type {const} */ ({ settled: false })));
|
|
531
|
+
})
|
|
532
|
+
]);
|
|
533
|
+
|
|
534
|
+
if (result.settled) {
|
|
535
|
+
if ('error' in result) throw result.error;
|
|
536
|
+
remote[remote_key] = result.value;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
498
539
|
}
|
|
499
540
|
}
|
|
500
541
|
|
|
@@ -6,7 +6,7 @@ import { hash } from '../../../utils/hash.js';
|
|
|
6
6
|
*
|
|
7
7
|
* The first closes the script element, so everything after is treated as raw HTML.
|
|
8
8
|
* The second disables further parsing until `-->`, so the script element might be unexpectedly
|
|
9
|
-
* kept open
|
|
9
|
+
* kept open up until an unrelated HTML comment in the page.
|
|
10
10
|
*
|
|
11
11
|
* U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR are escaped for the sake of pre-2018
|
|
12
12
|
* browsers.
|
package/src/types/ambient.d.ts
CHANGED
|
@@ -104,6 +104,11 @@ declare module '$app/types' {
|
|
|
104
104
|
*/
|
|
105
105
|
export type RouteId = ReturnType<AppTypes['RouteId']>;
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* `RouteId`, but possibly suffixed with a search string and/or hash.
|
|
109
|
+
*/
|
|
110
|
+
export type RouteIdWithSearchOrHash = RouteId | `${RouteId}?${string}` | `${RouteId}#${string}`;
|
|
111
|
+
|
|
107
112
|
/**
|
|
108
113
|
* A utility for getting the parameters associated with a given route.
|
|
109
114
|
*/
|
|
@@ -123,6 +128,14 @@ declare module '$app/types' {
|
|
|
123
128
|
*/
|
|
124
129
|
export type Pathname = ReturnType<AppTypes['Pathname']>;
|
|
125
130
|
|
|
131
|
+
/**
|
|
132
|
+
* `Pathname`, but possibly suffixed with a search string and/or hash.
|
|
133
|
+
*/
|
|
134
|
+
export type PathnameWithSearchOrHash =
|
|
135
|
+
| Pathname
|
|
136
|
+
| `${Pathname}?${string}`
|
|
137
|
+
| `${Pathname}#${string}`;
|
|
138
|
+
|
|
126
139
|
/**
|
|
127
140
|
* `Pathname`, but possibly prefixed with a base path. Used for `page.url.pathname`.
|
|
128
141
|
*/
|
package/src/types/internal.d.ts
CHANGED