@sveltejs/kit 2.17.3 → 2.19.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "2.17.3",
3
+ "version": "2.19.0",
4
4
  "description": "SvelteKit is the fastest way to build Svelte apps",
5
5
  "keywords": [
6
6
  "framework",
@@ -36,7 +36,7 @@
36
36
  "@types/connect": "^3.4.38",
37
37
  "@types/node": "^18.19.48",
38
38
  "@types/set-cookie-parser": "^2.4.7",
39
- "dts-buddy": "^0.5.4",
39
+ "dts-buddy": "^0.5.5",
40
40
  "rollup": "^4.14.2",
41
41
  "svelte": "^5.2.9",
42
42
  "svelte-preprocess": "^6.0.0",
@@ -41,6 +41,7 @@ export function find_server_assets(build_data, routes) {
41
41
 
42
42
  for (const n of used_nodes) {
43
43
  const node = build_data.manifest_data.nodes[n];
44
+ if (node?.universal) add_assets(node.universal);
44
45
  if (node?.server) add_assets(node.server);
45
46
  }
46
47
 
@@ -27,12 +27,10 @@ export function generate_manifest({ build_data, prerendered, relative_path, rout
27
27
  const reindexed = new Map();
28
28
  /**
29
29
  * All nodes actually used in the routes definition (prerendered routes are omitted).
30
- * If `routes` is empty, it means that this manifest is only used for server-side resolution
31
- * and the root layout/error is therefore not needed.
32
- * Else, root layout/error is always included as they are needed for 404 and root errors.
30
+ * Root layout/error is always included as they are needed for 404 and root errors.
33
31
  * @type {Set<any>}
34
32
  */
35
- const used_nodes = new Set(routes.length > 0 ? [0, 1] : []);
33
+ const used_nodes = new Set([0, 1]);
36
34
 
37
35
  const server_assets = find_server_assets(build_data, routes);
38
36
 
@@ -1,22 +1,15 @@
1
1
  import { join } from 'node:path';
2
2
  import { pathToFileURL } from 'node:url';
3
- import { get_option } from '../../utils/options.js';
4
- import {
5
- validate_layout_exports,
6
- validate_layout_server_exports,
7
- validate_page_exports,
8
- validate_page_server_exports,
9
- validate_server_exports
10
- } from '../../utils/exports.js';
3
+ import { validate_server_exports } from '../../utils/exports.js';
11
4
  import { load_config } from '../config/index.js';
12
5
  import { forked } from '../../utils/fork.js';
13
6
  import { installPolyfills } from '../../exports/node/polyfills.js';
14
7
  import { ENDPOINT_METHODS } from '../../constants.js';
15
8
  import { filter_private_env, filter_public_env } from '../../utils/env.js';
16
9
  import { has_server_load, resolve_route } from '../../utils/routing.js';
17
- import { get_page_config } from '../../utils/route_config.js';
18
10
  import { check_feature } from '../../utils/features.js';
19
11
  import { createReadableStream } from '@sveltejs/kit/node';
12
+ import { PageNodes } from '../../utils/page_nodes.js';
20
13
 
21
14
  export default forked(import.meta.url, analyse);
22
15
 
@@ -190,25 +183,18 @@ function analyse_endpoint(route, mod) {
190
183
  * @param {import('types').SSRNode} leaf
191
184
  */
192
185
  function analyse_page(layouts, leaf) {
193
- for (const layout of layouts) {
194
- if (layout) {
195
- validate_layout_server_exports(layout.server, layout.server_id);
196
- validate_layout_exports(layout.universal, layout.universal_id);
197
- }
198
- }
199
-
200
186
  /** @type {Array<'GET' | 'POST'>} */
201
187
  const methods = ['GET'];
202
188
  if (leaf.server?.actions) methods.push('POST');
203
189
 
204
- validate_page_server_exports(leaf.server, leaf.server_id);
205
- validate_page_exports(leaf.universal, leaf.universal_id);
190
+ const nodes = new PageNodes([...layouts, leaf]);
191
+ nodes.validate();
206
192
 
207
193
  return {
208
- config: get_page_config([...layouts, leaf]),
194
+ config: nodes.get_config(),
209
195
  entries: leaf.universal?.entries ?? leaf.server?.entries,
210
196
  methods,
211
- prerender: get_option([...layouts, leaf], 'prerender') ?? false
197
+ prerender: nodes.prerender()
212
198
  };
213
199
  }
214
200
 
@@ -1,5 +1,13 @@
1
1
  import { HttpError, Redirect, ActionFailure } from '../runtime/control.js';
2
2
  import { BROWSER, DEV } from 'esm-env';
3
+ import {
4
+ add_data_suffix,
5
+ add_resolution_suffix,
6
+ has_data_suffix,
7
+ has_resolution_suffix,
8
+ strip_data_suffix,
9
+ strip_resolution_suffix
10
+ } from '../runtime/pathname.js';
3
11
 
4
12
  export { VERSION } from '../version.js';
5
13
 
@@ -207,3 +215,50 @@ export function fail(status, data) {
207
215
  export function isActionFailure(e) {
208
216
  return e instanceof ActionFailure;
209
217
  }
218
+
219
+ /**
220
+ * Strips possible SvelteKit-internal suffixes and trailing slashes from the URL pathname.
221
+ * Returns the normalized URL as well as a method for adding the potential suffix back
222
+ * based on a new pathname (possibly including search) or URL.
223
+ * ```js
224
+ * import { normalizeUrl } from '@sveltejs/kit';
225
+ *
226
+ * const { url, denormalize } = normalizeUrl('/blog/post/__data.json');
227
+ * console.log(url.pathname); // /blog/post
228
+ * console.log(denormalize('/blog/post/a')); // /blog/post/a/__data.json
229
+ * ```
230
+ * @param {URL | string} url
231
+ * @returns {{ url: URL, wasNormalized: boolean, denormalize: (url?: string | URL) => URL }}
232
+ * @since 2.18.0
233
+ */
234
+ export function normalizeUrl(url) {
235
+ url = new URL(url, 'http://internal');
236
+
237
+ const is_route_resolution = has_resolution_suffix(url.pathname);
238
+ const is_data_request = has_data_suffix(url.pathname);
239
+ const has_trailing_slash = url.pathname !== '/' && url.pathname.endsWith('/');
240
+
241
+ if (is_route_resolution) {
242
+ url.pathname = strip_resolution_suffix(url.pathname);
243
+ } else if (is_data_request) {
244
+ url.pathname = strip_data_suffix(url.pathname);
245
+ } else if (has_trailing_slash) {
246
+ url.pathname = url.pathname.slice(0, -1);
247
+ }
248
+
249
+ return {
250
+ url,
251
+ wasNormalized: is_data_request || is_route_resolution || has_trailing_slash,
252
+ denormalize: (new_url = url) => {
253
+ new_url = new URL(new_url, url);
254
+ if (is_route_resolution) {
255
+ new_url.pathname = add_resolution_suffix(new_url.pathname);
256
+ } else if (is_data_request) {
257
+ new_url.pathname = add_data_suffix(new_url.pathname);
258
+ } else if (has_trailing_slash && !new_url.pathname.endsWith('/')) {
259
+ new_url.pathname += '/';
260
+ }
261
+ return new_url;
262
+ }
263
+ };
264
+ }
@@ -814,7 +814,7 @@ export type ClientInit = () => MaybePromise<void>;
814
814
  * The [`reroute`](https://svelte.dev/docs/kit/hooks#Universal-hooks-reroute) hook allows you to modify the URL before it is used to determine which route to render.
815
815
  * @since 2.3.0
816
816
  */
817
- export type Reroute = (event: { url: URL }) => void | string;
817
+ export type Reroute = (event: { url: URL; fetch: typeof fetch }) => MaybePromise<void | string>;
818
818
 
819
819
  /**
820
820
  * The [`transport`](https://svelte.dev/docs/kit/hooks#Universal-hooks-transport) hook allows you to transport custom types across the server/client boundary.
@@ -1177,7 +1177,7 @@ export interface RequestEvent<
1177
1177
  * - During server-side rendering, the response will be captured and inlined into the rendered HTML by hooking into the `text` and `json` methods of the `Response` object. Note that headers will _not_ be serialized, unless explicitly included via [`filterSerializedResponseHeaders`](https://svelte.dev/docs/kit/hooks#Server-hooks-handle)
1178
1178
  * - During hydration, the response will be read from the HTML, guaranteeing consistency and preventing an additional network request.
1179
1179
  *
1180
- * You can learn more about making credentialed requests with cookies [here](https://svelte.dev/docs/kit/load#Cookies)
1180
+ * You can learn more about making credentialed requests with cookies [here](https://svelte.dev/docs/kit/load#Cookies).
1181
1181
  */
1182
1182
  fetch: typeof fetch;
1183
1183
  /**
@@ -1189,7 +1189,7 @@ export interface RequestEvent<
1189
1189
  */
1190
1190
  locals: App.Locals;
1191
1191
  /**
1192
- * The parameters of the current route - e.g. for a route like `/blog/[slug]`, a `{ slug: string }` object
1192
+ * The parameters of the current route - e.g. for a route like `/blog/[slug]`, a `{ slug: string }` object.
1193
1193
  */
1194
1194
  params: Params;
1195
1195
  /**
@@ -1197,15 +1197,15 @@ export interface RequestEvent<
1197
1197
  */
1198
1198
  platform: Readonly<App.Platform> | undefined;
1199
1199
  /**
1200
- * The original request object
1200
+ * The original request object.
1201
1201
  */
1202
1202
  request: Request;
1203
1203
  /**
1204
- * Info about the current route
1204
+ * Info about the current route.
1205
1205
  */
1206
1206
  route: {
1207
1207
  /**
1208
- * The ID of the current route - e.g. for `src/routes/blog/[slug]`, it would be `/blog/[slug]`
1208
+ * The ID of the current route - e.g. for `src/routes/blog/[slug]`, it would be `/blog/[slug]`.
1209
1209
  */
1210
1210
  id: RouteId;
1211
1211
  };
@@ -1302,15 +1302,16 @@ export class Server {
1302
1302
  }
1303
1303
 
1304
1304
  export interface ServerInitOptions {
1305
- /** A map of environment variables */
1305
+ /** A map of environment variables. */
1306
1306
  env: Record<string, string>;
1307
- /** A function that turns an asset filename into a `ReadableStream`. Required for the `read` export from `$app/server` to work */
1307
+ /** A function that turns an asset filename into a `ReadableStream`. Required for the `read` export from `$app/server` to work. */
1308
1308
  read?: (file: string) => ReadableStream;
1309
1309
  }
1310
1310
 
1311
1311
  export interface SSRManifest {
1312
1312
  appDir: string;
1313
1313
  appPath: string;
1314
+ /** Static files from `kit.config.files.assets` and the service worker (if any). */
1314
1315
  assets: Set<string>;
1315
1316
  mimeTypes: Record<string, string>;
1316
1317
 
@@ -1321,7 +1322,7 @@ export interface SSRManifest {
1321
1322
  routes: SSRRoute[];
1322
1323
  prerendered_routes: Set<string>;
1323
1324
  matchers: () => Promise<Record<string, ParamMatcher>>;
1324
- /** A `[file]: size` map of all assets imported by server code */
1325
+ /** A `[file]: size` map of all assets imported by server code. */
1325
1326
  server_assets: Record<string, number>;
1326
1327
  };
1327
1328
  }
@@ -1452,7 +1453,7 @@ export interface HttpError {
1452
1453
  }
1453
1454
 
1454
1455
  /**
1455
- * The object returned by the [`redirect`](https://svelte.dev/docs/kit/@sveltejs-kit#redirect) function
1456
+ * The object returned by the [`redirect`](https://svelte.dev/docs/kit/@sveltejs-kit#redirect) function.
1456
1457
  */
1457
1458
  export interface Redirect {
1458
1459
  /** The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages), in the range 300-308. */
@@ -177,17 +177,18 @@ export async function dev(vite, vite_config, svelte_config) {
177
177
  return async () => {
178
178
  /** @type {import('types').SSRNode} */
179
179
  const result = {};
180
-
181
- /** @type {import('vite').ModuleNode[]} */
182
- const module_nodes = [];
183
-
184
180
  result.index = index;
181
+ result.universal_id = node.universal;
182
+ result.server_id = node.server;
185
183
 
186
- // these are unused in dev, it's easier to include them
184
+ // these are unused in dev, but it's easier to include them
187
185
  result.imports = [];
188
186
  result.stylesheets = [];
189
187
  result.fonts = [];
190
188
 
189
+ /** @type {import('vite').ModuleNode[]} */
190
+ const module_nodes = [];
191
+
191
192
  if (node.component) {
192
193
  result.component = async () => {
193
194
  const { module_node, module } = await resolve(
@@ -202,17 +203,13 @@ export async function dev(vite, vite_config, svelte_config) {
202
203
 
203
204
  if (node.universal) {
204
205
  const { module, module_node } = await resolve(node.universal);
205
-
206
206
  module_nodes.push(module_node);
207
-
208
207
  result.universal = module;
209
- result.universal_id = node.universal;
210
208
  }
211
209
 
212
210
  if (node.server) {
213
211
  const { module } = await resolve(node.server);
214
212
  result.server = module;
215
- result.server_id = node.server;
216
213
  }
217
214
 
218
215
  // in dev we inline all styles to avoid FOUC. this gets populated lazily so that
@@ -188,6 +188,17 @@ const components = [];
188
188
  /** @type {{id: string, token: {}, promise: Promise<import('./types.js').NavigationResult>} | null} */
189
189
  let load_cache = null;
190
190
 
191
+ /**
192
+ * @type {Map<string, Promise<URL>>}
193
+ * Cache for client-side rerouting, since it could contain async calls which we want to
194
+ * avoid running multiple times which would slow down navigations (e.g. else preloading
195
+ * wouldn't help because on navigation it would be called again). Since `reroute` should be
196
+ * a pure function (i.e. always return the same) value it's safe to cache across navigations.
197
+ * The server reroute calls don't need to be cached because they are called using `import(...)`
198
+ * which is cached per the JS spec.
199
+ */
200
+ const reroute_cache = new Map();
201
+
191
202
  /**
192
203
  * Note on before_navigate_callbacks, on_navigate_callbacks and after_navigate_callbacks:
193
204
  * do not re-assign as some closures keep references to these Sets
@@ -679,12 +690,7 @@ async function load_node({ loader, parent, url, params, route, server_data_node
679
690
  app.hash
680
691
  ),
681
692
  async fetch(resource, init) {
682
- /** @type {URL | string} */
683
- let requested;
684
-
685
693
  if (resource instanceof Request) {
686
- requested = resource.url;
687
-
688
694
  // we're not allowed to modify the received `Request` object, so in order
689
695
  // to fixup relative urls we create a new equivalent `init` object instead
690
696
  init = {
@@ -709,25 +715,15 @@ async function load_node({ loader, parent, url, params, route, server_data_node
709
715
  signal: resource.signal,
710
716
  ...init
711
717
  };
712
- } else {
713
- requested = resource;
714
718
  }
715
719
 
716
- // we must fixup relative urls so they are resolved from the target page
717
- const resolved = new URL(requested, url);
720
+ const { resolved, promise } = resolve_fetch_url(resource, init, url);
721
+
718
722
  if (is_tracking) {
719
723
  depends(resolved.href);
720
724
  }
721
725
 
722
- // match ssr serialized data url, which is important to find cached responses
723
- if (resolved.origin === url.origin) {
724
- requested = resolved.href.slice(url.origin.length);
725
- }
726
-
727
- // prerendered pages may be served from any origin, so `initial_fetch` urls shouldn't be resolved
728
- return started
729
- ? subsequent_fetch(requested, resolved.href, init)
730
- : initial_fetch(requested, init);
726
+ return promise;
731
727
  },
732
728
  setHeaders: () => {}, // noop
733
729
  depends,
@@ -782,6 +778,30 @@ async function load_node({ loader, parent, url, params, route, server_data_node
782
778
  };
783
779
  }
784
780
 
781
+ /**
782
+ * @param {Request | string | URL} input
783
+ * @param {RequestInit | undefined} init
784
+ * @param {URL} url
785
+ */
786
+ function resolve_fetch_url(input, init, url) {
787
+ let requested = input instanceof Request ? input.url : input;
788
+
789
+ // we must fixup relative urls so they are resolved from the target page
790
+ const resolved = new URL(requested, url);
791
+
792
+ // match ssr serialized data url, which is important to find cached responses
793
+ if (resolved.origin === url.origin) {
794
+ requested = resolved.href.slice(url.origin.length);
795
+ }
796
+
797
+ // prerendered pages may be served from any origin, so `initial_fetch` urls shouldn't be resolved
798
+ const promise = started
799
+ ? subsequent_fetch(requested, resolved.href, init)
800
+ : initial_fetch(requested, init);
801
+
802
+ return { resolved, promise };
803
+ }
804
+
785
805
  /**
786
806
  * @param {boolean} parent_changed
787
807
  * @param {boolean} route_changed
@@ -1198,26 +1218,47 @@ async function load_root_error_page({ status, error, url, route }) {
1198
1218
  /**
1199
1219
  * Resolve the relative rerouted URL for a client-side navigation
1200
1220
  * @param {URL} url
1201
- * @returns {URL | undefined}
1221
+ * @returns {Promise<URL | undefined>}
1202
1222
  */
1203
- function get_rerouted_url(url) {
1204
- // reroute could alter the given URL, so we pass a copy
1223
+ async function get_rerouted_url(url) {
1224
+ const href = url.href;
1225
+
1226
+ if (reroute_cache.has(href)) {
1227
+ return reroute_cache.get(href);
1228
+ }
1229
+
1205
1230
  let rerouted;
1231
+
1206
1232
  try {
1207
- rerouted = app.hooks.reroute({ url: new URL(url) }) ?? url;
1233
+ const promise = (async () => {
1234
+ // reroute could alter the given URL, so we pass a copy
1235
+ let rerouted =
1236
+ (await app.hooks.reroute({
1237
+ url: new URL(url),
1238
+ fetch: async (input, init) => {
1239
+ return resolve_fetch_url(input, init, url).promise;
1240
+ }
1241
+ })) ?? url;
1208
1242
 
1209
- if (typeof rerouted === 'string') {
1210
- const tmp = new URL(url); // do not mutate the incoming URL
1243
+ if (typeof rerouted === 'string') {
1244
+ const tmp = new URL(url); // do not mutate the incoming URL
1211
1245
 
1212
- if (app.hash) {
1213
- tmp.hash = rerouted;
1214
- } else {
1215
- tmp.pathname = rerouted;
1246
+ if (app.hash) {
1247
+ tmp.hash = rerouted;
1248
+ } else {
1249
+ tmp.pathname = rerouted;
1250
+ }
1251
+
1252
+ rerouted = tmp;
1216
1253
  }
1217
1254
 
1218
- rerouted = tmp;
1219
- }
1255
+ return rerouted;
1256
+ })();
1257
+
1258
+ reroute_cache.set(href, promise);
1259
+ rerouted = await promise;
1220
1260
  } catch (e) {
1261
+ reroute_cache.delete(href);
1221
1262
  if (DEV) {
1222
1263
  // in development, print the error...
1223
1264
  console.error(e);
@@ -1246,7 +1287,7 @@ async function get_navigation_intent(url, invalidating) {
1246
1287
  if (is_external_url(url, base, app.hash)) return;
1247
1288
 
1248
1289
  if (__SVELTEKIT_CLIENT_ROUTING__) {
1249
- const rerouted = get_rerouted_url(url);
1290
+ const rerouted = await get_rerouted_url(url);
1250
1291
  if (!rerouted) return;
1251
1292
 
1252
1293
  const path = get_url_path(rerouted);
@@ -1636,25 +1677,29 @@ if (import.meta.hot) {
1636
1677
  });
1637
1678
  }
1638
1679
 
1680
+ /** @typedef {(typeof PRELOAD_PRIORITIES)['hover'] | (typeof PRELOAD_PRIORITIES)['tap']} PreloadDataPriority */
1681
+
1639
1682
  function setup_preload() {
1640
1683
  /** @type {NodeJS.Timeout} */
1641
1684
  let mousemove_timeout;
1642
1685
  /** @type {Element} */
1643
1686
  let current_a;
1687
+ /** @type {PreloadDataPriority} */
1688
+ let current_priority;
1644
1689
 
1645
1690
  container.addEventListener('mousemove', (event) => {
1646
1691
  const target = /** @type {Element} */ (event.target);
1647
1692
 
1648
1693
  clearTimeout(mousemove_timeout);
1649
1694
  mousemove_timeout = setTimeout(() => {
1650
- void preload(target, 2);
1695
+ void preload(target, PRELOAD_PRIORITIES.hover);
1651
1696
  }, 20);
1652
1697
  });
1653
1698
 
1654
1699
  /** @param {Event} event */
1655
1700
  function tap(event) {
1656
1701
  if (event.defaultPrevented) return;
1657
- void preload(/** @type {Element} */ (event.composedPath()[0]), 1);
1702
+ void preload(/** @type {Element} */ (event.composedPath()[0]), PRELOAD_PRIORITIES.tap);
1658
1703
  }
1659
1704
 
1660
1705
  container.addEventListener('mousedown', tap);
@@ -1674,11 +1719,14 @@ function setup_preload() {
1674
1719
 
1675
1720
  /**
1676
1721
  * @param {Element} element
1677
- * @param {number} priority
1722
+ * @param {PreloadDataPriority} priority
1678
1723
  */
1679
1724
  async function preload(element, priority) {
1680
1725
  const a = find_anchor(element, container);
1681
- if (!a || a === current_a) return;
1726
+
1727
+ // we don't want to preload data again if the user has already hovered/tapped
1728
+ const interacted = a === current_a && priority >= current_priority;
1729
+ if (!a || interacted) return;
1682
1730
 
1683
1731
  const { url, external, download } = get_link_info(a, base, app.hash);
1684
1732
  if (external || download) return;
@@ -1687,31 +1735,34 @@ function setup_preload() {
1687
1735
 
1688
1736
  // we don't want to preload data for a page we're already on
1689
1737
  const same_url = url && get_page_key(current.url) === get_page_key(url);
1690
-
1691
- if (!options.reload && !same_url) {
1692
- if (priority <= options.preload_data) {
1693
- current_a = a;
1694
- const intent = await get_navigation_intent(url, false);
1695
- if (intent) {
1696
- if (DEV) {
1697
- void _preload_data(intent).then((result) => {
1698
- if (result.type === 'loaded' && result.state.error) {
1699
- console.warn(
1700
- `Preloading data for ${intent.url.pathname} failed with the following error: ${result.state.error.message}\n` +
1701
- 'If this error is transient, you can ignore it. Otherwise, consider disabling preloading for this route. ' +
1702
- 'This route was preloaded due to a data-sveltekit-preload-data attribute. ' +
1703
- 'See https://svelte.dev/docs/kit/link-options for more info'
1704
- );
1705
- }
1706
- });
1707
- } else {
1708
- void _preload_data(intent);
1738
+ if (options.reload || same_url) return;
1739
+
1740
+ if (priority <= options.preload_data) {
1741
+ current_a = a;
1742
+ // we don't want to preload data again on tap if we've already preloaded it on hover
1743
+ current_priority = PRELOAD_PRIORITIES.tap;
1744
+
1745
+ const intent = await get_navigation_intent(url, false);
1746
+ if (!intent) return;
1747
+
1748
+ if (DEV) {
1749
+ void _preload_data(intent).then((result) => {
1750
+ if (result.type === 'loaded' && result.state.error) {
1751
+ console.warn(
1752
+ `Preloading data for ${intent.url.pathname} failed with the following error: ${result.state.error.message}\n` +
1753
+ 'If this error is transient, you can ignore it. Otherwise, consider disabling preloading for this route. ' +
1754
+ 'This route was preloaded due to a data-sveltekit-preload-data attribute. ' +
1755
+ 'See https://svelte.dev/docs/kit/link-options for more info'
1756
+ );
1709
1757
  }
1710
- }
1711
- } else if (priority <= options.preload_code) {
1712
- current_a = a;
1713
- void _preload_code(/** @type {URL} */ (url));
1758
+ });
1759
+ } else {
1760
+ void _preload_data(intent);
1714
1761
  }
1762
+ } else if (priority <= options.preload_code) {
1763
+ current_a = a;
1764
+ current_priority = priority;
1765
+ void _preload_code(/** @type {URL} */ (url));
1715
1766
  }
1716
1767
  }
1717
1768
 
@@ -1997,7 +2048,7 @@ export async function preloadCode(pathname) {
1997
2048
  }
1998
2049
 
1999
2050
  if (__SVELTEKIT_CLIENT_ROUTING__) {
2000
- const rerouted = get_rerouted_url(url);
2051
+ const rerouted = await get_rerouted_url(url);
2001
2052
  if (!rerouted || !routes.find((route) => route.exec(get_url_path(rerouted)))) {
2002
2053
  throw new Error(`'${pathname}' did not match any routes`);
2003
2054
  }
@@ -2435,6 +2486,12 @@ function _start_router() {
2435
2486
  if (!hash_navigating) {
2436
2487
  const url = new URL(location.href);
2437
2488
  update_url(url);
2489
+
2490
+ // if the user edits the hash via the browser URL bar, trigger a full-page
2491
+ // reload to align with pathname router behavior
2492
+ if (app.hash) {
2493
+ location.reload();
2494
+ }
2438
2495
  }
2439
2496
  }
2440
2497
  });
@@ -2453,13 +2510,6 @@ function _start_router() {
2453
2510
  '',
2454
2511
  location.href
2455
2512
  );
2456
- } else if (app.hash) {
2457
- // If the user edits the hash via the browser URL bar, it
2458
- // (surprisingly!) mutates `current.url`, allowing us to
2459
- // detect it and trigger a navigation
2460
- if (current.url.hash === location.hash) {
2461
- void navigate({ type: 'goto', url: decode_hash(current.url) });
2462
- }
2463
2513
  }
2464
2514
  });
2465
2515
 
@@ -29,13 +29,13 @@ function validate_options(options) {
29
29
  /**
30
30
  * @param {Request} request
31
31
  * @param {URL} url
32
- * @param {import('types').TrailingSlash} trailing_slash
33
32
  */
34
- export function get_cookies(request, url, trailing_slash) {
33
+ export function get_cookies(request, url) {
35
34
  const header = request.headers.get('cookie') ?? '';
36
35
  const initial_cookies = parse(header, { decode: (value) => value });
37
36
 
38
- const normalized_url = normalize_path(url.pathname, trailing_slash);
37
+ /** @type {string | undefined} */
38
+ let normalized_url;
39
39
 
40
40
  /** @type {Record<string, import('./page/types.js').Cookie>} */
41
41
  const new_cookies = {};
@@ -149,6 +149,9 @@ export function get_cookies(request, url, trailing_slash) {
149
149
  let path = options.path;
150
150
 
151
151
  if (!options.domain || options.domain === url.hostname) {
152
+ if (!normalized_url) {
153
+ throw new Error('Cannot serialize cookies until after the route is determined');
154
+ }
152
155
  path = resolve(normalized_url, path);
153
156
  }
154
157
 
@@ -190,12 +193,20 @@ export function get_cookies(request, url, trailing_slash) {
190
193
  .join('; ');
191
194
  }
192
195
 
196
+ /** @type {Array<() => void>} */
197
+ const internal_queue = [];
198
+
193
199
  /**
194
200
  * @param {string} name
195
201
  * @param {string} value
196
202
  * @param {import('./page/types.js').Cookie['options']} options
197
203
  */
198
204
  function set_internal(name, value, options) {
205
+ if (!normalized_url) {
206
+ internal_queue.push(() => set_internal(name, value, options));
207
+ return;
208
+ }
209
+
199
210
  let path = options.path;
200
211
 
201
212
  if (!options.domain || options.domain === url.hostname) {
@@ -220,7 +231,15 @@ export function get_cookies(request, url, trailing_slash) {
220
231
  }
221
232
  }
222
233
 
223
- return { cookies, new_cookies, get_cookie_header, set_internal };
234
+ /**
235
+ * @param {import('types').TrailingSlash} trailing_slash
236
+ */
237
+ function set_trailing_slash(trailing_slash) {
238
+ normalized_url = normalize_path(url.pathname, trailing_slash);
239
+ internal_queue.forEach((fn) => fn());
240
+ }
241
+
242
+ return { cookies, new_cookies, get_cookie_header, set_internal, set_trailing_slash };
224
243
  }
225
244
 
226
245
  /**
@@ -214,7 +214,7 @@ function check_named_default_separate(actions) {
214
214
 
215
215
  /**
216
216
  * @param {import('@sveltejs/kit').RequestEvent} event
217
- * @param {NonNullable<import('types').SSRNode['server']['actions']>} actions
217
+ * @param {NonNullable<import('types').ServerNode['actions']>} actions
218
218
  * @throws {Redirect | HttpError | SvelteKitError | Error}
219
219
  */
220
220
  async function call_action(event, actions) {