@sveltejs/kit 2.13.0 → 2.14.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.13.0",
3
+ "version": "2.14.0",
4
4
  "description": "SvelteKit is the fastest way to build Svelte apps",
5
5
  "keywords": [
6
6
  "framework",
@@ -260,6 +260,10 @@ const options = object(
260
260
  })
261
261
  }),
262
262
 
263
+ router: object({
264
+ type: list(['pathname', 'hash'])
265
+ }),
266
+
263
267
  serviceWorker: object({
264
268
  register: boolean(true),
265
269
  files: fun((filename) => !/\.DS_Store/.test(filename))
@@ -22,6 +22,7 @@ export default forked(import.meta.url, analyse);
22
22
 
23
23
  /**
24
24
  * @param {{
25
+ * hash: boolean;
25
26
  * manifest_path: string;
26
27
  * manifest_data: import('types').ManifestData;
27
28
  * server_manifest: import('vite').Manifest;
@@ -29,7 +30,14 @@ export default forked(import.meta.url, analyse);
29
30
  * env: Record<string, string>
30
31
  * }} opts
31
32
  */
32
- async function analyse({ manifest_path, manifest_data, server_manifest, tracked_features, env }) {
33
+ async function analyse({
34
+ hash,
35
+ manifest_path,
36
+ manifest_data,
37
+ server_manifest,
38
+ tracked_features,
39
+ env
40
+ }) {
33
41
  /** @type {import('@sveltejs/kit').SSRManifest} */
34
42
  const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
35
43
 
@@ -67,6 +75,18 @@ async function analyse({ manifest_path, manifest_data, server_manifest, tracked_
67
75
 
68
76
  // analyse nodes
69
77
  for (const node of nodes) {
78
+ if (hash && node.universal) {
79
+ const options = Object.keys(node.universal).filter((o) => o !== 'load');
80
+ if (options.length > 0) {
81
+ throw new Error(
82
+ `Page options are ignored when \`router.type === 'hash'\` (${node.universal_id} has ${options
83
+ .filter((o) => o !== 'load')
84
+ .map((o) => `'${o}'`)
85
+ .join(', ')})`
86
+ );
87
+ }
88
+ }
89
+
70
90
  metadata.nodes[node.index] = {
71
91
  has_server_load: node.server?.load !== undefined || node.server?.trailingSlash !== undefined
72
92
  };
@@ -13,6 +13,7 @@ import { crawl } from './crawl.js';
13
13
  import { forked } from '../../utils/fork.js';
14
14
  import * as devalue from 'devalue';
15
15
  import { createReadableStream } from '@sveltejs/kit/node';
16
+ import generate_fallback from './fallback.js';
16
17
 
17
18
  export default forked(import.meta.url, prerender);
18
19
 
@@ -24,6 +25,7 @@ const SPECIAL_HASHLINKS = new Set(['', 'top']);
24
25
 
25
26
  /**
26
27
  * @param {{
28
+ * hash: boolean;
27
29
  * out: string;
28
30
  * manifest_path: string;
29
31
  * metadata: import('types').ServerMetadata;
@@ -31,7 +33,7 @@ const SPECIAL_HASHLINKS = new Set(['', 'top']);
31
33
  * env: Record<string, string>
32
34
  * }} opts
33
35
  */
34
- async function prerender({ out, manifest_path, metadata, verbose, env }) {
36
+ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) {
35
37
  /** @type {import('@sveltejs/kit').SSRManifest} */
36
38
  const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
37
39
 
@@ -98,6 +100,23 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
98
100
  /** @type {import('types').ValidatedKitConfig} */
99
101
  const config = (await load_config()).kit;
100
102
 
103
+ if (hash) {
104
+ const fallback = await generate_fallback({
105
+ manifest_path,
106
+ env
107
+ });
108
+
109
+ const file = output_filename('/', true);
110
+ const dest = `${config.outDir}/output/prerendered/pages/${file}`;
111
+
112
+ mkdirp(dirname(dest));
113
+ writeFileSync(dest, fallback);
114
+
115
+ prerendered.pages.set('/', { file });
116
+
117
+ return { prerendered, prerender_map };
118
+ }
119
+
101
120
  const emulator = await config.adapter?.emulate?.();
102
121
 
103
122
  /** @type {import('types').Logger} */
@@ -271,6 +271,12 @@ function create_routes_and_nodes(cwd, config, fallback) {
271
271
  config.kit.moduleExtensions
272
272
  );
273
273
 
274
+ if (config.kit.router.type === 'hash' && item.kind === 'server') {
275
+ throw new Error(
276
+ `Cannot use server-only files in an app with \`router.type === 'hash': ${project_relative}`
277
+ );
278
+ }
279
+
274
280
  /**
275
281
  * @param {string} type
276
282
  * @param {string} existing_file
@@ -158,6 +158,8 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
158
158
 
159
159
  export const decoders = Object.fromEntries(Object.entries(hooks.transport).map(([k, v]) => [k, v.decode]));
160
160
 
161
+ export const hash = ${JSON.stringify(kit.router.type === 'hash')};
162
+
161
163
  export const decode = (type, value) => decoders[type](value);
162
164
 
163
165
  export { default as root } from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}';
@@ -42,6 +42,7 @@ export const options = {
42
42
  embedded: ${config.kit.embedded},
43
43
  env_public_prefix: '${config.kit.env.publicPrefix}',
44
44
  env_private_prefix: '${config.kit.env.privatePrefix}',
45
+ hash_routing: ${s(config.kit.router.type === 'hash')},
45
46
  hooks: null, // added lazily, via \`get_hooks\`
46
47
  preload_strategy: ${s(config.kit.output.preloadStrategy)},
47
48
  root,
@@ -617,6 +617,16 @@ export interface KitConfig {
617
617
  */
618
618
  origin?: string;
619
619
  };
620
+ router?: {
621
+ /**
622
+ * What type of client-side router to use.
623
+ * - `'pathname'` is the default and means the current URL pathname determines the route
624
+ * - `'hash'` means the route is determined by `location.hash`. In this case, SSR and prerendering are disabled. This is only recommended if `pathname` is not an option, for example because you don't control the webserver where your app is deployed.
625
+ *
626
+ * @default "pathname"
627
+ */
628
+ type?: 'pathname' | 'hash';
629
+ };
620
630
  serviceWorker?: {
621
631
  /**
622
632
  * Whether to automatically register the service worker, if it exists.
@@ -793,6 +793,7 @@ async function kit({ svelte_config }) {
793
793
  );
794
794
 
795
795
  const metadata = await analyse({
796
+ hash: kit.router.type === 'hash',
796
797
  manifest_path,
797
798
  manifest_data,
798
799
  server_manifest,
@@ -897,6 +898,7 @@ async function kit({ svelte_config }) {
897
898
 
898
899
  // ...and prerender
899
900
  const { prerendered, prerender_map } = await prerender({
901
+ hash: kit.router.type === 'hash',
900
902
  out,
901
903
  manifest_path,
902
904
  metadata,
@@ -424,9 +424,9 @@ async function _preload_data(intent) {
424
424
  return load_cache.promise;
425
425
  }
426
426
 
427
- /** @param {string} pathname */
428
- async function _preload_code(pathname) {
429
- const route = routes.find((route) => route.exec(get_url_path(pathname)));
427
+ /** @param {URL} url */
428
+ async function _preload_code(url) {
429
+ const route = routes.find((route) => route.exec(get_url_path(url)));
430
430
 
431
431
  if (route) {
432
432
  await Promise.all([...route.layouts, route.leaf].map((load) => load?.[1]()));
@@ -610,6 +610,19 @@ async function load_node({ loader, parent, url, params, route, server_data_node
610
610
 
611
611
  if (DEV) {
612
612
  validate_page_exports(node.universal);
613
+
614
+ if (node.universal && app.hash) {
615
+ const options = Object.keys(node.universal).filter((o) => o !== 'load');
616
+
617
+ if (options.length > 0) {
618
+ throw new Error(
619
+ `Page options are ignored when \`router.type === 'hash'\` (${route.id} has ${options
620
+ .filter((o) => o !== 'load')
621
+ .map((o) => `'${o}'`)
622
+ .join(', ')})`
623
+ );
624
+ }
625
+ }
613
626
  }
614
627
 
615
628
  if (node.universal?.load) {
@@ -653,7 +666,8 @@ async function load_node({ loader, parent, url, params, route, server_data_node
653
666
  if (is_tracking) {
654
667
  uses.search_params.add(param);
655
668
  }
656
- }
669
+ },
670
+ app.hash
657
671
  ),
658
672
  async fetch(resource, init) {
659
673
  /** @type {URL | string} */
@@ -810,7 +824,6 @@ function create_data_node(node, previous) {
810
824
  }
811
825
 
812
826
  /**
813
- *
814
827
  * @param {URL | null} old_url
815
828
  * @param {URL} new_url
816
829
  */
@@ -875,7 +888,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
875
888
 
876
889
  /** @type {import('types').ServerNodesResponse | import('types').ServerRedirectNode | null} */
877
890
  let server_data = null;
878
- const url_changed = current.url ? id !== current.url.pathname + current.url.search : false;
891
+ const url_changed = current.url ? id !== get_page_key(current.url) : false;
879
892
  const route_changed = current.route ? route.id !== current.route.id : false;
880
893
  const search_params_changed = diff_search_params(current.url, url);
881
894
 
@@ -1169,13 +1182,23 @@ async function load_root_error_page({ status, error, url, route }) {
1169
1182
  * @param {boolean} invalidating
1170
1183
  */
1171
1184
  function get_navigation_intent(url, invalidating) {
1172
- if (!url) return undefined;
1173
- if (is_external_url(url, base)) return;
1185
+ if (!url) return;
1186
+ if (is_external_url(url, base, app.hash)) return;
1174
1187
 
1175
1188
  // reroute could alter the given URL, so we pass a copy
1176
1189
  let rerouted;
1177
1190
  try {
1178
- rerouted = app.hooks.reroute({ url: new URL(url) }) ?? url.pathname;
1191
+ rerouted = app.hooks.reroute({ url: new URL(url) }) ?? url;
1192
+
1193
+ if (typeof rerouted === 'string') {
1194
+ if (app.hash) {
1195
+ url.hash = rerouted;
1196
+ } else {
1197
+ url.pathname = rerouted;
1198
+ }
1199
+
1200
+ rerouted = url;
1201
+ }
1179
1202
  } catch (e) {
1180
1203
  if (DEV) {
1181
1204
  // in development, print the error...
@@ -1195,7 +1218,7 @@ function get_navigation_intent(url, invalidating) {
1195
1218
  const params = route.exec(path);
1196
1219
 
1197
1220
  if (params) {
1198
- const id = url.pathname + url.search;
1221
+ const id = get_page_key(url);
1199
1222
  /** @type {import('./types.js').NavigationIntent} */
1200
1223
  const intent = {
1201
1224
  id,
@@ -1209,9 +1232,18 @@ function get_navigation_intent(url, invalidating) {
1209
1232
  }
1210
1233
  }
1211
1234
 
1212
- /** @param {string} pathname */
1213
- function get_url_path(pathname) {
1214
- return decode_pathname(pathname.slice(base.length) || '/');
1235
+ /** @param {URL} url */
1236
+ function get_url_path(url) {
1237
+ return (
1238
+ decode_pathname(
1239
+ app.hash ? url.hash.replace(/^#/, '').replace(/[?#].+/, '') : url.pathname.slice(base.length)
1240
+ ) || '/'
1241
+ );
1242
+ }
1243
+
1244
+ /** @param {URL} url */
1245
+ function get_page_key(url) {
1246
+ return (app.hash ? url.hash.replace(/^#/, '') : url.pathname) + url.search;
1215
1247
  }
1216
1248
 
1217
1249
  /**
@@ -1303,19 +1335,42 @@ async function navigate({
1303
1335
  let navigation_result = intent && (await load_route(intent));
1304
1336
 
1305
1337
  if (!navigation_result) {
1306
- if (is_external_url(url, base)) {
1307
- return await native_navigation(url);
1308
- }
1309
- navigation_result = await server_fallback(
1310
- url,
1311
- { id: null },
1312
- await handle_error(new SvelteKitError(404, 'Not Found', `Not found: ${url.pathname}`), {
1338
+ if (is_external_url(url, base, app.hash)) {
1339
+ if (DEV && app.hash) {
1340
+ // Special case for hash mode during DEV: If someone accidentally forgets to use a hash for the link,
1341
+ // they would end up here in an endless loop. Fall back to error page in that case
1342
+ navigation_result = await server_fallback(
1343
+ url,
1344
+ { id: null },
1345
+ await handle_error(
1346
+ new SvelteKitError(
1347
+ 404,
1348
+ 'Not Found',
1349
+ `Not found: ${url.pathname} (did you forget the hash?)`
1350
+ ),
1351
+ {
1352
+ url,
1353
+ params: {},
1354
+ route: { id: null }
1355
+ }
1356
+ ),
1357
+ 404
1358
+ );
1359
+ } else {
1360
+ return await native_navigation(url);
1361
+ }
1362
+ } else {
1363
+ navigation_result = await server_fallback(
1313
1364
  url,
1314
- params: {},
1315
- route: { id: null }
1316
- }),
1317
- 404
1318
- );
1365
+ { id: null },
1366
+ await handle_error(new SvelteKitError(404, 'Not Found', `Not found: ${url.pathname}`), {
1367
+ url,
1368
+ params: {},
1369
+ route: { id: null }
1370
+ }),
1371
+ 404
1372
+ );
1373
+ }
1319
1374
  }
1320
1375
 
1321
1376
  // if this is an internal navigation intent, use the normalized
@@ -1437,7 +1492,11 @@ async function navigate({
1437
1492
  const scroll = popped ? popped.scroll : noscroll ? scroll_state() : null;
1438
1493
 
1439
1494
  if (autoscroll) {
1440
- const deep_linked = url.hash && document.getElementById(decodeURIComponent(url.hash.slice(1)));
1495
+ const deep_linked =
1496
+ url.hash &&
1497
+ document.getElementById(
1498
+ decodeURIComponent(app.hash ? (url.hash.split('#')[2] ?? '') : url.hash.slice(1))
1499
+ );
1441
1500
  if (scroll) {
1442
1501
  scrollTo(scroll.x, scroll.y);
1443
1502
  } else if (deep_linked) {
@@ -1547,7 +1606,7 @@ function setup_preload() {
1547
1606
  (entries) => {
1548
1607
  for (const entry of entries) {
1549
1608
  if (entry.isIntersecting) {
1550
- _preload_code(/** @type {HTMLAnchorElement} */ (entry.target).href);
1609
+ _preload_code(new URL(/** @type {HTMLAnchorElement} */ (entry.target).href));
1551
1610
  observer.unobserve(entry.target);
1552
1611
  }
1553
1612
  }
@@ -1563,13 +1622,13 @@ function setup_preload() {
1563
1622
  const a = find_anchor(element, container);
1564
1623
  if (!a) return;
1565
1624
 
1566
- const { url, external, download } = get_link_info(a, base);
1625
+ const { url, external, download } = get_link_info(a, base, app.hash);
1567
1626
  if (external || download) return;
1568
1627
 
1569
1628
  const options = get_router_options(a);
1570
1629
 
1571
1630
  // we don't want to preload data for a page we're already on
1572
- const same_url = url && current.url.pathname + current.url.search === url.pathname + url.search;
1631
+ const same_url = url && get_page_key(current.url) === get_page_key(url);
1573
1632
 
1574
1633
  if (!options.reload && !same_url) {
1575
1634
  if (priority <= options.preload_data) {
@@ -1591,7 +1650,7 @@ function setup_preload() {
1591
1650
  }
1592
1651
  }
1593
1652
  } else if (priority <= options.preload_code) {
1594
- _preload_code(/** @type {URL} */ (url).pathname);
1653
+ _preload_code(/** @type {URL} */ (url));
1595
1654
  }
1596
1655
  }
1597
1656
  }
@@ -1600,7 +1659,7 @@ function setup_preload() {
1600
1659
  observer.disconnect();
1601
1660
 
1602
1661
  for (const a of container.querySelectorAll('a')) {
1603
- const { url, external, download } = get_link_info(a, base);
1662
+ const { url, external, download } = get_link_info(a, base, app.hash);
1604
1663
  if (external || download) continue;
1605
1664
 
1606
1665
  const options = get_router_options(a);
@@ -1611,7 +1670,7 @@ function setup_preload() {
1611
1670
  }
1612
1671
 
1613
1672
  if (options.preload_code === PRELOAD_PRIORITIES.eager) {
1614
- _preload_code(/** @type {URL} */ (url).pathname);
1673
+ _preload_code(/** @type {URL} */ (url));
1615
1674
  }
1616
1675
  }
1617
1676
  }
@@ -1741,7 +1800,7 @@ export function goto(url, opts = {}) {
1741
1800
  throw new Error('Cannot call goto(...) on the server');
1742
1801
  }
1743
1802
 
1744
- url = resolve_url(url);
1803
+ url = new URL(resolve_url(url));
1745
1804
 
1746
1805
  if (url.origin !== origin) {
1747
1806
  return Promise.reject(
@@ -1855,6 +1914,8 @@ export function preloadCode(pathname) {
1855
1914
  throw new Error('Cannot call preloadCode(...) on the server');
1856
1915
  }
1857
1916
 
1917
+ const url = new URL(pathname, current.url);
1918
+
1858
1919
  if (DEV) {
1859
1920
  if (!pathname.startsWith(base)) {
1860
1921
  throw new Error(
@@ -1862,12 +1923,12 @@ export function preloadCode(pathname) {
1862
1923
  );
1863
1924
  }
1864
1925
 
1865
- if (!routes.find((route) => route.exec(get_url_path(pathname)))) {
1926
+ if (!routes.find((route) => route.exec(get_url_path(url)))) {
1866
1927
  throw new Error(`'${pathname}' did not match any routes`);
1867
1928
  }
1868
1929
  }
1869
1930
 
1870
- return _preload_code(pathname);
1931
+ return _preload_code(url);
1871
1932
  }
1872
1933
 
1873
1934
  /**
@@ -2073,7 +2134,7 @@ function _start_router() {
2073
2134
  const a = find_anchor(/** @type {Element} */ (event.composedPath()[0]), container);
2074
2135
  if (!a) return;
2075
2136
 
2076
- const { url, external, target, download } = get_link_info(a, base);
2137
+ const { url, external, target, download } = get_link_info(a, base, app.hash);
2077
2138
  if (!url) return;
2078
2139
 
2079
2140
  // bail out before `beforeNavigate` if link opens in a different tab
@@ -2103,7 +2164,7 @@ function _start_router() {
2103
2164
 
2104
2165
  if (download) return;
2105
2166
 
2106
- const [nonhash, hash] = url.href.split('#');
2167
+ const [nonhash, hash] = (app.hash ? url.hash.replace(/^#/, '') : url.href).split('#');
2107
2168
  const same_pathname = nonhash === strip_hash(location);
2108
2169
 
2109
2170
  // Ignore the following but fire beforeNavigate
@@ -2198,11 +2259,12 @@ function _start_router() {
2198
2259
 
2199
2260
  if (method !== 'get') return;
2200
2261
 
2262
+ // It is impossible to use form actions with hash router, so we just ignore handling them here
2201
2263
  const url = new URL(
2202
2264
  (submitter?.hasAttribute('formaction') && submitter?.formAction) || form.action
2203
2265
  );
2204
2266
 
2205
- if (is_external_url(url, base)) return;
2267
+ if (is_external_url(url, base, false)) return;
2206
2268
 
2207
2269
  const event_form = /** @type {HTMLFormElement} */ (event.target);
2208
2270
 
@@ -2311,6 +2373,13 @@ function _start_router() {
2311
2373
  '',
2312
2374
  location.href
2313
2375
  );
2376
+ } else if (app.hash) {
2377
+ // If the user edits the hash via the browser URL bar, it
2378
+ // (surprisingly!) mutates `current.url`, allowing us to
2379
+ // detect it and trigger a navigation
2380
+ if (current.url.hash === location.hash) {
2381
+ navigate({ type: 'goto', url: current.url });
2382
+ }
2314
2383
  }
2315
2384
  });
2316
2385
 
@@ -38,6 +38,11 @@ export interface SvelteKitApp {
38
38
 
39
39
  decoders: Record<string, (data: any) => any>;
40
40
 
41
+ /**
42
+ * Whether or not we're using hash-based routing
43
+ */
44
+ hash: boolean;
45
+
41
46
  root: typeof SvelteComponent;
42
47
  }
43
48
 
@@ -122,8 +122,9 @@ export function find_anchor(element, target) {
122
122
  /**
123
123
  * @param {HTMLAnchorElement | SVGAElement} a
124
124
  * @param {string} base
125
+ * @param {boolean} uses_hash_router
125
126
  */
126
- export function get_link_info(a, base) {
127
+ export function get_link_info(a, base, uses_hash_router) {
127
128
  /** @type {URL | undefined} */
128
129
  let url;
129
130
 
@@ -136,7 +137,7 @@ export function get_link_info(a, base) {
136
137
  const external =
137
138
  !url ||
138
139
  !!target ||
139
- is_external_url(url, base) ||
140
+ is_external_url(url, base, uses_hash_router) ||
140
141
  (a.getAttribute('rel') || '').split(/\s+/).includes('external');
141
142
 
142
143
  const download = url?.origin === origin && a.hasAttribute('download');
@@ -296,9 +297,18 @@ export function create_updated_store() {
296
297
  }
297
298
 
298
299
  /**
300
+ * Is external if
301
+ * - origin different
302
+ * - path doesn't start with base
303
+ * - uses hash router and pathname is more than base
299
304
  * @param {URL} url
300
305
  * @param {string} base
306
+ * @param {boolean} has_pathname_in_hash
301
307
  */
302
- export function is_external_url(url, base) {
303
- return url.origin !== origin || !url.pathname.startsWith(base);
308
+ export function is_external_url(url, base, has_pathname_in_hash) {
309
+ return (
310
+ url.origin !== origin ||
311
+ !url.pathname.startsWith(base) ||
312
+ (has_pathname_in_hash && url.pathname !== (base || '/'))
313
+ );
304
314
  }
@@ -81,6 +81,10 @@ export async function respond(request, options, manifest, state) {
81
81
  }
82
82
  }
83
83
 
84
+ if (options.hash_routing && url.pathname !== base + '/' && url.pathname !== '/[fallback]') {
85
+ return text('Not found', { status: 404 });
86
+ }
87
+
84
88
  // reroute could alter the given URL, so we pass a copy
85
89
  let rerouted_path;
86
90
  try {
@@ -416,7 +420,7 @@ export async function respond(request, options, manifest, state) {
416
420
  };
417
421
  }
418
422
 
419
- if (state.prerendering?.fallback) {
423
+ if (options.hash_routing || state.prerendering?.fallback) {
420
424
  return await render_response({
421
425
  event,
422
426
  options,
@@ -370,6 +370,7 @@ export interface SSROptions {
370
370
  embedded: boolean;
371
371
  env_public_prefix: string;
372
372
  env_private_prefix: string;
373
+ hash_routing: boolean;
373
374
  hooks: ServerHooks;
374
375
  preload_strategy: ValidatedConfig['kit']['output']['preloadStrategy'];
375
376
  root: SSRComponent['default'];
package/src/utils/url.js CHANGED
@@ -85,24 +85,13 @@ export function strip_hash({ href }) {
85
85
  return href.split('#')[0];
86
86
  }
87
87
 
88
- /**
89
- * URL properties that could change during the lifetime of the page,
90
- * which excludes things like `origin`
91
- */
92
- const tracked_url_properties = /** @type {const} */ ([
93
- 'href',
94
- 'pathname',
95
- 'search',
96
- 'toString',
97
- 'toJSON'
98
- ]);
99
-
100
88
  /**
101
89
  * @param {URL} url
102
90
  * @param {() => void} callback
103
91
  * @param {(search_param: string) => void} search_params_callback
92
+ * @param {boolean} [allow_hash]
104
93
  */
105
- export function make_trackable(url, callback, search_params_callback) {
94
+ export function make_trackable(url, callback, search_params_callback, allow_hash = false) {
106
95
  const tracked = new URL(url);
107
96
 
108
97
  Object.defineProperty(tracked, 'searchParams', {
@@ -127,10 +116,18 @@ export function make_trackable(url, callback, search_params_callback) {
127
116
  configurable: true
128
117
  });
129
118
 
119
+ /**
120
+ * URL properties that could change during the lifetime of the page,
121
+ * which excludes things like `origin`
122
+ */
123
+ const tracked_url_properties = ['href', 'pathname', 'search', 'toString', 'toJSON'];
124
+ if (allow_hash) tracked_url_properties.push('hash');
125
+
130
126
  for (const property of tracked_url_properties) {
131
127
  Object.defineProperty(tracked, property, {
132
128
  get() {
133
129
  callback();
130
+ // @ts-expect-error
134
131
  return url[property];
135
132
  },
136
133
 
@@ -151,7 +148,7 @@ export function make_trackable(url, callback, search_params_callback) {
151
148
  };
152
149
  }
153
150
 
154
- if (DEV || !BROWSER) {
151
+ if ((DEV || !BROWSER) && !allow_hash) {
155
152
  disable_hash(tracked);
156
153
  }
157
154
 
package/src/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // generated during release, do not modify
2
2
 
3
3
  /** @type {string} */
4
- export const VERSION = '2.13.0';
4
+ export const VERSION = '2.14.0';
package/types/index.d.ts CHANGED
@@ -599,6 +599,16 @@ declare module '@sveltejs/kit' {
599
599
  */
600
600
  origin?: string;
601
601
  };
602
+ router?: {
603
+ /**
604
+ * What type of client-side router to use.
605
+ * - `'pathname'` is the default and means the current URL pathname determines the route
606
+ * - `'hash'` means the route is determined by `location.hash`. In this case, SSR and prerendering are disabled. This is only recommended if `pathname` is not an option, for example because you don't control the webserver where your app is deployed.
607
+ *
608
+ * @default "pathname"
609
+ */
610
+ type?: 'pathname' | 'hash';
611
+ };
602
612
  serviceWorker?: {
603
613
  /**
604
614
  * Whether to automatically register the service worker, if it exists.
@@ -159,6 +159,6 @@
159
159
  null,
160
160
  null
161
161
  ],
162
- "mappings": ";;;;;;;;;kBA2BiBA,OAAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;aA2BZC,cAAcA;;;;;;aAMdC,cAAcA;;;;;;;;;;;;;;;kBAeTC,aAAaA;;;;;;;;;;;;;;;;;kBAiBbC,OAAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAkGPC,MAAMA;;;;;;;;;;;;;;;;;;;;;kBAqBNC,OAAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA4DPC,QAAQA;;;;;;;;kBAQRC,SAASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAgadC,MAAMA;;;;;;;;;;;aAWNC,iBAAiBA;;;;;;;;;;;;;aAajBC,iBAAiBA;;;;;;;;;;aAUjBC,WAAWA;;;;;;;;;;aAUXC,UAAUA;;;;;;aAMVC,UAAUA;;;;;;aAMVC,OAAOA;;;;;;;;;;;;;;;;;;;;;;;;;;aA0BPC,SAASA;;;;;kBAKJC,WAAWA;;;;;;;;;;;;aAYhBC,IAAIA;;;;;;;;;;;;kBAYCC,SAASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA4GTC,eAAeA;;;;;;;;;;;;;;;;;;;;;;;;;;kBA0BfC,gBAAgBA;;;;;;;;;;;;;;;;;;;;;;;;aAwBrBC,cAAcA;;kBAETC,UAAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAoCVC,cAAcA;;;;;;;;;;kBAUdC,UAAUA;;;;;;;;;;;;;;;;;;kBAkBVC,aAAaA;;;;;;;;;;;;;;;;;;;kBAmBbC,IAAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA8CTC,YAAYA;;kBAEPC,YAAYA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA4FjBC,cAAcA;;;;;kBAKTC,cAAcA;;;;;;;;;;;;;;;;;;;;;;;kBAuBdC,eAAeA;;;;;;;;;;;;;;;cAenBC,MAAMA;;;;;;kBAMFC,iBAAiBA;;;;;;;kBAOjBC,WAAWA;;;;;;;;;;;;;;;;;;;;;aAqBhBC,UAAUA;;;;;;;kBAOLC,eAAeA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAqEpBC,MAAMA;;;;;;;;;;aAUNC,OAAOA;;;;;;;;;;;;;;;;aAgBPC,YAAYA;;;;;;;;;;;;kBCx1CXC,SAASA;;;;;;;;;;kBAqBTC,QAAQA;;;;;;;aDg2CTC,cAAcA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA6BTC,QAAQA;;;;WE54CRC,YAAYA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAkDZC,GAAGA;;;;;;;;;;;;;;;;;;;;;WAqBHC,aAAaA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAmElBC,UAAUA;;WAELC,MAAMA;;;;;;;;;MASXC,YAAYA;;WAEPC,WAAWA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAmCXC,yBAAyBA;;;;;;;;;;WAUzBC,yBAAyBA;;;;WAIzBC,sCAAsCA;;;;MAI3CC,8BAA8BA;MAC9BC,8BAA8BA;MAC9BC,2CAA2CA;;;;;;aAM3CC,eAAeA;;WAIVC,cAAcA;;;;;WAKdC,YAAYA;;;;;;MAMjBC,aAAaA;WCxLRC,KAAKA;;;;;;WAcLC,SAASA;;;;;;;;;;;;;;;;;WAiFTC,YAAYA;;;;;;;;;;;;WAYZC,QAAQA;;;;;;;;;;;;;;MAyBbC,iBAAiBA;;;;;;;;WAUZC,UAAUA;;;;;;;;;;;;;WAaVC,SAASA;;;;;;;;;;;;;;;;;;;;;;;WAsGTC,YAAYA;;;;;;;;;;;;;;;;MAgBjBC,kBAAkBA;;WAEbC,OAAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAsCZC,aAAaA;;WA2BRC,eAAeA;;;;;;MAMpBC,uBAAuBA;;MAEvBC,WAAWA;;;;;;;;WAQNC,QAAQA;;;;;;;;;MA2CbC,eAAeA;;;;;MAKfC,kBAAkBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBCvXdC,WAAWA;;;;;;;;;;;iBAcXC,QAAQA;;;;;iBAiBRC,UAAUA;;;;;;iBASVC,IAAIA;;;;;;iBA8BJC,IAAIA;;;;;;;;;;;;;;;;iBAkDJC,eAAeA;;;cCnMlBC,OAAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBCoEJC,QAAQA;;;;;;iBCoCFC,UAAUA;;;;;;iBAkCVC,WAAWA;;;;;iBAgFjBC,oBAAoBA;;;;;;;;;;;iBC3MpBC,gBAAgBA;;;;;;;;;iBC+GVC,SAASA;;;;;;;;;cC9HlBC,OAAOA;;;;;cAKPC,GAAGA;;;;;cAKHC,QAAQA;;;;;cAKRC,OAAOA;;;;;;;;;;;;;;;;;;;;;;;;iBCWJC,WAAWA;;;;;;;;;;;;;;;;;;;;;iBA2CXC,OAAOA;;;;;;;iBCm2DDC,WAAWA;;;;;;;;;;;iBAvSjBC,aAAaA;;;;;;;;;;;;iBAiBbC,cAAcA;;;;;;;;;;iBAedC,UAAUA;;;;;iBASVC,qBAAqBA;;;;;;;;;;iBA6BrBC,IAAIA;;;;;;;;;;;;;;;;;;;;;;;;iBAsCJC,UAAUA;;;;iBAmBVC,aAAaA;;;;;;;;;;;;iBAqBPC,WAAWA;;;;;;;;;;;;;;;;;;iBAoCjBC,WAAWA;;;;;iBA2BXC,SAASA;;;;;iBA4CTC,YAAYA;MVxuDhB3D,YAAYA;;;;;;;;;;;YWtJb4D,IAAIA;;;;;;;YAOJC,MAAMA;;;;;;;;;;;;;;;;;iBAiBDC,YAAYA;;;;;;;;;;;;;;;;;;iBCVZC,IAAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cCmBPC,IAAIA;;;;;cAQJC,UAAUA;;;;;;;;;;;cAMVC,OAAOA;;;;;;;;;iBC1CPC,SAASA;;;;;;;;;;;;;;;cAyBTH,IAAIA;;;;;;;;;;cAiBJC,UAAUA;;;;;;;;cAeVC,OAAOA",
162
+ "mappings": "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",
163
163
  "ignoreList": []
164
164
  }