@sveltejs/kit 3.0.0-next.13 → 3.0.0-next.16

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.
Files changed (86) hide show
  1. package/package.json +7 -4
  2. package/src/cli.js +8 -5
  3. package/src/core/adapt/builder.js +20 -10
  4. package/src/core/config/index.js +4 -5
  5. package/src/core/env.js +3 -3
  6. package/src/core/generate_manifest/index.js +6 -0
  7. package/src/core/postbuild/entities.js +8 -2
  8. package/src/core/postbuild/fallback.js +2 -1
  9. package/src/core/postbuild/prerender.js +18 -13
  10. package/src/core/sync/create_manifest_data/conflict.js +1 -1
  11. package/src/core/sync/create_manifest_data/index.js +33 -2
  12. package/src/core/sync/sync.js +3 -2
  13. package/src/core/sync/utils.js +2 -2
  14. package/src/core/sync/write_app_types.js +72 -27
  15. package/src/core/sync/write_tsconfig/index.js +73 -54
  16. package/src/core/sync/write_tsconfig/utils.js +0 -61
  17. package/src/core/sync/write_tsconfig/validate.js +128 -0
  18. package/src/core/sync/write_types/index.js +7 -4
  19. package/src/exports/node/index.js +2 -7
  20. package/src/exports/public.d.ts +25 -18
  21. package/src/exports/vite/build/build_server.js +2 -3
  22. package/src/exports/vite/dev/index.js +44 -36
  23. package/src/exports/vite/index.js +91 -16
  24. package/src/exports/vite/utils.js +62 -15
  25. package/src/runner.js +4 -2
  26. package/src/runtime/app/forms.js +4 -7
  27. package/src/runtime/app/internal/transport.js +53 -0
  28. package/src/runtime/app/paths/client.js +2 -2
  29. package/src/runtime/app/paths/internal/client.js +2 -2
  30. package/src/runtime/app/server/remote/prerender.js +6 -10
  31. package/src/runtime/app/server/remote/query.js +3 -9
  32. package/src/runtime/app/server/remote/requested.js +2 -2
  33. package/src/runtime/app/server/remote/shared.js +1 -16
  34. package/src/runtime/client/client.js +177 -63
  35. package/src/runtime/client/fetcher.js +2 -13
  36. package/src/runtime/client/parse.js +1 -1
  37. package/src/runtime/client/remote-functions/command.svelte.js +1 -2
  38. package/src/runtime/client/remote-functions/form.svelte.js +3 -7
  39. package/src/runtime/client/remote-functions/prerender.svelte.js +2 -2
  40. package/src/runtime/client/remote-functions/query/index.js +1 -1
  41. package/src/runtime/client/remote-functions/query/proxy.js +2 -2
  42. package/src/runtime/client/remote-functions/query-batch.svelte.js +1 -1
  43. package/src/runtime/client/remote-functions/query-live/proxy.js +2 -12
  44. package/src/runtime/client/remote-functions/shared.svelte.js +1 -1
  45. package/src/runtime/client/utils.js +1 -0
  46. package/src/runtime/form-utils.js +4 -6
  47. package/src/runtime/pathname.js +44 -1
  48. package/src/runtime/server/data/index.js +5 -8
  49. package/src/runtime/server/dev.js +22 -0
  50. package/src/runtime/server/endpoint.js +3 -4
  51. package/src/runtime/server/fetch.js +25 -30
  52. package/src/runtime/server/index.js +64 -46
  53. package/src/runtime/server/internal.js +12 -5
  54. package/src/runtime/server/page/actions.js +18 -43
  55. package/src/runtime/server/page/crypto.js +2 -2
  56. package/src/runtime/server/page/csp.js +2 -1
  57. package/src/runtime/server/page/data_serializer.js +12 -13
  58. package/src/runtime/server/page/index.js +16 -32
  59. package/src/runtime/server/page/load_data.js +28 -44
  60. package/src/runtime/server/page/render.js +28 -12
  61. package/src/runtime/server/page/respond_with_error.js +8 -20
  62. package/src/runtime/server/page/serialize_data.js +2 -13
  63. package/src/runtime/server/page/server_routing.js +58 -9
  64. package/src/runtime/server/remote-functions.js +11 -15
  65. package/src/runtime/server/respond.js +44 -56
  66. package/src/runtime/server/state.js +60 -0
  67. package/src/runtime/server/utils.js +0 -20
  68. package/src/runtime/shared.js +20 -40
  69. package/src/runtime/utils.js +3 -0
  70. package/src/types/ambient-private.d.ts +1 -1
  71. package/src/types/ambient.d.ts +58 -7
  72. package/src/types/global-private.d.ts +1 -1
  73. package/src/types/internal.d.ts +47 -53
  74. package/src/utils/filesystem.js +1 -23
  75. package/src/utils/hash.js +21 -0
  76. package/src/utils/http.js +8 -7
  77. package/src/utils/import.js +2 -1
  78. package/src/utils/params.js +1 -1
  79. package/src/utils/regex.js +9 -0
  80. package/src/utils/routing.js +52 -27
  81. package/src/utils/url.js +1 -0
  82. package/src/version.js +1 -1
  83. package/types/index.d.ts +98 -28
  84. package/types/index.d.ts.map +1 -1
  85. package/src/exports/node/polyfills.js +0 -30
  86. package/src/runtime/server/app.js +0 -9
