@sveltejs/kit 2.16.0 → 2.17.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 +5 -3
- package/src/core/adapt/builder.js +2 -0
- package/src/core/config/index.js +16 -1
- package/src/core/config/options.js +2 -1
- package/src/core/generate_manifest/index.js +14 -5
- package/src/core/postbuild/analyse.js +2 -2
- package/src/core/sync/write_client_manifest.js +33 -17
- package/src/core/sync/write_server.js +2 -1
- package/src/exports/public.d.ts +29 -6
- package/src/exports/vite/build/build_server.js +5 -0
- package/src/exports/vite/dev/index.js +29 -1
- package/src/exports/vite/index.js +51 -5
- package/src/runtime/app/state/index.js +11 -0
- package/src/runtime/client/client.js +80 -46
- package/src/runtime/client/parse.js +20 -0
- package/src/runtime/client/types.d.ts +16 -1
- package/src/runtime/client/utils.js +6 -0
- package/src/runtime/pathname.js +54 -0
- package/src/runtime/server/cookie.js +2 -1
- package/src/runtime/server/endpoint.js +0 -5
- package/src/runtime/server/fetch.js +12 -0
- package/src/runtime/server/page/index.js +1 -1
- package/src/runtime/server/page/render.js +25 -3
- package/src/runtime/server/page/server_routing.js +110 -0
- package/src/runtime/server/respond.js +45 -26
- package/src/runtime/server/validate-headers.js +64 -0
- package/src/runtime/utils.js +21 -0
- package/src/types/ambient-private.d.ts +1 -0
- package/src/types/global-private.d.ts +2 -0
- package/src/types/internal.d.ts +47 -9
- package/src/utils/filesystem.js +4 -3
- package/src/utils/routing.js +8 -0
- package/src/utils/url.js +0 -23
- package/src/version.js +1 -1
- package/types/index.d.ts +72 -13
- package/types/index.d.ts.map +2 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { BROWSER, DEV } from 'esm-env';
|
|
2
2
|
import { onMount, tick } from 'svelte';
|
|
3
3
|
import {
|
|
4
|
-
add_data_suffix,
|
|
5
4
|
decode_params,
|
|
6
5
|
decode_pathname,
|
|
7
6
|
strip_hash,
|
|
@@ -9,7 +8,7 @@ import {
|
|
|
9
8
|
normalize_path
|
|
10
9
|
} from '../../utils/url.js';
|
|
11
10
|
import { dev_fetch, initial_fetch, lock_fetch, subsequent_fetch, unlock_fetch } from './fetcher.js';
|
|
12
|
-
import { parse } from './parse.js';
|
|
11
|
+
import { parse, parse_server_route } from './parse.js';
|
|
13
12
|
import * as storage from './session-storage.js';
|
|
14
13
|
import {
|
|
15
14
|
find_anchor,
|
|
@@ -40,6 +39,7 @@ import { INVALIDATED_PARAM, TRAILING_SLASH_PARAM, validate_depends } from '../sh
|
|
|
40
39
|
import { get_message, get_status } from '../../utils/error.js';
|
|
41
40
|
import { writable } from 'svelte/store';
|
|
42
41
|
import { page, update, navigating } from './state.svelte.js';
|
|
42
|
+
import { add_data_suffix, add_resolution_prefix } from '../pathname.js';
|
|
43
43
|
|
|
44
44
|
const ICON_REL_ATTRIBUTES = new Set(['icon', 'shortcut icon', 'apple-touch-icon']);
|
|
45
45
|
|
|
@@ -158,7 +158,7 @@ async function update_service_worker() {
|
|
|
158
158
|
|
|
159
159
|
function noop() {}
|
|
160
160
|
|
|
161
|
-
/** @type {import('types').CSRRoute[]} */
|
|
161
|
+
/** @type {import('types').CSRRoute[]} All routes of the app. Only available when kit.router.resolution=client */
|
|
162
162
|
let routes;
|
|
163
163
|
/** @type {import('types').CSRPageNodeLoader} */
|
|
164
164
|
let default_layout_loader;
|
|
@@ -265,7 +265,7 @@ export async function start(_app, _target, hydrate) {
|
|
|
265
265
|
|
|
266
266
|
await _app.hooks.init?.();
|
|
267
267
|
|
|
268
|
-
routes = parse(_app);
|
|
268
|
+
routes = __SVELTEKIT_CLIENT_ROUTING__ ? parse(_app) : [];
|
|
269
269
|
container = __SVELTEKIT_EMBEDDED__ ? _target : document.documentElement;
|
|
270
270
|
target = _target;
|
|
271
271
|
|
|
@@ -322,7 +322,8 @@ async function _invalidate() {
|
|
|
322
322
|
if (!pending_invalidate) return;
|
|
323
323
|
pending_invalidate = null;
|
|
324
324
|
|
|
325
|
-
const
|
|
325
|
+
const nav_token = (token = {});
|
|
326
|
+
const intent = await get_navigation_intent(current.url, true);
|
|
326
327
|
|
|
327
328
|
// Clear preload, it might be affected by the invalidation.
|
|
328
329
|
// Also solves an edge case where a preload is triggered, the navigation for it
|
|
@@ -330,7 +331,6 @@ async function _invalidate() {
|
|
|
330
331
|
// at which point the invalidation should take over and "win".
|
|
331
332
|
load_cache = null;
|
|
332
333
|
|
|
333
|
-
const nav_token = (token = {});
|
|
334
334
|
const navigation_result = intent && (await load_route(intent));
|
|
335
335
|
if (!navigation_result || nav_token !== token) return;
|
|
336
336
|
|
|
@@ -433,10 +433,7 @@ async function _preload_data(intent) {
|
|
|
433
433
|
* @returns {Promise<void>}
|
|
434
434
|
*/
|
|
435
435
|
async function _preload_code(url) {
|
|
436
|
-
const
|
|
437
|
-
if (!rerouted) return;
|
|
438
|
-
|
|
439
|
-
const route = routes.find((route) => route.exec(get_url_path(rerouted)));
|
|
436
|
+
const route = (await get_navigation_intent(url, false))?.route;
|
|
440
437
|
|
|
441
438
|
if (route) {
|
|
442
439
|
await Promise.all([...route.layouts, route.leaf].map((load) => load?.[1]()));
|
|
@@ -587,8 +584,7 @@ function get_navigation_result_from_branch({ url, params, branch, status, error,
|
|
|
587
584
|
}
|
|
588
585
|
|
|
589
586
|
/**
|
|
590
|
-
* Call the load function of the given node, if it exists.
|
|
591
|
-
* If `server_data` is passed, this is treated as the initial run and the page endpoint is not requested.
|
|
587
|
+
* Call the universal load function of the given node, if it exists.
|
|
592
588
|
*
|
|
593
589
|
* @param {{
|
|
594
590
|
* loader: import('types').CSRPageNodeLoader;
|
|
@@ -1240,31 +1236,47 @@ function get_rerouted_url(url) {
|
|
|
1240
1236
|
* returns undefined.
|
|
1241
1237
|
* @param {URL | undefined} url
|
|
1242
1238
|
* @param {boolean} invalidating
|
|
1239
|
+
* @returns {Promise<import('./types.js').NavigationIntent | undefined>}
|
|
1243
1240
|
*/
|
|
1244
|
-
function get_navigation_intent(url, invalidating) {
|
|
1241
|
+
async function get_navigation_intent(url, invalidating) {
|
|
1245
1242
|
if (!url) return;
|
|
1246
1243
|
if (is_external_url(url, base, app.hash)) return;
|
|
1247
1244
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1245
|
+
if (__SVELTEKIT_CLIENT_ROUTING__) {
|
|
1246
|
+
const rerouted = get_rerouted_url(url);
|
|
1247
|
+
if (!rerouted) return;
|
|
1250
1248
|
|
|
1251
|
-
|
|
1249
|
+
const path = get_url_path(rerouted);
|
|
1252
1250
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1251
|
+
for (const route of routes) {
|
|
1252
|
+
const params = route.exec(path);
|
|
1255
1253
|
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
};
|
|
1266
|
-
return intent;
|
|
1254
|
+
if (params) {
|
|
1255
|
+
return {
|
|
1256
|
+
id: get_page_key(url),
|
|
1257
|
+
invalidating,
|
|
1258
|
+
route,
|
|
1259
|
+
params: decode_params(params),
|
|
1260
|
+
url
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1267
1263
|
}
|
|
1264
|
+
} else {
|
|
1265
|
+
/** @type {{ route?: import('types').CSRRouteServer, params: Record<string, string>}} */
|
|
1266
|
+
const { route, params } = await import(
|
|
1267
|
+
/* @vite-ignore */
|
|
1268
|
+
add_resolution_prefix(url.pathname)
|
|
1269
|
+
);
|
|
1270
|
+
|
|
1271
|
+
if (!route) return;
|
|
1272
|
+
|
|
1273
|
+
return {
|
|
1274
|
+
id: get_page_key(url),
|
|
1275
|
+
invalidating,
|
|
1276
|
+
route: parse_server_route(route, app.nodes),
|
|
1277
|
+
params,
|
|
1278
|
+
url
|
|
1279
|
+
};
|
|
1268
1280
|
}
|
|
1269
1281
|
}
|
|
1270
1282
|
|
|
@@ -1347,11 +1359,15 @@ async function navigate({
|
|
|
1347
1359
|
accept = noop,
|
|
1348
1360
|
block = noop
|
|
1349
1361
|
}) {
|
|
1350
|
-
const
|
|
1362
|
+
const prev_token = token;
|
|
1363
|
+
token = nav_token;
|
|
1364
|
+
|
|
1365
|
+
const intent = await get_navigation_intent(url, false);
|
|
1351
1366
|
const nav = _before_navigate({ url, type, delta: popped?.delta, intent });
|
|
1352
1367
|
|
|
1353
1368
|
if (!nav) {
|
|
1354
1369
|
block();
|
|
1370
|
+
if (token === nav_token) token = prev_token;
|
|
1355
1371
|
return;
|
|
1356
1372
|
}
|
|
1357
1373
|
|
|
@@ -1367,7 +1383,6 @@ async function navigate({
|
|
|
1367
1383
|
stores.navigating.set((navigating.current = nav.navigation));
|
|
1368
1384
|
}
|
|
1369
1385
|
|
|
1370
|
-
token = nav_token;
|
|
1371
1386
|
let navigation_result = intent && (await load_route(intent));
|
|
1372
1387
|
|
|
1373
1388
|
if (!navigation_result) {
|
|
@@ -1621,6 +1636,8 @@ if (import.meta.hot) {
|
|
|
1621
1636
|
function setup_preload() {
|
|
1622
1637
|
/** @type {NodeJS.Timeout} */
|
|
1623
1638
|
let mousemove_timeout;
|
|
1639
|
+
/** @type {Element} */
|
|
1640
|
+
let current_a;
|
|
1624
1641
|
|
|
1625
1642
|
container.addEventListener('mousemove', (event) => {
|
|
1626
1643
|
const target = /** @type {Element} */ (event.target);
|
|
@@ -1656,9 +1673,11 @@ function setup_preload() {
|
|
|
1656
1673
|
* @param {Element} element
|
|
1657
1674
|
* @param {number} priority
|
|
1658
1675
|
*/
|
|
1659
|
-
function preload(element, priority) {
|
|
1676
|
+
async function preload(element, priority) {
|
|
1660
1677
|
const a = find_anchor(element, container);
|
|
1661
|
-
if (!a) return;
|
|
1678
|
+
if (!a || a === current_a) return;
|
|
1679
|
+
|
|
1680
|
+
current_a = a;
|
|
1662
1681
|
|
|
1663
1682
|
const { url, external, download } = get_link_info(a, base, app.hash);
|
|
1664
1683
|
if (external || download) return;
|
|
@@ -1670,7 +1689,7 @@ function setup_preload() {
|
|
|
1670
1689
|
|
|
1671
1690
|
if (!options.reload && !same_url) {
|
|
1672
1691
|
if (priority <= options.preload_data) {
|
|
1673
|
-
const intent = get_navigation_intent(url, false);
|
|
1692
|
+
const intent = await get_navigation_intent(url, false);
|
|
1674
1693
|
if (intent) {
|
|
1675
1694
|
if (DEV) {
|
|
1676
1695
|
_preload_data(intent).then((result) => {
|
|
@@ -1924,7 +1943,7 @@ export async function preloadData(href) {
|
|
|
1924
1943
|
}
|
|
1925
1944
|
|
|
1926
1945
|
const url = resolve_url(href);
|
|
1927
|
-
const intent = get_navigation_intent(url, false);
|
|
1946
|
+
const intent = await get_navigation_intent(url, false);
|
|
1928
1947
|
|
|
1929
1948
|
if (!intent) {
|
|
1930
1949
|
throw new Error(`Attempted to preload a URL that does not belong to this app: ${url}`);
|
|
@@ -1954,7 +1973,7 @@ export async function preloadData(href) {
|
|
|
1954
1973
|
* @param {string} pathname
|
|
1955
1974
|
* @returns {Promise<void>}
|
|
1956
1975
|
*/
|
|
1957
|
-
export function preloadCode(pathname) {
|
|
1976
|
+
export async function preloadCode(pathname) {
|
|
1958
1977
|
if (!BROWSER) {
|
|
1959
1978
|
throw new Error('Cannot call preloadCode(...) on the server');
|
|
1960
1979
|
}
|
|
@@ -1974,9 +1993,11 @@ export function preloadCode(pathname) {
|
|
|
1974
1993
|
);
|
|
1975
1994
|
}
|
|
1976
1995
|
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1996
|
+
if (__SVELTEKIT_CLIENT_ROUTING__) {
|
|
1997
|
+
const rerouted = get_rerouted_url(url);
|
|
1998
|
+
if (!rerouted || !routes.find((route) => route.exec(get_url_path(rerouted)))) {
|
|
1999
|
+
throw new Error(`'${pathname}' did not match any routes`);
|
|
2000
|
+
}
|
|
1980
2001
|
}
|
|
1981
2002
|
}
|
|
1982
2003
|
|
|
@@ -2474,16 +2495,31 @@ function _start_router() {
|
|
|
2474
2495
|
*/
|
|
2475
2496
|
async function _hydrate(
|
|
2476
2497
|
target,
|
|
2477
|
-
{ status = 200, error, node_ids, params, route, data: server_data_nodes, form }
|
|
2498
|
+
{ status = 200, error, node_ids, params, route, server_route, data: server_data_nodes, form }
|
|
2478
2499
|
) {
|
|
2479
2500
|
hydrated = true;
|
|
2480
2501
|
|
|
2481
2502
|
const url = new URL(location.href);
|
|
2482
2503
|
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2504
|
+
/** @type {import('types').CSRRoute | undefined} */
|
|
2505
|
+
let parsed_route;
|
|
2506
|
+
|
|
2507
|
+
if (__SVELTEKIT_CLIENT_ROUTING__) {
|
|
2508
|
+
if (!__SVELTEKIT_EMBEDDED__) {
|
|
2509
|
+
// See https://github.com/sveltejs/kit/pull/4935#issuecomment-1328093358 for one motivation
|
|
2510
|
+
// of determining the params on the client side.
|
|
2511
|
+
({ params = {}, route = { id: null } } = (await get_navigation_intent(url, false)) || {});
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
parsed_route = routes.find(({ id }) => id === route.id);
|
|
2515
|
+
} else {
|
|
2516
|
+
// undefined in case of 404
|
|
2517
|
+
if (server_route) {
|
|
2518
|
+
parsed_route = route = parse_server_route(server_route, app.nodes);
|
|
2519
|
+
} else {
|
|
2520
|
+
route = { id: null };
|
|
2521
|
+
params = {};
|
|
2522
|
+
}
|
|
2487
2523
|
}
|
|
2488
2524
|
|
|
2489
2525
|
/** @type {import('./types.js').NavigationFinished | undefined} */
|
|
@@ -2517,8 +2553,6 @@ async function _hydrate(
|
|
|
2517
2553
|
/** @type {Array<import('./types.js').BranchNode | undefined>} */
|
|
2518
2554
|
const branch = await Promise.all(branch_promises);
|
|
2519
2555
|
|
|
2520
|
-
const parsed_route = routes.find(({ id }) => id === route.id);
|
|
2521
|
-
|
|
2522
2556
|
// server-side will have compacted the branch, reinstate empty slots
|
|
2523
2557
|
// so that error boundaries can be lined up correctly
|
|
2524
2558
|
if (parsed_route) {
|
|
@@ -10,6 +10,7 @@ export function parse({ nodes, server_loads, dictionary, matchers }) {
|
|
|
10
10
|
return Object.entries(dictionary).map(([id, [leaf, layouts, errors]]) => {
|
|
11
11
|
const { pattern, params } = parse_route_id(id);
|
|
12
12
|
|
|
13
|
+
/** @type {import('types').CSRRoute} */
|
|
13
14
|
const route = {
|
|
14
15
|
id,
|
|
15
16
|
/** @param {string} path */
|
|
@@ -55,3 +56,22 @@ export function parse({ nodes, server_loads, dictionary, matchers }) {
|
|
|
55
56
|
return id === undefined ? id : [layouts_with_server_load.has(id), nodes[id]];
|
|
56
57
|
}
|
|
57
58
|
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {import('types').CSRRouteServer} input
|
|
62
|
+
* @param {import('types').CSRPageNodeLoader[]} app_nodes Will be modified if a new node is loaded that's not already in the array
|
|
63
|
+
* @returns {import('types').CSRRoute}
|
|
64
|
+
*/
|
|
65
|
+
export function parse_server_route({ nodes, id, leaf, layouts, errors }, app_nodes) {
|
|
66
|
+
return {
|
|
67
|
+
id,
|
|
68
|
+
exec: () => ({}), // dummy function; exec already happened on the server
|
|
69
|
+
// By writing to app_nodes only when a loader at that index is not already defined,
|
|
70
|
+
// we ensure that loaders have referential equality when they load the same node.
|
|
71
|
+
// Code elsewhere in client.js relies on this referential equality to determine
|
|
72
|
+
// if a loader is different and should therefore (re-)run.
|
|
73
|
+
errors: errors.map((n) => (n ? (app_nodes[n] ||= nodes[n]) : undefined)),
|
|
74
|
+
layouts: layouts.map((n) => (n ? [n[0], (app_nodes[n[1]] ||= nodes[n[1]])] : undefined)),
|
|
75
|
+
leaf: [leaf[0], (app_nodes[leaf[1]] ||= nodes[leaf[1]])]
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
CSRPageNode,
|
|
5
5
|
CSRPageNodeLoader,
|
|
6
6
|
CSRRoute,
|
|
7
|
+
CSRRouteServer,
|
|
7
8
|
ServerDataNode,
|
|
8
9
|
TrailingSlash,
|
|
9
10
|
Uses
|
|
@@ -12,13 +13,18 @@ import { Page, ParamMatcher } from '@sveltejs/kit';
|
|
|
12
13
|
|
|
13
14
|
export interface SvelteKitApp {
|
|
14
15
|
/**
|
|
15
|
-
* A list of all the error/layout/page nodes used in the app
|
|
16
|
+
* A list of all the error/layout/page nodes used in the app.
|
|
17
|
+
* - In case of router.resolution=client, this is filled completely upfront.
|
|
18
|
+
* - In case of router.resolution=server, this is filled with the root layout and root error page
|
|
19
|
+
* at the beginning and then filled up as the user navigates around the app, loading new nodes
|
|
16
20
|
*/
|
|
17
21
|
nodes: CSRPageNodeLoader[];
|
|
18
22
|
|
|
19
23
|
/**
|
|
20
24
|
* A list of all layout node ids that have a server load function.
|
|
21
25
|
* Pages are not present because it's shorter to encode it on the leaf itself.
|
|
26
|
+
*
|
|
27
|
+
* In case of router.resolution=server, this only contains one entry for the root layout.
|
|
22
28
|
*/
|
|
23
29
|
server_loads: number[];
|
|
24
30
|
|
|
@@ -27,9 +33,16 @@ export interface SvelteKitApp {
|
|
|
27
33
|
* is parsed into an array of routes on startup. The numbers refer to the indices in `nodes`.
|
|
28
34
|
* If the leaf number is negative, it means it does use a server load function and the complement is the node index.
|
|
29
35
|
* The route layout and error nodes are not referenced, they are always number 0 and 1 and always apply.
|
|
36
|
+
*
|
|
37
|
+
* In case of router.resolution=server, this object is empty, as resolution happens on the server.
|
|
30
38
|
*/
|
|
31
39
|
dictionary: Record<string, [leaf: number, layouts: number[], errors?: number[]]>;
|
|
32
40
|
|
|
41
|
+
/**
|
|
42
|
+
* A map of `[matcherName: string]: (..) => boolean`, which is used to match route parameters.
|
|
43
|
+
*
|
|
44
|
+
* In case of router.resolution=server, this object is empty, as resolution happens on the server.
|
|
45
|
+
*/
|
|
33
46
|
matchers: Record<string, ParamMatcher>;
|
|
34
47
|
|
|
35
48
|
hooks: ClientHooks;
|
|
@@ -108,6 +121,8 @@ export interface HydrateOptions {
|
|
|
108
121
|
node_ids: number[];
|
|
109
122
|
params: Record<string, string>;
|
|
110
123
|
route: { id: string | null };
|
|
124
|
+
/** Only used when `router.resolution=server`; can then still be undefined in case of 404 */
|
|
125
|
+
server_route?: CSRRouteServer;
|
|
111
126
|
data: Array<ServerDataNode | null>;
|
|
112
127
|
form: Record<string, any> | null;
|
|
113
128
|
}
|
|
@@ -130,6 +130,12 @@ export function get_link_info(a, base, uses_hash_router) {
|
|
|
130
130
|
|
|
131
131
|
try {
|
|
132
132
|
url = new URL(a instanceof SVGAElement ? a.href.baseVal : a.href, document.baseURI);
|
|
133
|
+
|
|
134
|
+
// if the hash doesn't start with `#/` then it's probably linking to an id on the current page
|
|
135
|
+
if (uses_hash_router && url.hash.match(/^#[^/]/)) {
|
|
136
|
+
const route = location.hash.split('#')[1] || '/';
|
|
137
|
+
url.hash = `#${route}${url.hash}`;
|
|
138
|
+
}
|
|
133
139
|
} catch {}
|
|
134
140
|
|
|
135
141
|
const target = a instanceof SVGAElement ? a.target.baseVal : a.target;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { base, app_dir } from '__sveltekit/paths';
|
|
2
|
+
|
|
3
|
+
const DATA_SUFFIX = '/__data.json';
|
|
4
|
+
const HTML_DATA_SUFFIX = '.html__data.json';
|
|
5
|
+
|
|
6
|
+
/** @param {string} pathname */
|
|
7
|
+
export function has_data_suffix(pathname) {
|
|
8
|
+
return pathname.endsWith(DATA_SUFFIX) || pathname.endsWith(HTML_DATA_SUFFIX);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** @param {string} pathname */
|
|
12
|
+
export function add_data_suffix(pathname) {
|
|
13
|
+
if (pathname.endsWith('.html')) return pathname.replace(/\.html$/, HTML_DATA_SUFFIX);
|
|
14
|
+
return pathname.replace(/\/$/, '') + DATA_SUFFIX;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** @param {string} pathname */
|
|
18
|
+
export function strip_data_suffix(pathname) {
|
|
19
|
+
if (pathname.endsWith(HTML_DATA_SUFFIX)) {
|
|
20
|
+
return pathname.slice(0, -HTML_DATA_SUFFIX.length) + '.html';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return pathname.slice(0, -DATA_SUFFIX.length);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ROUTE_PREFIX = `${base}/${app_dir}/route`;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} pathname
|
|
30
|
+
* @returns {boolean}
|
|
31
|
+
*/
|
|
32
|
+
export function has_resolution_prefix(pathname) {
|
|
33
|
+
return pathname === `${ROUTE_PREFIX}.js` || pathname.startsWith(`${ROUTE_PREFIX}/`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Convert a regular URL to a route to send to SvelteKit's server-side route resolution endpoint
|
|
38
|
+
* @param {string} pathname
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
export function add_resolution_prefix(pathname) {
|
|
42
|
+
let normalized = pathname.slice(base.length);
|
|
43
|
+
if (normalized.endsWith('/')) normalized = normalized.slice(0, -1);
|
|
44
|
+
|
|
45
|
+
return `${ROUTE_PREFIX}${normalized}.js`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {string} pathname
|
|
50
|
+
* @returns {string}
|
|
51
|
+
*/
|
|
52
|
+
export function strip_resolution_prefix(pathname) {
|
|
53
|
+
return base + (pathname.slice(ROUTE_PREFIX.length, -3) || '/');
|
|
54
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parse, serialize } from 'cookie';
|
|
2
|
-
import {
|
|
2
|
+
import { normalize_path, resolve } from '../../utils/url.js';
|
|
3
|
+
import { add_data_suffix } from '../pathname.js';
|
|
3
4
|
|
|
4
5
|
// eslint-disable-next-line no-control-regex -- control characters are invalid in cookie names
|
|
5
6
|
const INVALID_COOKIE_CHARACTER_REGEX = /[\x00-\x1F\x7F()<>@,;:"/[\]?={} \t]/;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { DEV } from 'esm-env';
|
|
2
1
|
import { ENDPOINT_METHODS, PAGE_METHODS } from '../../constants.js';
|
|
3
2
|
import { negotiate } from '../../utils/http.js';
|
|
4
3
|
import { Redirect } from '../control.js';
|
|
@@ -11,10 +10,6 @@ import { method_not_allowed } from './utils.js';
|
|
|
11
10
|
* @returns {Promise<Response>}
|
|
12
11
|
*/
|
|
13
12
|
export async function render_endpoint(event, mod, state) {
|
|
14
|
-
if (DEV && event.request.headers.get('x-sveltekit-action') === 'true') {
|
|
15
|
-
throw new Error('use:enhance should only be used with SvelteKit form actions');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
13
|
const method = /** @type {import('types').HttpMethod} */ (event.request.method);
|
|
19
14
|
|
|
20
15
|
let handler = mod[method] || mod.fallback;
|
|
@@ -112,6 +112,18 @@ export function create_fetch({ event, options, manifest, state, get_cookie_heade
|
|
|
112
112
|
return await fetch(request);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
if (
|
|
116
|
+
manifest._.prerendered_routes.has(decoded) ||
|
|
117
|
+
(decoded.at(-1) === '/' && manifest._.prerendered_routes.has(decoded.slice(0, -1)))
|
|
118
|
+
) {
|
|
119
|
+
// The path of something prerendered could match a different route
|
|
120
|
+
// that is still in the manifest, leading to the wrong route being loaded.
|
|
121
|
+
// We therefore bail early here. The prerendered logic is different for
|
|
122
|
+
// each adapter, (except maybe for prerendered redirects)
|
|
123
|
+
// so we need to make an actual HTTP request.
|
|
124
|
+
return await fetch(request);
|
|
125
|
+
}
|
|
126
|
+
|
|
115
127
|
if (credentials !== 'omit') {
|
|
116
128
|
const cookie = get_cookie_header(url, request.headers.get('cookie'));
|
|
117
129
|
if (cookie) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { text } from '../../../exports/index.js';
|
|
2
2
|
import { compact } from '../../../utils/array.js';
|
|
3
3
|
import { get_status, normalize_error } from '../../../utils/error.js';
|
|
4
|
-
import { add_data_suffix } from '
|
|
4
|
+
import { add_data_suffix } from '../../pathname.js';
|
|
5
5
|
import { Redirect } from '../../control.js';
|
|
6
6
|
import { redirect_response, static_error_page, handle_error_and_jsonify } from '../utils.js';
|
|
7
7
|
import {
|
|
@@ -13,6 +13,8 @@ import { text } from '../../../exports/index.js';
|
|
|
13
13
|
import { create_async_iterator } from '../../../utils/streaming.js';
|
|
14
14
|
import { SVELTE_KIT_ASSETS } from '../../../constants.js';
|
|
15
15
|
import { SCHEME } from '../../../utils/url.js';
|
|
16
|
+
import { create_server_routing_response, generate_route_object } from './server_routing.js';
|
|
17
|
+
import { add_resolution_prefix } from '../../pathname.js';
|
|
16
18
|
|
|
17
19
|
// TODO rename this function/module
|
|
18
20
|
|
|
@@ -297,8 +299,10 @@ export async function render_response({
|
|
|
297
299
|
}
|
|
298
300
|
|
|
299
301
|
if (page_config.csr) {
|
|
302
|
+
const route = manifest._.client.routes?.find((r) => r.id === event.route.id) ?? null;
|
|
303
|
+
|
|
300
304
|
if (client.uses_env_dynamic_public && state.prerendering) {
|
|
301
|
-
modulepreloads.add(`${
|
|
305
|
+
modulepreloads.add(`${paths.app_dir}/env.js`);
|
|
302
306
|
}
|
|
303
307
|
|
|
304
308
|
if (!client.inline) {
|
|
@@ -317,6 +321,16 @@ export async function render_response({
|
|
|
317
321
|
}
|
|
318
322
|
}
|
|
319
323
|
|
|
324
|
+
// prerender a `/_app/route/path/to/page.js` module
|
|
325
|
+
if (manifest._.client.routes && state.prerendering && !state.prerendering.fallback) {
|
|
326
|
+
const pathname = add_resolution_prefix(event.url.pathname);
|
|
327
|
+
|
|
328
|
+
state.prerendering.dependencies.set(
|
|
329
|
+
pathname,
|
|
330
|
+
create_server_routing_response(route, event.params, new URL(pathname, event.url), manifest)
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
320
334
|
const blocks = [];
|
|
321
335
|
|
|
322
336
|
// when serving a prerendered page in an app that uses $env/dynamic/public, we must
|
|
@@ -394,7 +408,15 @@ export async function render_response({
|
|
|
394
408
|
hydrate.push(`status: ${status}`);
|
|
395
409
|
}
|
|
396
410
|
|
|
397
|
-
if (
|
|
411
|
+
if (manifest._.client.routes) {
|
|
412
|
+
if (route) {
|
|
413
|
+
const stringified = generate_route_object(route, event.url, manifest).replaceAll(
|
|
414
|
+
'\n',
|
|
415
|
+
'\n\t\t\t\t\t\t\t'
|
|
416
|
+
); // make output after it's put together with the rest more readable
|
|
417
|
+
hydrate.push(`params: ${devalue.uneval(event.params)}`, `server_route: ${stringified}`);
|
|
418
|
+
}
|
|
419
|
+
} else if (options.embedded) {
|
|
398
420
|
hydrate.push(`params: ${devalue.uneval(event.params)}`, `route: ${s(event.route)}`);
|
|
399
421
|
}
|
|
400
422
|
|
|
@@ -419,7 +441,7 @@ export async function render_response({
|
|
|
419
441
|
});`;
|
|
420
442
|
|
|
421
443
|
if (load_env_eagerly) {
|
|
422
|
-
blocks.push(`import(${s(`${base}/${
|
|
444
|
+
blocks.push(`import(${s(`${base}/${paths.app_dir}/env.js`)}).then(({ env }) => {
|
|
423
445
|
${global}.env = env;
|
|
424
446
|
|
|
425
447
|
${boot.replace(/\n/g, '\n\t')}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { base, assets } from '__sveltekit/paths';
|
|
2
|
+
import { text } from '../../../exports/index.js';
|
|
3
|
+
import { s } from '../../../utils/misc.js';
|
|
4
|
+
import { exec } from '../../../utils/routing.js';
|
|
5
|
+
import { decode_params } from '../../../utils/url.js';
|
|
6
|
+
import { get_relative_path } from '../../utils.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {import('types').SSRClientRoute} route
|
|
10
|
+
* @param {URL} url
|
|
11
|
+
* @param {import('@sveltejs/kit').SSRManifest} manifest
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
export function generate_route_object(route, url, manifest) {
|
|
15
|
+
const { errors, layouts, leaf } = route;
|
|
16
|
+
|
|
17
|
+
const nodes = [...errors, ...layouts.map((l) => l?.[1]), leaf[1]]
|
|
18
|
+
.filter((n) => typeof n === 'number')
|
|
19
|
+
.map((n) => `'${n}': () => ${create_client_import(manifest._.client.nodes?.[n], url)}`)
|
|
20
|
+
.join(',\n\t\t');
|
|
21
|
+
|
|
22
|
+
// stringified version of
|
|
23
|
+
/** @type {import('types').CSRRouteServer} */
|
|
24
|
+
return [
|
|
25
|
+
`{\n\tid: ${s(route.id)}`,
|
|
26
|
+
`errors: ${s(route.errors)}`,
|
|
27
|
+
`layouts: ${s(route.layouts)}`,
|
|
28
|
+
`leaf: ${s(route.leaf)}`,
|
|
29
|
+
`nodes: {\n\t\t${nodes}\n\t}\n}`
|
|
30
|
+
].join(',\n\t');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {string | undefined} import_path
|
|
35
|
+
* @param {URL} url
|
|
36
|
+
*/
|
|
37
|
+
function create_client_import(import_path, url) {
|
|
38
|
+
if (!import_path) return 'Promise.resolve({})';
|
|
39
|
+
|
|
40
|
+
// During DEV, Vite will make the paths absolute (e.g. /@fs/...)
|
|
41
|
+
if (import_path[0] === '/') {
|
|
42
|
+
return `import('${import_path}')`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// During PROD, they're root-relative
|
|
46
|
+
if (assets !== '') {
|
|
47
|
+
return `import('${assets}/${import_path}')`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Else we make them relative to the server-side route resolution request
|
|
51
|
+
// to support IPFS, the internet archive, etc.
|
|
52
|
+
let path = get_relative_path(url.pathname, `${base}/${import_path}`);
|
|
53
|
+
if (path[0] !== '.') path = `./${path}`;
|
|
54
|
+
return `import('${path}')`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {string} resolved_path
|
|
59
|
+
* @param {URL} url
|
|
60
|
+
* @param {import('@sveltejs/kit').SSRManifest} manifest
|
|
61
|
+
* @returns {Promise<Response>}
|
|
62
|
+
*/
|
|
63
|
+
export async function resolve_route(resolved_path, url, manifest) {
|
|
64
|
+
if (!manifest._.client.routes) {
|
|
65
|
+
return text('Server-side route resolution disabled', { status: 400 });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** @type {import('types').SSRClientRoute | null} */
|
|
69
|
+
let route = null;
|
|
70
|
+
/** @type {Record<string, string>} */
|
|
71
|
+
let params = {};
|
|
72
|
+
|
|
73
|
+
const matchers = await manifest._.matchers();
|
|
74
|
+
|
|
75
|
+
for (const candidate of manifest._.client.routes) {
|
|
76
|
+
const match = candidate.pattern.exec(resolved_path);
|
|
77
|
+
if (!match) continue;
|
|
78
|
+
|
|
79
|
+
const matched = exec(match, candidate.params, matchers);
|
|
80
|
+
if (matched) {
|
|
81
|
+
route = candidate;
|
|
82
|
+
params = decode_params(matched);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return create_server_routing_response(route, params, url, manifest).response;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {import('types').SSRClientRoute | null} route
|
|
92
|
+
* @param {Partial<Record<string, string>>} params
|
|
93
|
+
* @param {URL} url
|
|
94
|
+
* @param {import('@sveltejs/kit').SSRManifest} manifest
|
|
95
|
+
* @returns {{response: Response, body: string}}
|
|
96
|
+
*/
|
|
97
|
+
export function create_server_routing_response(route, params, url, manifest) {
|
|
98
|
+
const headers = new Headers({
|
|
99
|
+
'content-type': 'application/javascript; charset=utf-8'
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (route) {
|
|
103
|
+
const csr_route = generate_route_object(route, url, manifest);
|
|
104
|
+
const body = `export const route = ${csr_route}; export const params = ${JSON.stringify(params)};`;
|
|
105
|
+
|
|
106
|
+
return { response: text(body, { headers }), body };
|
|
107
|
+
} else {
|
|
108
|
+
return { response: text('', { headers }), body: '' };
|
|
109
|
+
}
|
|
110
|
+
}
|