@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 +1 -1
- package/src/core/config/options.js +4 -0
- package/src/core/postbuild/analyse.js +21 -1
- package/src/core/postbuild/prerender.js +20 -1
- package/src/core/sync/create_manifest_data/index.js +6 -0
- package/src/core/sync/write_client_manifest.js +2 -0
- package/src/core/sync/write_server.js +1 -0
- package/src/exports/public.d.ts +10 -0
- package/src/exports/vite/index.js +2 -0
- package/src/runtime/client/client.js +107 -38
- package/src/runtime/client/types.d.ts +5 -0
- package/src/runtime/client/utils.js +14 -4
- package/src/runtime/server/respond.js +5 -1
- package/src/types/internal.d.ts +1 -0
- package/src/utils/url.js +11 -14
- package/src/version.js +1 -1
- package/types/index.d.ts +10 -0
- package/types/index.d.ts.map +1 -1
package/package.json
CHANGED
|
@@ -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({
|
|
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,
|
package/src/exports/public.d.ts
CHANGED
|
@@ -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 {
|
|
428
|
-
async function _preload_code(
|
|
429
|
-
const route = routes.find((route) => route.exec(get_url_path(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
1213
|
-
function get_url_path(
|
|
1214
|
-
return
|
|
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
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
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
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
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 =
|
|
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
|
|
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)
|
|
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)
|
|
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(
|
|
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(
|
|
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
|
|
|
@@ -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
|
|
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,
|
package/src/types/internal.d.ts
CHANGED
|
@@ -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
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.
|
package/types/index.d.ts.map
CHANGED
|
@@ -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
|
|
162
|
+
"mappings": "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",
|
|
163
163
|
"ignoreList": []
|
|
164
164
|
}
|