@@ -0,0 +1,53 @@
1
+ /** @import { Transport } from '@sveltejs/kit' */
2
+ import * as devalue from 'devalue';
3
+ import { DEV } from 'esm-env';
4
+
5
+ /** @type {(thing: any) => string} */
6
+ export let uneval = () => {
7
+ throw new Error(DEV ? 'called uneval before init_transport' : '');
8
+ };
9
+
10
+ /** @type {(data: any) => string} */
11
+ export let stringify = () => {
12
+ throw new Error(DEV ? 'called stringify before init_transport' : '');
13
+ };
14
+
15
+ /** @type {(data: string) => any} */
16
+ export let parse = () => {
17
+ throw new Error(DEV ? 'called parse before init_transport' : '');
18
+ };
19
+
20
+ /** @type {Record<string, (data: any) => any>} */
21
+ export let encoders = {};
22
+
23
+ /** @type {Record<string, (data: any) => any>} */
24
+ export let decoders = {};
25
+
26
+ export let has_custom_transporters = false;
27
+
28
+ /**
29
+ *
30
+ * @param {Transport} transport
31
+ */
32
+ export function init_transport(transport) {
33
+ const transporters = Object.entries(transport);
34
+
35
+ has_custom_transporters = transporters.length > 0;
36
+
37
+ /** @param {unknown} thing */
38
+ const replacer = (thing) => {
39
+ for (const key of Object.keys(transport)) {
40
+ const encoded = transport[key].encode(thing);
41
+ if (encoded) {
42
+ return `app.decode('${key}', ${devalue.uneval(encoded, replacer)})`;
43
+ }
44
+ }
45
+ };
46
+
47
+ encoders = Object.fromEntries(transporters.map(([k, v]) => [k, v.encode]));
48
+ decoders = Object.fromEntries(transporters.map(([k, v]) => [k, v.decode]));
49
+
50
+ uneval = (data) => devalue.uneval(data, replacer);
51
+ stringify = (data) => devalue.stringify(data, encoders);
52
+ parse = (data) => devalue.parse(data, decoders);
53
+ }
@@ -1,4 +1,4 @@
1
- /** @import { AssetPath, RouteId, RouteIdWithSearchOrHash, Path, PathnameWithSearchOrHash, ResolvedPathname, RouteParams } from '$app/types' */
1
+ /** @import { AssetPath, RouteId, RouteIdWithSearchOrHash, PathnameWithSearchOrHash, ResolvedPathname, RouteParams } from '$app/types' */
2
2
  /** @import { ResolveArgs } from './types.js' */
3
3
  import { base, assets, hash_routing, match_implementation } from './internal/client.js';
4
4
  import { resolve_route } from '../../../utils/routing.js';
