@sveltejs/kit 1.4.0 → 1.5.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": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -22,7 +22,7 @@
22
22
  "set-cookie-parser": "^2.5.1",
23
23
  "sirv": "^2.0.2",
24
24
  "tiny-glob": "^0.2.9",
25
- "undici": "5.16.0"
25
+ "undici": "5.18.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@playwright/test": "^1.29.2",
@@ -18,13 +18,54 @@ const pipe = promisify(pipeline);
18
18
  * config: import('types').ValidatedConfig;
19
19
  * build_data: import('types').BuildData;
20
20
  * server_metadata: import('types').ServerMetadata;
21
- * routes: import('types').RouteData[];
21
+ * route_data: import('types').RouteData[];
22
22
  * prerendered: import('types').Prerendered;
23
+ * prerender_map: import('types').PrerenderMap;
23
24
  * log: import('types').Logger;
24
25
  * }} opts
25
26
  * @returns {import('types').Builder}
26
27
  */
27
- export function create_builder({ config, build_data, server_metadata, routes, prerendered, log }) {
28
+ export function create_builder({
29
+ config,
30
+ build_data,
31
+ server_metadata,
32
+ route_data,
33
+ prerendered,
34
+ prerender_map,
35
+ log
36
+ }) {
37
+ /** @type {Map<import('types').RouteDefinition, import('types').RouteData>} */
38
+ const lookup = new Map();
39
+
40
+ /**
41
+ * Rather than exposing the internal `RouteData` type, which is subject to change,
42
+ * we expose a stable type that adapters can use to group/filter routes
43
+ */
44
+ const routes = route_data.map((route) => {
45
+ const methods =
46
+ /** @type {import('types').HttpMethod[]} */
47
+ (server_metadata.routes.get(route.id)?.methods);
48
+ const config = server_metadata.routes.get(route.id)?.config;
49
+
50
+ /** @type {import('types').RouteDefinition} */
51
+ const facade = {
52
+ id: route.id,
53
+ segments: get_route_segments(route.id).map((segment) => ({
54
+ dynamic: segment.includes('['),
55
+ rest: segment.includes('[...'),
56
+ content: segment
57
+ })),
58
+ pattern: route.pattern,
59
+ prerender: prerender_map.get(route.id) ?? false,
60
+ methods,
61
+ config
62
+ };
63
+
64
+ lookup.set(facade, route);
65
+
66
+ return facade;
67
+ });
68
+
28
69
  return {
29
70
  log,
30
71
  rimraf,
@@ -33,6 +74,7 @@ export function create_builder({ config, build_data, server_metadata, routes, pr
33
74
 
34
75
  config,
35
76
  prerendered,
77
+ routes,
36
78
 
37
79
  async compress(directory) {
38
80
  if (!existsSync(directory)) {
@@ -52,29 +94,12 @@ export function create_builder({ config, build_data, server_metadata, routes, pr
52
94
  },
53
95
 
54
96
  async createEntries(fn) {
55
- /** @type {import('types').RouteDefinition[]} */
56
- const facades = routes.map((route) => {
57
- const methods =
58
- /** @type {import('types').HttpMethod[]} */
59
- (server_metadata.routes.get(route.id)?.methods);
60
-
61
- return {
62
- id: route.id,
63
- segments: get_route_segments(route.id).map((segment) => ({
64
- dynamic: segment.includes('['),
65
- rest: segment.includes('[...'),
66
- content: segment
67
- })),
68
- pattern: route.pattern,
69
- methods
70
- };
71
- });
72
-
73
97
  const seen = new Set();
74
98
 
75
- for (let i = 0; i < routes.length; i += 1) {
76
- const route = routes[i];
77
- const { id, filter, complete } = fn(facades[i]);
99
+ for (let i = 0; i < route_data.length; i += 1) {
100
+ const route = route_data[i];
101
+ if (prerender_map.get(route.id) === true) continue;
102
+ const { id, filter, complete } = fn(routes[i]);
78
103
 
79
104
  if (seen.has(id)) continue;
80
105
  seen.add(id);
@@ -82,9 +107,10 @@ export function create_builder({ config, build_data, server_metadata, routes, pr
82
107
  const group = [route];
83
108
 
84
109
  // figure out which lower priority routes should be considered fallbacks
85
- for (let j = i + 1; j < routes.length; j += 1) {
86
- if (filter(facades[j])) {
87
- group.push(routes[j]);
110
+ for (let j = i + 1; j < route_data.length; j += 1) {
111
+ if (prerender_map.get(routes[j].id) === true) continue;
112
+ if (filter(routes[j])) {
113
+ group.push(route_data[j]);
88
114
  }
89
115
  }
90
116
 
@@ -95,7 +121,7 @@ export function create_builder({ config, build_data, server_metadata, routes, pr
95
121
  // TODO is this still necessary, given the new way of doing things?
96
122
  filtered.forEach((route) => {
97
123
  if (route.page) {
98
- const endpoint = routes.find((candidate) => candidate.id === route.id + '.json');
124
+ const endpoint = route_data.find((candidate) => candidate.id === route.id + '.json');
99
125
 
100
126
  if (endpoint) {
101
127
  filtered.add(endpoint);
@@ -143,11 +169,13 @@ export function create_builder({ config, build_data, server_metadata, routes, pr
143
169
  });
144
170
  },
145
171
 
146
- generateManifest: ({ relativePath }) => {
172
+ generateManifest: ({ relativePath, routes: subset }) => {
147
173
  return generate_manifest({
148
174
  build_data,
149
175
  relative_path: relativePath,
150
- routes
176
+ routes: subset
177
+ ? subset.map((route) => /** @type {import('types').RouteData} */ (lookup.get(route)))
178
+ : route_data
151
179
  });
152
180
  },
153
181
 
@@ -18,13 +18,9 @@ export async function adapt(config, build_data, server_metadata, prerendered, pr
18
18
  config,
19
19
  build_data,
20
20
  server_metadata,
21
- routes: build_data.manifest_data.routes.filter((route) => {
22
- if (!route.page && !route.endpoint) return false;
23
-
24
- const prerender = prerender_map.get(route.id);
25
- return prerender === false || prerender === undefined || prerender === 'auto';
26
- }),
21
+ route_data: build_data.manifest_data.routes.filter((route) => route.page || route.endpoint),
27
22
  prerendered,
23
+ prerender_map,
28
24
  log
29
25
  });
30
26
  await adapt(builder);
@@ -67,6 +67,8 @@ async function analyse({ manifest_path, env }) {
67
67
 
68
68
  /** @type {import('types').PrerenderOption | undefined} */
69
69
  let prerender = undefined;
70
+ /** @type {any} */
71
+ let config = undefined;
70
72
 
71
73
  if (route.endpoint) {
72
74
  const mod = await route.endpoint();
@@ -123,13 +125,35 @@ async function analyse({ manifest_path, env }) {
123
125
  (should_prerender !== false && get_option(nodes, 'ssr') === false && !page?.server?.actions
124
126
  ? 'auto'
125
127
  : should_prerender ?? false);
128
+
129
+ config = get_config(nodes);
126
130
  }
127
131
 
128
132
  metadata.routes.set(route.id, {
129
133
  prerender,
134
+ config,
130
135
  methods: Array.from(methods)
131
136
  });
132
137
  }
133
138
 
134
139
  return metadata;
135
140
  }
141
+
142
+ /**
143
+ * Do a shallow merge (first level) of the config object
144
+ * @param {Array<import('types').SSRNode | undefined>} nodes
145
+ */
146
+ function get_config(nodes) {
147
+ let current = {};
148
+ for (const node of nodes) {
149
+ const config = node?.universal?.config ?? node?.server?.config;
150
+ if (config) {
151
+ current = {
152
+ ...current,
153
+ ...config
154
+ };
155
+ }
156
+ }
157
+
158
+ return Object.keys(current).length ? current : undefined;
159
+ }
@@ -21,16 +21,16 @@ export function write_root(manifest_data, output) {
21
21
 
22
22
  let l = max_depth;
23
23
 
24
- let pyramid = `<svelte:component this={components[${l}]} data={data_${l}} {form} />`;
24
+ let pyramid = `<svelte:component this={constructors[${l}]} bind:this={components[${l}]} data={data_${l}} {form} />`;
25
25
 
26
26
  while (l--) {
27
27
  pyramid = `
28
- {#if components[${l + 1}]}
29
- <svelte:component this={components[${l}]} data={data_${l}}>
28
+ {#if constructors[${l + 1}]}
29
+ <svelte:component this={constructors[${l}]} bind:this={components[${l}]} data={data_${l}}>
30
30
  ${pyramid.replace(/\n/g, '\n\t\t\t\t\t')}
31
31
  </svelte:component>
32
32
  {:else}
33
- <svelte:component this={components[${l}]} data={data_${l}} {form} />
33
+ <svelte:component this={constructors[${l}]} bind:this={components[${l}]} data={data_${l}} {form} />
34
34
  {/if}
35
35
  `
36
36
  .replace(/^\t\t\t/gm, '')
@@ -49,7 +49,8 @@ export function write_root(manifest_data, output) {
49
49
  export let stores;
50
50
  export let page;
51
51
 
52
- export let components;
52
+ export let constructors;
53
+ export let components = [];
53
54
  export let form;
54
55
  ${levels.map((l) => `export let data_${l} = null;`).join('\n\t\t\t\t')}
55
56
 
@@ -198,23 +198,26 @@ function update_types(config, routes, route, to_delete = new Set()) {
198
198
 
199
199
  // These could also be placed in our public types, but it would bloat them unnecessarily and we may want to change these in the future
200
200
  if (route.layout || route.leaf) {
201
- // If T extends the empty object, void is also allowed as a return type
202
- declarations.push(`type MaybeWithVoid<T> = {} extends T ? T | void : T;`);
203
- // Returns the key of the object whose values are required.
204
201
  declarations.push(
205
- `export type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];`
206
- );
207
- // Helper type to get the correct output type for load functions. It should be passed the parent type to check what types from App.PageData are still required.
208
- // If none, void is also allowed as a return type.
209
- declarations.push(
210
- `type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>`
211
- );
212
- // null & {} == null, we need to prevent that in some situations
213
- declarations.push(`type EnsureDefined<T> = T extends null | undefined ? {} : T;`);
214
- // Takes a union type and returns a union type where each type also has all properties
215
- // of all possible types (typed as undefined), making accessing them more ergonomic
216
- declarations.push(
217
- `type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;`
202
+ // If T extends the empty object, void is also allowed as a return type
203
+ `type MaybeWithVoid<T> = {} extends T ? T | void : T;`,
204
+
205
+ // Returns the key of the object whose values are required.
206
+ `export type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];`,
207
+
208
+ // Helper type to get the correct output type for load functions. It should be passed the parent type to check what types from App.PageData are still required.
209
+ // If none, void is also allowed as a return type.
210
+ `type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>`,
211
+
212
+ // null & {} == null, we need to prevent that in some situations
213
+ `type EnsureDefined<T> = T extends null | undefined ? {} : T;`,
214
+
215
+ // Takes a union type and returns a union type where each type also has all properties
216
+ // of all possible types (typed as undefined), making accessing them more ergonomic
217
+ `type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;`,
218
+
219
+ // Re-export `Snapshot` from @sveltejs/kit — in future we could use this to infer <T> from the return type of `snapshot.capture`
220
+ `export type Snapshot<T = any> = Kit.Snapshot<T>;`
218
221
  );
219
222
  }
220
223
 
@@ -15,6 +15,7 @@ import {
15
15
  is_external_url,
16
16
  scroll_state
17
17
  } from './utils.js';
18
+ import * as storage from './session-storage.js';
18
19
  import {
19
20
  lock_fetch,
20
21
  unlock_fetch,
@@ -31,7 +32,7 @@ import { HttpError, Redirect } from '../control.js';
31
32
  import { stores } from './singletons.js';
32
33
  import { unwrap_promises } from '../../utils/promises.js';
33
34
  import * as devalue from 'devalue';
34
- import { INDEX_KEY, PRELOAD_PRIORITIES, SCROLL_KEY } from './constants.js';
35
+ import { INDEX_KEY, PRELOAD_PRIORITIES, SCROLL_KEY, SNAPSHOT_KEY } from './constants.js';
35
36
  import { validate_common_exports } from '../../utils/exports.js';
36
37
  import { compact } from '../../utils/array.js';
37
38
 
@@ -52,12 +53,10 @@ default_error_loader();
52
53
 
53
54
  /** @typedef {{ x: number, y: number }} ScrollPosition */
54
55
  /** @type {Record<number, ScrollPosition>} */
55
- let scroll_positions = {};
56
- try {
57
- scroll_positions = JSON.parse(sessionStorage[SCROLL_KEY]);
58
- } catch {
59
- // do nothing
60
- }
56
+ const scroll_positions = storage.get(SCROLL_KEY) ?? {};
57
+
58
+ /** @type {Record<string, any[]>} */
59
+ const snapshots = storage.get(SNAPSHOT_KEY) ?? {};
61
60
 
62
61
  /** @param {number} index */
63
62
  function update_scroll_positions(index) {
@@ -75,6 +74,14 @@ export function create_client({ target }) {
75
74
  /** @type {Array<((url: URL) => boolean)>} */
76
75
  const invalidated = [];
77
76
 
77
+ /**
78
+ * An array of the `+layout.svelte` and `+page.svelte` component instances
79
+ * that currently live on the page — used for capturing and restoring snapshots.
80
+ * It's updated/manipulated through `bind:this` in `Root.svelte`.
81
+ * @type {import('svelte').SvelteComponent[]}
82
+ */
83
+ const components = [];
84
+
78
85
  /** @type {{id: string, promise: Promise<import('./types').NavigationResult>} | null} */
79
86
  let load_cache = null;
80
87
 
@@ -158,6 +165,20 @@ export function create_client({ target }) {
158
165
  await update(intent, url, []);
159
166
  }
160
167
 
168
+ /** @param {number} index */
169
+ function capture_snapshot(index) {
170
+ if (components.some((c) => c?.snapshot)) {
171
+ snapshots[index] = components.map((c) => c?.snapshot?.capture());
172
+ }
173
+ }
174
+
175
+ /** @param {number} index */
176
+ function restore_snapshot(index) {
177
+ snapshots[index]?.forEach((value, i) => {
178
+ components[i]?.snapshot?.restore(value);
179
+ });
180
+ }
181
+
161
182
  /**
162
183
  * @param {string | URL} url
163
184
  * @param {{ noScroll?: boolean; replaceState?: boolean; keepFocus?: boolean; state?: any; invalidateAll?: boolean }} opts
@@ -238,11 +259,20 @@ export function create_client({ target }) {
238
259
  * @param {import('./types').NavigationIntent | undefined} intent
239
260
  * @param {URL} url
240
261
  * @param {string[]} redirect_chain
262
+ * @param {number} [previous_history_index]
241
263
  * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean, details: { replaceState: boolean, state: any } | null}} [opts]
242
264
  * @param {{}} [nav_token] To distinguish between different navigation events and determine the latest. Needed for example for redirects to keep the original token
243
265
  * @param {() => void} [callback]
244
266
  */
245
- async function update(intent, url, redirect_chain, opts, nav_token = {}, callback) {
267
+ async function update(
268
+ intent,
269
+ url,
270
+ redirect_chain,
271
+ previous_history_index,
272
+ opts,
273
+ nav_token = {},
274
+ callback
275
+ ) {
246
276
  token = nav_token;
247
277
  let navigation_result = intent && (await load_route(intent));
248
278
 
@@ -301,11 +331,28 @@ export function create_client({ target }) {
301
331
 
302
332
  updating = true;
303
333
 
334
+ // `previous_history_index` will be undefined for invalidation
335
+ if (previous_history_index) {
336
+ update_scroll_positions(previous_history_index);
337
+ capture_snapshot(previous_history_index);
338
+ }
339
+
304
340
  if (opts && opts.details) {
305
341
  const { details } = opts;
306
342
  const change = details.replaceState ? 0 : 1;
307
343
  details.state[INDEX_KEY] = current_history_index += change;
308
344
  history[details.replaceState ? 'replaceState' : 'pushState'](details.state, '', url);
345
+
346
+ if (!details.replaceState) {
347
+ // if we navigated back, then pushed a new state, we can
348
+ // release memory by pruning the scroll/snapshot lookup
349
+ let i = current_history_index + 1;
350
+ while (snapshots[i] || scroll_positions[i]) {
351
+ delete snapshots[i];
352
+ delete scroll_positions[i];
353
+ i += 1;
354
+ }
355
+ }
309
356
  }
310
357
 
311
358
  // reset preload synchronously after the history state has been set to avoid race conditions
@@ -384,10 +431,12 @@ export function create_client({ target }) {
384
431
 
385
432
  root = new Root({
386
433
  target,
387
- props: { ...result.props, stores },
434
+ props: { ...result.props, stores, components },
388
435
  hydrate: true
389
436
  });
390
437
 
438
+ restore_snapshot(current_history_index);
439
+
391
440
  /** @type {import('types').AfterNavigate} */
392
441
  const navigation = {
393
442
  from: null,
@@ -445,7 +494,7 @@ export function create_client({ target }) {
445
494
  },
446
495
  props: {
447
496
  // @ts-ignore Somehow it's getting SvelteComponent and SvelteComponentDev mixed up
448
- components: compact(branch).map((branch_node) => branch_node.node.component)
497
+ constructors: compact(branch).map((branch_node) => branch_node.node.component)
449
498
  }
450
499
  };
451
500
 
@@ -1091,7 +1140,8 @@ export function create_client({ target }) {
1091
1140
  return;
1092
1141
  }
1093
1142
 
1094
- update_scroll_positions(current_history_index);
1143
+ // store this before calling `accepted()`, which may change the index
1144
+ const previous_history_index = current_history_index;
1095
1145
 
1096
1146
  accepted();
1097
1147
 
@@ -1105,6 +1155,7 @@ export function create_client({ target }) {
1105
1155
  intent,
1106
1156
  url,
1107
1157
  redirect_chain,
1158
+ previous_history_index,
1108
1159
  {
1109
1160
  scroll,
1110
1161
  keepfocus,
@@ -1385,12 +1436,10 @@ export function create_client({ target }) {
1385
1436
  addEventListener('visibilitychange', () => {
1386
1437
  if (document.visibilityState === 'hidden') {
1387
1438
  update_scroll_positions(current_history_index);
1439
+ storage.set(SCROLL_KEY, scroll_positions);
1388
1440
 
1389
- try {
1390
- sessionStorage[SCROLL_KEY] = JSON.stringify(scroll_positions);
1391
- } catch {
1392
- // do nothing
1393
- }
1441
+ capture_snapshot(current_history_index);
1442
+ storage.set(SNAPSHOT_KEY, snapshots);
1394
1443
  }
1395
1444
  });
1396
1445
 
@@ -1537,7 +1586,7 @@ export function create_client({ target }) {
1537
1586
  });
1538
1587
  });
1539
1588
 
1540
- addEventListener('popstate', (event) => {
1589
+ addEventListener('popstate', async (event) => {
1541
1590
  if (event.state?.[INDEX_KEY]) {
1542
1591
  // if a popstate-driven navigation is cancelled, we need to counteract it
1543
1592
  // with history.go, which means we end up back here, hence this check
@@ -1555,8 +1604,9 @@ export function create_client({ target }) {
1555
1604
  }
1556
1605
 
1557
1606
  const delta = event.state[INDEX_KEY] - current_history_index;
1607
+ let blocked = false;
1558
1608
 
1559
- navigate({
1609
+ await navigate({
1560
1610
  url: new URL(location.href),
1561
1611
  scroll,
1562
1612
  keepfocus: false,
@@ -1567,10 +1617,15 @@ export function create_client({ target }) {
1567
1617
  },
1568
1618
  blocked: () => {
1569
1619
  history.go(-delta);
1620
+ blocked = true;
1570
1621
  },
1571
1622
  type: 'popstate',
1572
1623
  delta
1573
1624
  });
1625
+
1626
+ if (!blocked) {
1627
+ restore_snapshot(current_history_index);
1628
+ }
1574
1629
  }
1575
1630
  });
1576
1631
 
@@ -1,3 +1,4 @@
1
+ export const SNAPSHOT_KEY = 'sveltekit:snapshot';
1
2
  export const SCROLL_KEY = 'sveltekit:scroll';
2
3
  export const INDEX_KEY = 'sveltekit:index';
3
4
 
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Read a value from `sessionStorage`
3
+ * @param {string} key
4
+ */
5
+ export function get(key) {
6
+ try {
7
+ return JSON.parse(sessionStorage[key]);
8
+ } catch {
9
+ // do nothing
10
+ }
11
+ }
12
+
13
+ /**
14
+ * Write a value to `sessionStorage`
15
+ * @param {string} key
16
+ * @param {any} value
17
+ */
18
+ export function set(key, value) {
19
+ const json = JSON.stringify(value);
20
+ try {
21
+ sessionStorage[key] = json;
22
+ } catch {
23
+ // do nothing
24
+ }
25
+ }
@@ -90,7 +90,7 @@ export async function render_response({
90
90
  navigating: writable(null),
91
91
  updated
92
92
  },
93
- components: await Promise.all(branch.map(({ node }) => node.component())),
93
+ constructors: await Promise.all(branch.map(({ node }) => node.component())),
94
94
  form: form_value
95
95
  };
96
96
 
@@ -31,7 +31,8 @@ export const validate_common_exports = validator([
31
31
  'prerender',
32
32
  'csr',
33
33
  'ssr',
34
- 'trailingSlash'
34
+ 'trailingSlash',
35
+ 'config'
35
36
  ]);
36
37
 
37
38
  export const validate_page_server_exports = validator([
@@ -40,7 +41,8 @@ export const validate_page_server_exports = validator([
40
41
  'csr',
41
42
  'ssr',
42
43
  'actions',
43
- 'trailingSlash'
44
+ 'trailingSlash',
45
+ 'config'
44
46
  ]);
45
47
 
46
48
  export const validate_server_exports = validator([
@@ -50,5 +52,6 @@ export const validate_server_exports = validator([
50
52
  'PUT',
51
53
  'DELETE',
52
54
  'prerender',
53
- 'trailingSlash'
55
+ 'trailingSlash',
56
+ 'config'
54
57
  ]);
package/types/index.d.ts CHANGED
@@ -7,13 +7,15 @@ import { CompileOptions } from 'svelte/types/compiler/interfaces';
7
7
  import {
8
8
  AdapterEntry,
9
9
  CspDirectives,
10
+ HttpMethod,
10
11
  Logger,
11
12
  MaybePromise,
12
13
  Prerendered,
13
14
  PrerenderHttpErrorHandlerValue,
14
15
  PrerenderMissingIdHandlerValue,
16
+ PrerenderOption,
15
17
  RequestOptions,
16
- RouteDefinition,
18
+ RouteSegment,
17
19
  UniqueInterface
18
20
  } from './private.js';
19
21
  import { SSRNodeLoader, SSRRoute, ValidatedConfig } from './internal.js';
@@ -85,10 +87,13 @@ export interface Builder {
85
87
  config: ValidatedConfig;
86
88
  /** Information about prerendered pages and assets, if any. */
87
89
  prerendered: Prerendered;
90
+ /** An array of dynamic (not prerendered) routes */
91
+ routes: RouteDefinition[];
88
92
 
89
93
  /**
90
94
  * Create separate functions that map to one or more routes of your app.
91
95
  * @param fn A function that groups a set of routes into an entry point
96
+ * @deprecated Use `builder.routes` instead
92
97
  */
93
98
  createEntries(fn: (route: RouteDefinition) => AdapterEntry): Promise<void>;
94
99
 
@@ -101,7 +106,7 @@ export interface Builder {
101
106
  * Generate a server-side manifest to initialise the SvelteKit [server](https://kit.svelte.dev/docs/types#public-types-server) with.
102
107
  * @param opts a relative path to the base directory of the app and optionally in which format (esm or cjs) the manifest should be generated
103
108
  */
104
- generateManifest(opts: { relativePath: string }): string;
109
+ generateManifest(opts: { relativePath: string; routes?: RouteDefinition[] }): string;
105
110
 
106
111
  /**
107
112
  * Resolve a path to the `name` directory inside `outDir`, e.g. `/path/to/.svelte-kit/my-adapter`.
@@ -959,6 +964,15 @@ export interface ResolveOptions {
959
964
  preload?(input: { type: 'font' | 'css' | 'js' | 'asset'; path: string }): boolean;
960
965
  }
961
966
 
967
+ export interface RouteDefinition<Config = any> {
968
+ id: string;
969
+ pattern: RegExp;
970
+ prerender: PrerenderOption;
971
+ segments: RouteSegment[];
972
+ methods: HttpMethod[];
973
+ config: Config;
974
+ }
975
+
962
976
  export class Server {
963
977
  constructor(manifest: SSRManifest);
964
978
  init(options: ServerInitOptions): Promise<void>;
@@ -1192,3 +1206,11 @@ export interface SubmitFunction<
1192
1206
  }) => void)
1193
1207
  >;
1194
1208
  }
1209
+
1210
+ /**
1211
+ * The type of `export const snapshot` exported from a page or layout component.
1212
+ */
1213
+ export interface Snapshot<T = any> {
1214
+ capture: () => T;
1215
+ restore: (snapshot: T) => void;
1216
+ }
@@ -239,6 +239,7 @@ export interface ServerMetadata {
239
239
  {
240
240
  prerender: PrerenderOption | undefined;
241
241
  methods: HttpMethod[];
242
+ config: any;
242
243
  }
243
244
  >;
244
245
  }
@@ -279,6 +280,7 @@ export interface SSRNode {
279
280
  ssr?: boolean;
280
281
  csr?: boolean;
281
282
  trailingSlash?: TrailingSlash;
283
+ config?: any;
282
284
  };
283
285
 
284
286
  server: {
@@ -288,6 +290,7 @@ export interface SSRNode {
288
290
  csr?: boolean;
289
291
  trailingSlash?: TrailingSlash;
290
292
  actions?: Actions;
293
+ config?: any;
291
294
  };
292
295
 
293
296
  // store this in dev so we can print serialization errors
@@ -2,6 +2,8 @@
2
2
  // but which cannot be imported from `@sveltejs/kit`. Care should
3
3
  // be taken to avoid breaking changes when editing this file
4
4
 
5
+ import { RouteDefinition } from './index.js';
6
+
5
7
  export interface AdapterEntry {
6
8
  /**
7
9
  * A string that uniquely identifies an HTTP service (e.g. serverless function) and is used for deduplication.
@@ -12,8 +14,11 @@ export interface AdapterEntry {
12
14
 
13
15
  /**
14
16
  * A function that compares the candidate route with the current route to determine
15
- * if it should be treated as a fallback for the current route. For example, `/foo/[c]`
16
- * is a fallback for `/foo/a-[b]`, and `/[...catchall]` is a fallback for all routes
17
+ * if it should be grouped with the current route.
18
+ *
19
+ * Use cases:
20
+ * - Fallback pages: `/foo/[c]` is a fallback for `/foo/a-[b]`, and `/[...catchall]` is a fallback for all routes
21
+ * - Grouping routes that share a common `config`: `/foo` should be deployed to the edge, `/bar` and `/baz` should be deployed to a serverless function
17
22
  */
18
23
  filter(route: RouteDefinition): boolean;
19
24
 
@@ -212,13 +217,6 @@ export interface RequestOptions {
212
217
  platform?: App.Platform;
213
218
  }
214
219
 
215
- export interface RouteDefinition {
216
- id: string;
217
- pattern: RegExp;
218
- segments: RouteSegment[];
219
- methods: HttpMethod[];
220
- }
221
-
222
220
  export interface RouteSegment {
223
221
  content: string;
224
222
  dynamic: boolean;