@@ -92,7 +92,7 @@ export function resolve(...args) {
92
92
  * ```
93
93
  * @since 2.52.0
94
94
  *
95
- * @param {Path | URL | (string & {})} url
95
+ * @param {URL | string} url
96
96
  * @returns {Promise<{ [K in RouteId]: { id: K; params: RouteParams<K>; } }[RouteId] | null>}
97
97
  */
98
98
  export function match(url) {
@@ -1,4 +1,4 @@
1
- /** @import { RouteId, Path } from '$app/types' */
1
+ /** @import { RouteId } from '$app/types' */
2
2
  import { payload } from '../../../client/payload.js';
3
3
 
4
4
  export const base = payload.base ?? __SVELTEKIT_PATHS_BASE__;
@@ -9,7 +9,7 @@ export const hash_routing = __SVELTEKIT_HASH_ROUTING__;
9
9
  /**
10
10
  * We make this configurable per-environment so that it's possible to import `$app/paths`
11
11
  * into a service worker without importing the entire client
12
- * @param {Path | URL | (string & {})} _url
12
+ * @param {URL | string} _url
13
13
  * @returns {Promise<{ [K in RouteId]: { id: K; params: import('$app/types').RouteParams<K>; } }[RouteId] | null>}
14
14
  */
15
15
  // eslint-disable-next-line @typescript-eslint/require-await
@@ -4,15 +4,11 @@
4
4
  import { json } from '@sveltejs/kit';
5
5
  import { HttpError } from '@sveltejs/kit/internal';
6
6
  import { get_request_store } from '@sveltejs/kit/internal/server';
7
- import { stringify, stringify_remote_arg } from '../../../shared.js';
7
+ import { stringify_remote_arg } from '../../../shared.js';
8
+ import { parse, stringify } from '#app/internal/transport';
8
9
  import { noop } from '../../../../utils/functions.js';
9
10
  import { app_dir, base } from '$app/paths/internal/server';
10
- import {
11
- create_validator,
12
- get_response,
13
- parse_remote_response,
14
- run_remote_function
15
- } from './shared.js';
11
+ import { create_validator, get_response, run_remote_function } from './shared.js';
16
12
 
17
13
  /**
18
14
  * Creates a remote prerender function. When called from the browser, the function will be invoked on the server via a `fetch` call.
@@ -89,7 +85,7 @@ export function prerender(validate_or_fn, fn_or_options, maybe_options) {
89
85
  /** @type {RemotePrerenderFunction<Input, Output> & { __: RemotePrerenderInternals }} */
90
86
  const wrapper = (arg) => {
91
87
  const { event, state } = get_request_store();
92
- const payload = stringify_remote_arg(arg, state.transport);
88
+ const payload = stringify_remote_arg(arg);
93
89
 
94
90
  // `get_response` (as opposed to bare `get_cache`) also registers the call in the
95
91
  // implicit lookup, so that the result is inlined into the page payload (`data.p`)
@@ -121,7 +117,7 @@ export function prerender(validate_or_fn, fn_or_options, maybe_options) {
121
117
  throw new HttpError(prerendered.error);
122
118
  }
123
119
 
124
- return parse_remote_response(prerendered.data, state.transport)._;
120
+ return parse(prerendered.data)._;
125
121
  }
126
122
  }
127
123
 
@@ -146,7 +142,7 @@ export function prerender(validate_or_fn, fn_or_options, maybe_options) {
146
142
  const result = await promise;
147
143
 
148
144
  if (state.prerendering) {
149
- const body = { type: 'result', data: stringify({ _: result }, state.transport) };
145
+ const body = { type: 'result', data: stringify({ _: result }) };
150
146
  state.prerendering.dependencies.set(url, {
151
147
  body: JSON.stringify(body),
152
148
  response: json(body)
@@ -98,7 +98,7 @@ export function query(validate_or_fn, maybe_fn) {
98
98
  }
99
99
 
100
100
  const { event, state } = get_request_store();
101
- const payload = stringify_remote_arg(arg, state.transport);
101
+ const payload = stringify_remote_arg(arg);
102
102
 
103
103
  return create_query_resource(__, payload, event, state, () =>
104
104
  run_remote_function(
@@ -197,7 +197,7 @@ function live(validate_or_fn, maybe_fn) {
197
197
  }
198
198
 
199
199
  const { event, state } = get_request_store();
200
- const payload = stringify_remote_arg(arg, state.transport);
200
+ const payload = stringify_remote_arg(arg);
201
201
 
202
202
  return create_live_query_resource(__, payload, event, state, () =>
203
203
  run(event, state, () => validate(arg))
@@ -376,7 +376,7 @@ function batch(validate_or_fn, maybe_fn) {
376
376
  }
377
377
 
378
378
  const { event, state } = get_request_store();
379
- const payload = stringify_remote_arg(arg, state.transport);
379
+ const payload = stringify_remote_arg(arg);
380
380
 
381
381
  return create_query_resource(__, payload, event, state, () =>
382
382
  // Collect all the calls to the same query in the same macrotask,
@@ -575,12 +575,6 @@ function create_live_query_resource(__, payload, event, state, get_generator) {
575
575
 
576
576
  return Promise.resolve();
577
577
  },
578
- /** @ts-expect-error This method no longer exists */
579
- run() {
580
- throw new Error(
581
- '`.run()` has been removed from live queries. Use `for await (const value of liveQuery())` instead.'
582
- );
583
- },
584
578
  /** @type {Promise<any>['then']} */
585
579
  then(onfulfilled, onrejected) {
586
580
  return get_promise().then(onfulfilled, onrejected);
@@ -163,7 +163,7 @@ export function requested(query, limit) {
163
163
  *[Symbol.iterator]() {
164
164
  for (const payload of selected) {
165
165
  try {
166
- const parsed = parse_remote_arg(payload, state.transport);
166
+ const parsed = parse_remote_arg(payload);
167
167
  const validated = __.validate(parsed);
168
168
 
169
169
  if (is_thenable(validated)) {
@@ -183,7 +183,7 @@ export function requested(query, limit) {
183
183
  async *[Symbol.asyncIterator]() {
184
184
  yield* race_all(selected, async (payload) => {
185
185
  try {
186
- const parsed = parse_remote_arg(payload, state.transport);
186
+ const parsed = parse_remote_arg(payload);
187
187
  const validated = await __.validate(parsed);
188
188
  return { arg: validated, query: __.bind(payload, validated) };
189
189
  } catch (error) {
@@ -1,6 +1,5 @@
1
1
  /** @import { RequestEvent } from '@sveltejs/kit' */
2
- /** @import { ServerHooks, MaybePromise, RequestState, RemoteInternals, RequestStore, RemoteLiveQueryUserFunctionReturnType } from 'types' */
3
- import { parse } from 'devalue';
2
+ /** @import { MaybePromise, RequestState, RemoteInternals, RequestStore, RemoteLiveQueryUserFunctionReturnType } from 'types' */
4
3
  import { error } from '@sveltejs/kit';
5
4
  import { with_request_store, get_request_store } from '@sveltejs/kit/internal/server';
6
5
 
@@ -80,20 +79,6 @@ export async function get_response(internals, payload, state, get_result) {
80
79
  return (cache[payload] ??= get_result());
81
80
  }
82
81
 
83
- /**
84
- * @param {any} data
85
- * @param {ServerHooks['transport']} transport
86
- */
87
- export function parse_remote_response(data, transport) {
88
- /** @type {Record<string, any>} */
89
- const revivers = {};
90
- for (const key in transport) {
91
- revivers[key] = transport[key].decode;
92
- }
93
-
94
- return parse(data, revivers);
95
- }
96
-
97
82
  /**
98
83
  * @param {RequestEvent} event
99
84
  * @param {RequestState} state
@@ -9,7 +9,7 @@ import { settled, tick, fork, onMount, hydrate, mount } from 'svelte';
9
9
  import { HttpError, Redirect, SvelteKitError } from '@sveltejs/kit/internal';
10
10
  import { decode_pathname, strip_hash, make_trackable, normalize_path } from '../../utils/url.js';
11
11
  import { dev_fetch, initial_fetch, lock_fetch, subsequent_fetch, unlock_fetch } from './fetcher.js';
12
- import { parse, parse_server_route } from './parse.js';
12
+ import { parse_routes, parse_server_route } from './parse.js';
13
13
  import * as storage from './session-storage.js';
14
14
  import {
15
15
  find_anchor,
@@ -21,7 +21,7 @@ import {
21
21
  scroll_state,
22
22
  load_css
23
23
  } from './utils.js';
24
- import { base, set_match_implementation } from '$app/paths/internal/client';
24
+ import { base, app_dir, set_match_implementation } from '$app/paths/internal/client';
25
25
  import * as devalue from 'devalue';
26
26
  import {
27
27
  HISTORY_INFO_KEY,
@@ -41,18 +41,23 @@ import {
41
41
  import { get_message, get_status } from '../../utils/error.js';
42
42
  import { page, navigating, updated, notify_version } from './state.svelte.js';
43
43
  import { payload } from './payload.js';
44
- import { add_data_suffix, add_resolution_suffix } from '../pathname.js';
44
+ import {
45
+ add_data_suffix,
46
+ add_resolution_suffix,
47
+ route_id_resolution_pathname
48
+ } from '../pathname.js';
45
49
  import { noop_span } from '../telemetry/noop.js';
46
50
  import { read_ndjson } from './ndjson.js';
47
51
  import Root from '../components/root.svelte';
48
52
  import { Props, RenderNode } from '../props.svelte.js';
53
+ import { init_transport, parse, stringify } from '#app/internal/transport';
49
54
 
50
55
  /**
51
56
  * @typedef {{
52
57
  * historyIndex: number;
53
58
  * navigationIndex: number;
54
59
  * pageUrl?: string;
55
- * state: Record<string, any>;
60
+ * state: string;
56
61
  * persistState: boolean;
57
62
  * resetIndex: number;
58
63
  * }} HistoryMetadata
@@ -294,6 +299,35 @@ function discard_load_cache() {
294
299
  */
295
300
  const reroute_cache = new Map();
296
301
 
302
+ /**
303
+ * Sentinel for a route that exists but has no code to preload, i.e. a `+server.js` with no
304
+ * `+page`. Cached like a parsed route so that repeated `preloadCode(id)` calls for the same
305
+ * id don't re-request it.
306
+ */
307
+ const ENDPOINT_ONLY = Symbol('endpoint only');
308
+
309
+ /**
310
+ * Cache of route ID -> parsed route for server-side route resolution.
311
+ * Populated whenever a server route resolution occurs (`match`, link preloading,
312
+ * hydration), so that `preloadCode(id)` doesn't need an extra server round trip.
313
+ * Lives until full page reload.
314
+ * @type {Map<string, import('types').CSRRoute | typeof ENDPOINT_ONLY>}
315
+ */
316
+ const route_id_cache = new Map();
317
+
318
+ /**
319
+ * Parse a server-provided route and record it in `route_id_cache`, so that a subsequent
320
+ * `preloadCode(id)` for the same route doesn't need another server round trip. Always use
321
+ * this rather than calling `parse_server_route` directly.
322
+ * @param {import('types').CSRRouteServer} server_route
323
+ * @returns {import('types').CSRRoute}
324
+ */
325
+ function parse_and_cache_server_route(server_route) {
326
+ const route = parse_server_route(server_route, app.nodes);
327
+ route_id_cache.set(route.id, route);
328
+ return route;
329
+ }
330
+
297
331
  /**
298
332
  * Note on before_navigate_callbacks, on_navigate_callbacks and after_navigate_callbacks:
299
333
  * do not re-assign as some closures keep references to these Sets
@@ -454,9 +488,11 @@ async function _start(_app, _target, data) {
454
488
 
455
489
  app = _app;
456
490
 
491
+ init_transport(app.hooks.transport ?? {});
492
+
457
493
  await _app.hooks.init?.();
458
494
 
459
- routes = __SVELTEKIT_CLIENT_ROUTING__ ? parse(_app) : [];
495
+ routes = __SVELTEKIT_CLIENT_ROUTING__ ? parse_routes(_app) : [];
460
496
  container = __SVELTEKIT_EMBEDDED__ ? _target : document.documentElement;
461
497
  target = _target;
462
498
 
@@ -497,7 +533,7 @@ async function _start(_app, _target, data) {
497
533
  [HISTORY_METADATA_KEY]: {
498
534
  historyIndex: current_history_index,
499
535
  navigationIndex: current_navigation_index,
500
- state: {},
536
+ state: stringify({}),
501
537
  persistState: false,
502
538
  resetIndex: current_history_index
503
539
  }
@@ -530,7 +566,7 @@ async function _start(_app, _target, data) {
530
566
  type: 'enter',
531
567
  url: resolve_url(app.hash ? decode_hash(new URL(location.href)) : location.href),
532
568
  replace_state: true,
533
- state: history_metadata?.persistState ? history_metadata.state : {},
569
+ state: history_metadata?.persistState ? parse(history_metadata.state) : {},
534
570
  persist_state: history_metadata?.persistState ?? false
535
571
  });
536
572
 
@@ -661,13 +697,13 @@ function persist_state() {
661
697
 
662
698
  /**
663
699
  * @param {string | URL} url
664
- * @param {{ replace?: boolean; reset?: boolean; refreshAll?: boolean; invalidate?: Array<string | URL | ((url: URL) => boolean)>; state?: Record<string, any>; persistState?: boolean }} options
665
- * @param {number} redirect_count
700
+ * @param {{ type?: import('@sveltejs/kit').NavigationType; replace?: boolean; reset?: boolean; refreshAll?: boolean; invalidate?: Array<string | URL | ((url: URL) => boolean)>; state?: Record<string, any>; persistState?: boolean; event?: Event }} [options]
701
+ * @param {number} [redirect_count]
666
702
  * @param {{}} [nav_token]
667
703
  * @param {NavigationIntent | undefined} [intent] navigation intent, when already known by the caller (avoids recomputing it)
668
704
  * @returns {Promise<void>}
669
705
  */
670
- export async function _goto(url, options, redirect_count, nav_token, intent) {
706
+ export async function _goto(url, options = {}, redirect_count = 0, nav_token = {}, intent) {
671
707
  /** @type {Set<string>} */
672
708
  let query_keys;
673
709
  /** @type {Set<string>} */
@@ -680,12 +716,13 @@ export async function _goto(url, options, redirect_count, nav_token, intent) {
680
716
  }
681
717
 
682
718
  await navigate({
683
- type: 'goto',
719
+ type: options.type ?? 'goto',
684
720
  url: resolve_url(url),
685
721
  reset: options.reset,
686
722
  replace_state: options.replace,
687
723
  state: options.state,
688
724
  persist_state: options.persistState,
725
+ event: options.event,
689
726
  redirect_count,
690
727
  nav_token,
691
728
  intent,
@@ -786,6 +823,53 @@ async function _preload_data(intent) {
786
823
  return load_cache.promise;
787
824
  }
788
825
 
826
+ /**
827
+ * Fetch and parse the route with the given ID from the server-side route resolution endpoint.
828
+ * Returns `ENDPOINT_ONLY` if the route exists but has no code to preload, or `undefined` if
829
+ * there is no such route. Only used when `router.resolution === 'server'`.
830
+ * @param {string} id
831
+ * @returns {Promise<import('types').CSRRoute | typeof ENDPOINT_ONLY | undefined>}
832
+ */
833
+ async function load_route_by_id(id) {
834
+ /** @type {{ route?: import('types').CSRRouteServer, endpoint_only?: boolean }} */
835
+ let module;
836
+
837
+ try {
838
+ module = await import(
839
+ /* @vite-ignore */
840
+ base + route_id_resolution_pathname(app_dir, id)
841
+ );
842
+ } catch {
843
+ // if there's no module at that path the response is a 404 (or, on a static
844
+ // host, fallback HTML with the wrong MIME type) and the import rejects —
845
+ // treat it the same as an unknown route rather than surfacing a cryptic error
846
+ return;
847
+ }
848
+
849
+ if (module.endpoint_only) {
850
+ // The route exists, it just has no code to preload.
851
+ route_id_cache.set(id, ENDPOINT_ONLY);
852
+ return ENDPOINT_ONLY;
853
+ }
854
+
855
+ if (!module.route) return;
856
+
857
+ return parse_and_cache_server_route(module.route);
858
+ }
859
+
860
+ /**
861
+ * Import the modules for a route's layout and leaf nodes, without running `load` functions.
862
+ * @param {import('types').CSRRoute} route
863
+ * @returns {Promise<void>}
864
+ */
865
+ async function load_route_nodes(route) {
866
+ await Promise.all(
867
+ /** @type {[has_server_load: boolean, node_loader: import('types').CSRPageNodeLoader][]} */ (
868
+ [...route.layouts, route.leaf].filter(Boolean)
869
+ ).map(([, node_loader]) => node_loader())
870
+ );
871
+ }
872
+
789
873
  /**
790
874
  * @param {URL} url
791
875
  * @returns {Promise<void>}
@@ -794,11 +878,7 @@ async function _preload_code(url) {
794
878
  const route = (await get_navigation_intent(url, false))?.route;
795
879
 
796
880
  if (route) {
797
- await Promise.all(
798
- /** @type {[has_server_load: boolean, node_loader: import('types').CSRPageNodeLoader][]} */ (
799
- [...route.layouts, route.leaf].filter(Boolean)
800
- ).map((load) => load[1]())
801
- );
881
+ await load_route_nodes(route);
802
882
  }
803
883
  }
804
884
 
@@ -1607,7 +1687,7 @@ async function load_root_error_page({ error, url, route }) {
1607
1687
  // client-side navigation if the root layout loader throws a redirect while
1608
1688
  // rendering the default error page
1609
1689
  if (error instanceof Redirect) {
1610
- await _goto(new URL(error.location, location.href), {}, 0);
1690
+ await _goto(new URL(error.location, location.href));
1611
1691
  return;
1612
1692
  }
1613
1693
 
@@ -1729,7 +1809,7 @@ export async function get_navigation_intent(url, invalidating) {
1729
1809
  return {
1730
1810
  id: get_page_key(url),
1731
1811
  invalidating,
1732
- route: parse_server_route(route, app.nodes),
1812
+ route: parse_and_cache_server_route(route),
1733
1813
  params,
1734
1814
  url
1735
1815
  };
@@ -1977,10 +2057,17 @@ async function navigate({
1977
2057
  url.pathname = navigation_result.props.page.url.pathname;
1978
2058
  }
1979
2059
 
1980
- state = popped ? popped.state : state;
1981
-
1982
- if (!popped) {
1983
- // this is a new navigation, rather than a popstate
2060
+ if (popped) {
2061
+ state = popped.state;
2062
+ } else {
2063
+ // we immediately serialize-then-parse to ensure that the value is
2064
+ // serializable, and to prevent the developer from dangerously
2065
+ // relying on the identity of the serialized objects
2066
+ const serialized_state = stringify(state);
2067
+ state = parse(serialized_state);
2068
+
2069
+ // Store the serialized state so the browser history can preserve custom transport values.
2070
+ // This is a new navigation, rather than a popstate.
1984
2071
  const change = replace_state ? 0 : 1;
1985
2072
  if (type !== 'enter') {
1986
2073
  if (reset) current_reset_index += 1;
@@ -1990,7 +2077,7 @@ async function navigate({
1990
2077
  [HISTORY_METADATA_KEY]: /** @satisfies {HistoryMetadata} */ ({
1991
2078
  historyIndex: (current_history_index += change),
1992
2079
  navigationIndex: (current_navigation_index += change),
1993
- state,
2080
+ state: serialized_state,
1994
2081
  persistState: persist_state,
1995
2082
  resetIndex: current_reset_index
1996
2083
  })
@@ -2656,45 +2743,76 @@ export async function preloadData(href) {
2656
2743
  * Programmatically imports the code for routes that haven't yet been fetched.
2657
2744
  * Typically, you might call this to speed up subsequent navigation.
2658
2745
  *
2659
- * You can specify routes by any matching pathname such as `/about` (to match `src/routes/about/+page.svelte`) or `/blog/*` (to match `src/routes/blog/[slug]/+page.svelte`).
2746
+ * Takes a route ID such as `/about` or `/blog/[slug]`. Unlike pathnames, route IDs
2747
+ * are never prefixed with the app's [base path](https://svelte.dev/docs/kit/configuration#paths).
2748
+ * If you have a pathname rather than a route ID, you can convert it with
2749
+ * [`match`](https://svelte.dev/docs/kit/$app-paths#match) from `$app/paths`:
2750
+ *
2751
+ * ```js
2752
+ * import { match } from '$app/paths';
2753
+ * import { preloadCode } from '$app/navigation';
2754
+ *
2755
+ * const matched = await match('/blog/hello-world');
2756
+ * if (matched) await preloadCode(matched.id);
2757
+ * ```
2660
2758
  *
2661
2759
  * Unlike `preloadData`, this won't call `load` functions.
2662
2760
  * Returns a Promise that resolves when the modules have been imported.
2663
2761
  *
2664
- * @param {string} pathname
2762
+ * @param {RouteId} id
2665
2763
  * @returns {Promise<void>}
2666
2764
  */
2667
- export async function preloadCode(pathname) {
2765
+ export async function preloadCode(id) {
2668
2766
  if (!BROWSER) {
2669
2767
  throw new Error('Cannot call preloadCode(...) on the server');
2670
2768
  }
2671
2769
 
2672
- // `current.url` is null until the first navigation/hydration completes, so fall back
2673
- // to `location` to support calling `preloadCode` during initial page load (#13297)
2674
- const url = new URL(pathname, current.url ?? location.href);
2770
+ if (DEV && id[0] !== '/') {
2771
+ throw new Error(
2772
+ `argument passed to preloadCode must be a route ID (i.e. "/blog/[slug]" rather than "blog/[slug]")`
2773
+ );
2774
+ }
2675
2775
 
2676
- if (DEV) {
2677
- if (!pathname.startsWith('/')) {
2678
- throw new Error(
2679
- 'argument passed to preloadCode must be a pathname (i.e. "/about" rather than "http://example.com/about"'
2680
- );
2681
- }
2776
+ const route = __SVELTEKIT_CLIENT_ROUTING__
2777
+ ? routes.find((r) => r.id === id)
2778
+ : (route_id_cache.get(id) ?? (await load_route_by_id(id)));
2682
2779
 
2683
- if (!pathname.startsWith(base)) {
2684
- throw new Error(
2685
- `pathname passed to preloadCode must start with \`paths.base\` (i.e. "${base}${pathname}" rather than "${pathname}")`
2780
+ if (route === ENDPOINT_ONLY) {
2781
+ if (DEV) {
2782
+ console.warn(
2783
+ `'${id}' has no \`+page\`, so there is no code to preload. If you meant to warm up an ` +
2784
+ `endpoint, request it with \`fetch\` instead.`
2686
2785
  );
2687
2786
  }
2688
2787
 
2689
- if (__SVELTEKIT_CLIENT_ROUTING__) {
2690
- const rerouted = await get_rerouted_url(url);
2691
- if (!rerouted || !routes.find((route) => route.exec(get_url_path(rerouted)))) {
2692
- throw new Error(`'${pathname}' did not match any routes`);
2788
+ return;
2789
+ }
2790
+
2791
+ if (!route) {
2792
+ if (DEV) {
2793
+ // warn rather than throw, since under client routing an endpoint-only route id is
2794
+ // indistinguishable from a typo — the client manifest only contains routes with a `+page`
2795
+ let message = `'${id}' did not match any route`;
2796
+
2797
+ if (__SVELTEKIT_CLIENT_ROUTING__) {
2798
+ message += ` (note that routes without a \`+page\` have no code to preload)`;
2799
+
2800
+ // the most common migration mistake is passing a pathname, which used to work
2801
+ const candidates = [id];
2802
+ if (base && id.startsWith(base)) candidates.push(id.slice(base.length) || '/');
2803
+
2804
+ if (candidates.some((path) => routes.some((r) => r.exec(path)))) {
2805
+ message += `. It does match as a pathname — use \`match(...)\` from \`$app/paths\` to convert a pathname into a route ID`;
2806
+ }
2693
2807
  }
2808
+
2809
+ console.warn(message);
2694
2810
  }
2811
+
2812
+ return;
2695
2813
  }
2696
2814
 
2697
- return _preload_code(url);
2815
+ await load_route_nodes(route);
2698
2816
  }
2699
2817
 
2700
2818
  /**
@@ -2766,18 +2884,8 @@ export async function replaceState(url, state) {
2766
2884
  async function update_state(intent, state, { replace, persist_state, reset }, caller) {
2767
2885
  const url = intent.url;
2768
2886
 
2769
- if (DEV) {
2770
- if (!started) {
2771
- throw new Error(`Cannot call ${caller}(...) before router is initialized`);
2772
- }
2773
-
2774
- try {
2775
- // use `devalue.stringify` as a convenient way to ensure we exclude values that can't be properly rehydrated, such as custom class instances
2776
- devalue.stringify(state);
2777
- } catch (error) {
2778
- // @ts-expect-error
2779
- throw new Error(`Could not serialize state${error.path}`, { cause: error });
2780
- }
2887
+ if (DEV && !started) {
2888
+ throw new Error(`Cannot call ${caller}(...) before router is initialized`);
2781
2889
  }
2782
2890
 
2783
2891
  const nav =
@@ -2798,12 +2906,16 @@ async function update_state(intent, state, { replace, persist_state, reset }, ca
2798
2906
  if (!replace) capture_scroll(current_history_index);
2799
2907
  if (reset) current_reset_index += 1;
2800
2908
 
2909
+ // as above, serialize-then-parse to prevent bugs
2910
+ const serialized_state = stringify(state);
2911
+ state = parse(serialized_state);
2912
+
2801
2913
  const entry = {
2802
2914
  [HISTORY_METADATA_KEY]: /** @satisfies {HistoryMetadata} */ ({
2803
2915
  historyIndex: (current_history_index += replace ? 0 : 1),
2804
2916
  navigationIndex: current_navigation_index,
2805
2917
  pageUrl: page.url.href,
2806
- state,
2918
+ state: serialized_state,
2807
2919
  persistState: persist_state,
2808
2920
  resetIndex: current_reset_index
2809
2921
  })
@@ -2892,7 +3004,7 @@ export async function applyAction(result) {
2892
3004
  if (result.type === 'error') {
2893
3005
  await set_nearest_error_page(result.error);
2894
3006
  } else if (result.type === 'redirect') {
2895
- await _goto(result.location, { refreshAll: true }, 0);
3007
+ await _goto(result.location, { refreshAll: true });
2896
3008
  } else {
2897
3009
  page.form = result.data;
2898
3010
  page.status = result.status;
@@ -3108,11 +3220,13 @@ function _start_router() {
3108
3220
  setTimeout(fulfil, 100); // fallback for edge case where rAF doesn't fire because e.g. tab was backgrounded
3109
3221
  });
3110
3222
 
3111
- await navigate({
3223
+ const changed = url.href !== location.href;
3224
+
3225
+ await _goto(url, {
3112
3226
  type: 'link',
3113
- url,
3114
3227
  reset: options.reset,
3115
- replace_state: options.replace_state ?? url.href === location.href,
3228
+ replace: options.replace_state ?? !changed,
3229
+ refreshAll: !changed,
3116
3230
  event
3117
3231
  });
3118
3232
  });
@@ -3181,7 +3295,7 @@ function _start_router() {
3181
3295
  const reset_index = history_metadata.resetIndex;
3182
3296
  const reset = reset_index !== (source_info?.resetIndex ?? current_reset_index);
3183
3297
  const scroll = history_info[history_index]?.scroll;
3184
- const state = history_metadata.state;
3298
+ const state = parse(history_metadata.state);
3185
3299
  const url = new URL(history_metadata.pageUrl ?? location.href);
3186
3300
  const navigation_index = history_metadata.navigationIndex;
3187
3301
  const is_hash_change =
@@ -3343,7 +3457,7 @@ async function _hydrate(
3343
3457
  } else {
3344
3458
  // undefined in case of 404
3345
3459
  if (server_route) {
3346
- parsed_route = route = parse_server_route(server_route, app.nodes);
3460
+ parsed_route = route = parse_and_cache_server_route(server_route);
3347
3461
  } else {
3348
3462
  route = { id: null };
3349
3463
  params = {};
@@ -3427,7 +3541,7 @@ async function _hydrate(
3427
3541
 
3428
3542
  if (result.props.page) {
3429
3543
  const history_metadata = get_history_metadata();
3430
- result.props.page.state = history_metadata?.persistState ? history_metadata.state : {};
3544
+ result.props.page.state = history_metadata?.persistState ? parse(history_metadata.state) : {};
3431
3545
  }
3432
3546
 
3433
3547
  await initialize(result, target, should_hydrate);