@sveltejs/kit 1.3.10 → 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.3.10",
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",
@@ -83,7 +83,7 @@
83
83
  "lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore && eslint src/**",
84
84
  "check": "tsc",
85
85
  "check:all": "tsc && pnpm -r --filter=\"./**\" check",
86
- "format": "pnpm lint --write",
86
+ "format": "prettier --write . --config ../../.prettierrc --ignore-path .gitignore",
87
87
  "test": "pnpm test:unit && pnpm test:integration",
88
88
  "test:integration": "pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test",
89
89
  "test:cross-platform:dev": "pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test:cross-platform:dev",
package/src/constants.js CHANGED
@@ -1,5 +1,7 @@
1
- // in `vite dev` and `vite preview`, we use a fake asset path so that we can
2
- // serve local assets while verifying that requests are correctly prefixed
1
+ /**
2
+ * A fake asset path used in `vite dev` and `vite preview`, so that we can
3
+ * serve local assets while verifying that requests are correctly prefixed
4
+ */
3
5
  export const SVELTE_KIT_ASSETS = '/_svelte_kit_assets';
4
6
 
5
7
  export const GENERATED_COMMENT = '// this file is generated — do not edit it\n';
@@ -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);
@@ -1,6 +1,6 @@
1
1
  import { join } from 'node:path';
2
2
  import { pathToFileURL } from 'node:url';
3
- import { get_option } from '../../runtime/server/utils.js';
3
+ import { get_option } from '../../utils/options.js';
4
4
  import {
5
5
  validate_common_exports,
6
6
  validate_page_server_exports,
@@ -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
+ }
@@ -165,7 +165,7 @@ export function crawl(html) {
165
165
  } else if (name === 'rel') {
166
166
  rel = value;
167
167
  } else if (name === 'src') {
168
- hrefs.push(value);
168
+ if (value) hrefs.push(value);
169
169
  } else if (name === 'srcset') {
170
170
  const candidates = [];
171
171
  let insideURL = true;
@@ -183,7 +183,7 @@ export function crawl(html) {
183
183
  candidates.push(value);
184
184
  for (const candidate of candidates) {
185
185
  const src = candidate.split(WHITESPACE)[0];
186
- hrefs.push(src);
186
+ if (src) hrefs.push(src);
187
187
  }
188
188
  }
189
189
  } else {
@@ -15,9 +15,7 @@ installPolyfills();
15
15
  const server_root = join(config.outDir, 'output');
16
16
 
17
17
  /** @type {import('types').ServerInternalModule} */
18
- const { set_building, set_paths } = await import(
19
- pathToFileURL(`${server_root}/server/internal.js`).href
20
- );
18
+ const { set_building } = await import(pathToFileURL(`${server_root}/server/internal.js`).href);
21
19
 
22
20
  /** @type {import('types').ServerModule} */
23
21
  const { Server } = await import(pathToFileURL(`${server_root}/server/index.js`).href);
@@ -26,7 +24,6 @@ const { Server } = await import(pathToFileURL(`${server_root}/server/index.js`).
26
24
  const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
27
25
 
28
26
  set_building(true);
29
- set_paths(config.paths);
30
27
 
31
28
  const server = new Server(manifest);
32
29
  await server.init({ env: JSON.parse(env) });
@@ -100,8 +100,6 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
100
100
  /** @type {Map<string, string>} */
101
101
  const saved = new Map();
102
102
 
103
- internal.set_paths(config.paths);
104
-
105
103
  const server = new Server(manifest);
106
104
  await server.init({ env });
107
105
 
@@ -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
 
@@ -25,9 +25,8 @@ const server_template = ({
25
25
  error_page
26
26
  }) => `
27
27
  import root from '../root.svelte';
28
- import { set_building, set_paths, set_private_env, set_public_env, set_version } from '${runtime_directory}/shared.js';
28
+ import { set_assets, set_building, set_private_env, set_public_env, set_version } from '${runtime_directory}/shared.js';
29
29
 
30
- set_paths(${s(config.kit.paths)});
31
30
  set_version(${s(config.kit.version.name)});
32
31
 
33
32
  export const options = {
@@ -58,7 +57,7 @@ export function get_hooks() {
58
57
  return ${hooks ? `import(${s(hooks)})` : '{}'};
59
58
  }
60
59
 
61
- export { set_building, set_paths, set_private_env, set_public_env };
60
+ export { set_assets, set_building, set_private_env, set_public_env };
62
61
  `;
63
62
 
64
63
  // TODO need to re-run this whenever src/app.html or src/error.html are
@@ -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
 
@@ -438,20 +438,17 @@ export async function dev(vite, vite_config, svelte_config) {
438
438
  return;
439
439
  }
440
440
 
441
- // we have to import `Server` before calling `set_paths`
441
+ // we have to import `Server` before calling `set_assets`
442
442
  const { Server } = /** @type {import('types').ServerModule} */ (
443
443
  await vite.ssrLoadModule(`${runtime_base}/server/index.js`)
444
444
  );
445
445
 
446
- const { set_paths, set_version, set_fix_stack_trace } =
446
+ const { set_assets, set_version, set_fix_stack_trace } =
447
447
  /** @type {import('types').ServerInternalModule} */ (
448
448
  await vite.ssrLoadModule(`${runtime_base}/shared.js`)
449
449
  );
450
450
 
451
- set_paths({
452
- base: svelte_config.kit.paths.base,
453
- assets
454
- });
451
+ set_assets(assets);
455
452
 
456
453
  set_version(svelte_config.kit.version.name);
457
454
 
@@ -22,6 +22,7 @@ import { get_config_aliases, get_env } from './utils.js';
22
22
  import { write_client_manifest } from '../../core/sync/write_client_manifest.js';
23
23
  import prerender from '../../core/postbuild/prerender.js';
24
24
  import analyse from '../../core/postbuild/analyse.js';
25
+ import { s } from '../../utils/misc.js';
25
26
 
26
27
  export { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
27
28
 
@@ -253,9 +254,9 @@ function kit({ svelte_config }) {
253
254
  new_config.build.ssr = !secondary_build_started;
254
255
 
255
256
  new_config.define = {
256
- __SVELTEKIT_ADAPTER_NAME__: JSON.stringify(kit.adapter?.name),
257
- __SVELTEKIT_APP_VERSION_FILE__: JSON.stringify(`${kit.appDir}/version.json`),
258
- __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: JSON.stringify(kit.version.pollInterval),
257
+ __SVELTEKIT_ADAPTER_NAME__: s(kit.adapter?.name),
258
+ __SVELTEKIT_APP_VERSION_FILE__: s(`${kit.appDir}/version.json`),
259
+ __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: s(kit.version.pollInterval),
259
260
  __SVELTEKIT_DEV__: 'false',
260
261
  __SVELTEKIT_EMBEDDED__: kit.embedded ? 'true' : 'false'
261
262
  };
@@ -315,7 +316,9 @@ function kit({ svelte_config }) {
315
316
 
316
317
  async resolveId(id) {
317
318
  // treat $env/static/[public|private] as virtual
318
- if (id.startsWith('$env/') || id === '$service-worker') return `\0${id}`;
319
+ if (id.startsWith('$env/') || id === '$internal/paths' || id === '$service-worker') {
320
+ return `\0${id}`;
321
+ }
319
322
  },
320
323
 
321
324
  async load(id, options) {
@@ -351,6 +354,15 @@ function kit({ svelte_config }) {
351
354
  );
352
355
  case '\0$service-worker':
353
356
  return create_service_worker_module(svelte_config);
357
+ case '\0$internal/paths':
358
+ const { assets, base } = svelte_config.kit.paths;
359
+ return `export const base = ${s(base)};
360
+ export let assets = ${s(assets)};
361
+
362
+ /** @param {string} path */
363
+ export function set_assets(path) {
364
+ assets = path;
365
+ }`;
354
366
  }
355
367
  }
356
368
  };
@@ -552,7 +564,7 @@ function kit({ svelte_config }) {
552
564
  this.emitFile({
553
565
  type: 'asset',
554
566
  fileName: `${kit.appDir}/version.json`,
555
- source: JSON.stringify({ version: kit.version.name })
567
+ source: s({ version: kit.version.name })
556
568
  });
557
569
  },
558
570
 
@@ -788,9 +800,9 @@ export const build = [];
788
800
  export const files = [
789
801
  ${create_assets(config)
790
802
  .filter((asset) => config.kit.serviceWorker.files(asset.file))
791
- .map((asset) => `${JSON.stringify(`${config.kit.paths.base}/${asset.file}`)}`)
803
+ .map((asset) => `${s(`${config.kit.paths.base}/${asset.file}`)}`)
792
804
  .join(',\n\t\t\t\t')}
793
805
  ];
794
806
  export const prerendered = [];
795
- export const version = ${JSON.stringify(config.kit.version.name)};
807
+ export const version = ${s(config.kit.version.name)};
796
808
  `;
@@ -37,14 +37,14 @@ export async function preview(vite, vite_config, svelte_config) {
37
37
  const dir = join(svelte_config.kit.outDir, 'output/server');
38
38
 
39
39
  /** @type {import('types').ServerInternalModule} */
40
- const { set_paths } = await import(pathToFileURL(join(dir, 'internal.js')).href);
40
+ const { set_assets } = await import(pathToFileURL(join(dir, 'internal.js')).href);
41
41
 
42
42
  /** @type {import('types').ServerModule} */
43
43
  const { Server } = await import(pathToFileURL(join(dir, 'index.js')).href);
44
44
 
45
45
  const { manifest } = await import(pathToFileURL(join(dir, 'manifest.js')).href);
46
46
 
47
- set_paths({ base, assets });
47
+ set_assets(assets);
48
48
 
49
49
  const server = new Server(manifest);
50
50
  await server.init({
@@ -1 +1 @@
1
- export { base, assets } from '../shared.js';
1
+ export { base, assets } from '$internal/paths';
@@ -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,
@@ -26,11 +27,12 @@ import { parse } from './parse.js';
26
27
 
27
28
  import Root from '__GENERATED__/root.svelte';
28
29
  import { nodes, server_loads, dictionary, matchers, hooks } from '__CLIENT__/manifest.js';
30
+ import { base } from '$internal/paths';
29
31
  import { HttpError, Redirect } from '../control.js';
30
32
  import { stores } from './singletons.js';
31
33
  import { unwrap_promises } from '../../utils/promises.js';
32
34
  import * as devalue from 'devalue';
33
- import { INDEX_KEY, PRELOAD_PRIORITIES, SCROLL_KEY } from './constants.js';
35
+ import { INDEX_KEY, PRELOAD_PRIORITIES, SCROLL_KEY, SNAPSHOT_KEY } from './constants.js';
34
36
  import { validate_common_exports } from '../../utils/exports.js';
35
37
  import { compact } from '../../utils/array.js';
36
38
 
@@ -51,12 +53,10 @@ default_error_loader();
51
53
 
52
54
  /** @typedef {{ x: number, y: number }} ScrollPosition */
53
55
  /** @type {Record<number, ScrollPosition>} */
54
- let scroll_positions = {};
55
- try {
56
- scroll_positions = JSON.parse(sessionStorage[SCROLL_KEY]);
57
- } catch {
58
- // do nothing
59
- }
56
+ const scroll_positions = storage.get(SCROLL_KEY) ?? {};
57
+
58
+ /** @type {Record<string, any[]>} */
59
+ const snapshots = storage.get(SNAPSHOT_KEY) ?? {};
60
60
 
61
61
  /** @param {number} index */
62
62
  function update_scroll_positions(index) {
@@ -66,15 +66,22 @@ function update_scroll_positions(index) {
66
66
  /**
67
67
  * @param {{
68
68
  * target: HTMLElement;
69
- * base: string;
70
69
  * }} opts
71
70
  * @returns {import('./types').Client}
72
71
  */
73
- export function create_client({ target, base }) {
72
+ export function create_client({ target }) {
74
73
  const container = __SVELTEKIT_EMBEDDED__ ? target : document.documentElement;
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, base }) {
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, base }) {
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, base }) {
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, base }) {
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, base }) {
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
 
@@ -594,12 +643,17 @@ export function create_client({ target, base }) {
594
643
  }
595
644
 
596
645
  // we must fixup relative urls so they are resolved from the target page
597
- const resolved = new URL(requested, url).href;
598
- depends(resolved);
646
+ const resolved = new URL(requested, url);
647
+ depends(resolved.href);
648
+
649
+ // match ssr serialized data url, which is important to find cached responses
650
+ if (resolved.origin === url.origin) {
651
+ requested = resolved.href.slice(url.origin.length);
652
+ }
599
653
 
600
654
  // prerendered pages may be served from any origin, so `initial_fetch` urls shouldn't be resolved
601
655
  return started
602
- ? subsequent_fetch(requested, resolved, init)
656
+ ? subsequent_fetch(requested, resolved.href, init)
603
657
  : initial_fetch(requested, init);
604
658
  },
605
659
  setHeaders: () => {}, // noop
@@ -745,7 +799,7 @@ export function create_client({ target, base }) {
745
799
  server_data = await load_data(url, invalid_server_nodes);
746
800
  } catch (error) {
747
801
  return load_root_error_page({
748
- status: 500,
802
+ status: error instanceof HttpError ? error.status : 500,
749
803
  error: await handle_error(error, { url, params, route: { id: route.id } }),
750
804
  url,
751
805
  route
@@ -1086,7 +1140,8 @@ export function create_client({ target, base }) {
1086
1140
  return;
1087
1141
  }
1088
1142
 
1089
- 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;
1090
1145
 
1091
1146
  accepted();
1092
1147
 
@@ -1100,6 +1155,7 @@ export function create_client({ target, base }) {
1100
1155
  intent,
1101
1156
  url,
1102
1157
  redirect_chain,
1158
+ previous_history_index,
1103
1159
  {
1104
1160
  scroll,
1105
1161
  keepfocus,
@@ -1380,12 +1436,10 @@ export function create_client({ target, base }) {
1380
1436
  addEventListener('visibilitychange', () => {
1381
1437
  if (document.visibilityState === 'hidden') {
1382
1438
  update_scroll_positions(current_history_index);
1439
+ storage.set(SCROLL_KEY, scroll_positions);
1383
1440
 
1384
- try {
1385
- sessionStorage[SCROLL_KEY] = JSON.stringify(scroll_positions);
1386
- } catch {
1387
- // do nothing
1388
- }
1441
+ capture_snapshot(current_history_index);
1442
+ storage.set(SNAPSHOT_KEY, snapshots);
1389
1443
  }
1390
1444
  });
1391
1445
 
@@ -1532,7 +1586,7 @@ export function create_client({ target, base }) {
1532
1586
  });
1533
1587
  });
1534
1588
 
1535
- addEventListener('popstate', (event) => {
1589
+ addEventListener('popstate', async (event) => {
1536
1590
  if (event.state?.[INDEX_KEY]) {
1537
1591
  // if a popstate-driven navigation is cancelled, we need to counteract it
1538
1592
  // with history.go, which means we end up back here, hence this check
@@ -1550,8 +1604,9 @@ export function create_client({ target, base }) {
1550
1604
  }
1551
1605
 
1552
1606
  const delta = event.state[INDEX_KEY] - current_history_index;
1607
+ let blocked = false;
1553
1608
 
1554
- navigate({
1609
+ await navigate({
1555
1610
  url: new URL(location.href),
1556
1611
  scroll,
1557
1612
  keepfocus: false,
@@ -1562,10 +1617,15 @@ export function create_client({ target, base }) {
1562
1617
  },
1563
1618
  blocked: () => {
1564
1619
  history.go(-delta);
1620
+ blocked = true;
1565
1621
  },
1566
1622
  type: 'popstate',
1567
1623
  delta
1568
1624
  });
1625
+
1626
+ if (!blocked) {
1627
+ restore_snapshot(current_history_index);
1628
+ }
1569
1629
  }
1570
1630
  });
1571
1631
 
@@ -1693,7 +1753,8 @@ async function load_data(url, invalid) {
1693
1753
 
1694
1754
  if (!res.ok) {
1695
1755
  // error message is a JSON-stringified string which devalue can't handle at the top level
1696
- throw new Error(data);
1756
+ // turn it into a HttpError to not call handleError on the client again (was already handled on the server)
1757
+ throw new HttpError(res.status, data);
1697
1758
  }
1698
1759
 
1699
1760
  // revive devalue-flattened data
@@ -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
+ }
@@ -1,23 +1,20 @@
1
1
  import { DEV } from 'esm-env';
2
2
  import { create_client } from './client.js';
3
3
  import { init } from './singletons.js';
4
- import { set_paths, set_version, set_public_env } from '../shared.js';
4
+ import { set_assets, set_version, set_public_env } from '../shared.js';
5
5
 
6
6
  /**
7
7
  * @param {{
8
+ * assets: string;
8
9
  * env: Record<string, string>;
9
10
  * hydrate: Parameters<import('./types').Client['_hydrate']>[0];
10
- * paths: {
11
- * assets: string;
12
- * base: string;
13
- * },
14
11
  * target: HTMLElement;
15
12
  * version: string;
16
13
  * }} opts
17
14
  */
18
- export async function start({ env, hydrate, paths, target, version }) {
15
+ export async function start({ assets, env, hydrate, target, version }) {
19
16
  set_public_env(env);
20
- set_paths(paths);
17
+ set_assets(assets);
21
18
  set_version(version);
22
19
 
23
20
  if (DEV && target === document.body) {
@@ -27,8 +24,7 @@ export async function start({ env, hydrate, paths, target, version }) {
27
24
  }
28
25
 
29
26
  const client = create_client({
30
- target,
31
- base: paths.base
27
+ target
32
28
  });
33
29
 
34
30
  init({ client });
@@ -1,6 +1,7 @@
1
1
  import { BROWSER, DEV } from 'esm-env';
2
2
  import { writable } from 'svelte/store';
3
- import { assets, version } from '../shared.js';
3
+ import { assets } from '$internal/paths';
4
+ import { version } from '../shared.js';
4
5
  import { PRELOAD_PRIORITIES } from './constants.js';
5
6
 
6
7
  /* global __SVELTEKIT_APP_VERSION_FILE__, __SVELTEKIT_APP_VERSION_POLL_INTERVAL__ */
@@ -4,11 +4,12 @@ import { method_not_allowed } from './utils.js';
4
4
 
5
5
  /**
6
6
  * @param {import('types').RequestEvent} event
7
+ * @param {import('types').SSRRoute} route
7
8
  * @param {import('types').SSREndpoint} mod
8
9
  * @param {import('types').SSRState} state
9
10
  * @returns {Promise<Response>}
10
11
  */
11
- export async function render_endpoint(event, mod, state) {
12
+ export async function render_endpoint(event, route, mod, state) {
12
13
  const method = /** @type {import('types').HttpMethod} */ (event.request.method);
13
14
 
14
15
  let handler = mod[method];
@@ -38,6 +39,8 @@ export async function render_endpoint(event, mod, state) {
38
39
  }
39
40
  }
40
41
 
42
+ state.initiator = route;
43
+
41
44
  try {
42
45
  const response = await handler(
43
46
  /** @type {import('types').RequestEvent<Record<string, any>>} */ (event)
@@ -1,6 +1,6 @@
1
1
  import * as set_cookie_parser from 'set-cookie-parser';
2
2
  import { respond } from './respond.js';
3
- import * as paths from '../shared.js';
3
+ import * as paths from '$internal/paths';
4
4
 
5
5
  /**
6
6
  * @param {{
@@ -4,7 +4,6 @@ import { normalize_error } from '../../../utils/error.js';
4
4
  import { add_data_suffix } from '../../../utils/url.js';
5
5
  import { HttpError, Redirect } from '../../control.js';
6
6
  import {
7
- get_option,
8
7
  redirect_response,
9
8
  static_error_page,
10
9
  handle_error_and_jsonify,
@@ -19,6 +18,7 @@ import {
19
18
  import { load_data, load_server_data } from './load_data.js';
20
19
  import { render_response } from './render.js';
21
20
  import { respond_with_error } from './respond_with_error.js';
21
+ import { get_option } from '../../../utils/options.js';
22
22
 
23
23
  /**
24
24
  * @param {import('types').RequestEvent} event
@@ -1,13 +1,14 @@
1
1
  import * as devalue from 'devalue';
2
2
  import { readable, writable } from 'svelte/store';
3
3
  import { DEV } from 'esm-env';
4
+ import { assets, base } from '$internal/paths';
4
5
  import { hash } from '../../hash.js';
5
6
  import { serialize_data } from './serialize_data.js';
6
7
  import { s } from '../../../utils/misc.js';
7
8
  import { Csp } from './csp.js';
8
9
  import { uneval_action_response } from './actions.js';
9
10
  import { clarify_devalue_error } from '../utils.js';
10
- import { assets, base, version, public_env } from '../../shared.js';
11
+ import { version, public_env } from '../../shared.js';
11
12
  import { text } from '../../../exports/index.js';
12
13
 
13
14
  // TODO rename this function/module
@@ -89,7 +90,7 @@ export async function render_response({
89
90
  navigating: writable(null),
90
91
  updated
91
92
  },
92
- components: await Promise.all(branch.map(({ node }) => node.component())),
93
+ constructors: await Promise.all(branch.map(({ node }) => node.component())),
93
94
  form: form_value
94
95
  };
95
96
 
@@ -265,8 +266,8 @@ export async function render_response({
265
266
 
266
267
  if (page_config.csr) {
267
268
  const opts = [
269
+ `assets: ${s(assets)}`,
268
270
  `env: ${s(public_env)}`,
269
- `paths: ${s({ assets, base })}`,
270
271
  `target: document.querySelector('[data-sveltekit-hydrate="${target}"]').parentNode`,
271
272
  `version: ${s(version)}`
272
273
  ];
@@ -2,11 +2,11 @@ import { render_response } from './render.js';
2
2
  import { load_data, load_server_data } from './load_data.js';
3
3
  import {
4
4
  handle_error_and_jsonify,
5
- get_option,
6
5
  static_error_page,
7
6
  redirect_response,
8
7
  GENERIC_ERROR
9
8
  } from '../utils.js';
9
+ import { get_option } from '../../../utils/options.js';
10
10
  import { HttpError, Redirect } from '../../control.js';
11
11
 
12
12
  /**
@@ -1,10 +1,11 @@
1
1
  import { DEV } from 'esm-env';
2
+ import { base } from '$internal/paths';
2
3
  import { is_endpoint_request, render_endpoint } from './endpoint.js';
3
4
  import { render_page } from './page/index.js';
4
5
  import { render_response } from './page/render.js';
5
6
  import { respond_with_error } from './page/respond_with_error.js';
6
7
  import { is_form_content_type } from '../../utils/http.js';
7
- import { GENERIC_ERROR, get_option, handle_fatal_error, redirect_response } from './utils.js';
8
+ import { GENERIC_ERROR, handle_fatal_error, redirect_response } from './utils.js';
8
9
  import {
9
10
  decode_pathname,
10
11
  decode_params,
@@ -23,8 +24,8 @@ import {
23
24
  validate_page_server_exports,
24
25
  validate_server_exports
25
26
  } from '../../utils/exports.js';
27
+ import { get_option } from '../../utils/options.js';
26
28
  import { error, json, text } from '../../exports/index.js';
27
- import * as paths from '../shared.js';
28
29
 
29
30
  /* global __SVELTEKIT_ADAPTER_NAME__ */
30
31
 
@@ -70,11 +71,11 @@ export async function respond(request, options, manifest, state) {
70
71
  /** @type {Record<string, string>} */
71
72
  let params = {};
72
73
 
73
- if (paths.base && !state.prerendering?.fallback) {
74
- if (!decoded.startsWith(paths.base)) {
74
+ if (base && !state.prerendering?.fallback) {
75
+ if (!decoded.startsWith(base)) {
75
76
  return text('Not found', { status: 404 });
76
77
  }
77
- decoded = decoded.slice(paths.base.length) || '/';
78
+ decoded = decoded.slice(base.length) || '/';
78
79
  }
79
80
 
80
81
  const is_data_request = has_data_suffix(decoded);
@@ -357,7 +358,7 @@ export async function respond(request, options, manifest, state) {
357
358
  trailing_slash ?? 'never'
358
359
  );
359
360
  } else if (route.endpoint && (!route.page || is_endpoint_request(event))) {
360
- response = await render_endpoint(event, await route.endpoint(), state);
361
+ response = await render_endpoint(event, route, await route.endpoint(), state);
361
362
  } else if (route.page) {
362
363
  response = await render_page(
363
364
  event,
@@ -2,7 +2,6 @@ import * as devalue from 'devalue';
2
2
  import { json, text } from '../../exports/index.js';
3
3
  import { coalesce_to_error } from '../../utils/error.js';
4
4
  import { negotiate } from '../../utils/http.js';
5
- import { has_data_suffix } from '../../utils/url.js';
6
5
  import { HttpError } from '../control.js';
7
6
  import { fix_stack_trace } from '../shared.js';
8
7
 
@@ -51,23 +50,6 @@ export function allowed_methods(mod) {
51
50
  return allowed;
52
51
  }
53
52
 
54
- /**
55
- * @template {'prerender' | 'ssr' | 'csr' | 'trailingSlash'} Option
56
- * @template {Option extends 'prerender' ? import('types').PrerenderOption : Option extends 'trailingSlash' ? import('types').TrailingSlash : boolean} Value
57
- *
58
- * @param {Array<import('types').SSRNode | undefined>} nodes
59
- * @param {Option} option
60
- *
61
- * @returns {Value | undefined}
62
- */
63
- export function get_option(nodes, option) {
64
- return nodes.reduce((value, node) => {
65
- return /** @type {any} TypeScript's too dumb to understand this */ (
66
- node?.universal?.[option] ?? node?.server?.[option] ?? value
67
- );
68
- }, /** @type {Value | undefined} */ (undefined));
69
- }
70
-
71
53
  /**
72
54
  * Return as a response that renders the error.html
73
55
  *
@@ -98,7 +80,7 @@ export async function handle_fatal_error(event, options, error) {
98
80
  'text/html'
99
81
  ]);
100
82
 
101
- if (has_data_suffix(new URL(event.request.url).pathname) || type === 'application/json') {
83
+ if (event.isDataRequest || type === 'application/json') {
102
84
  return json(body, {
103
85
  status
104
86
  });
@@ -1,5 +1,5 @@
1
- export let assets = '';
2
- export let base = '';
1
+ export { set_assets } from '$internal/paths';
2
+
3
3
  export let building = false;
4
4
  export let version = '';
5
5
 
@@ -12,12 +12,6 @@ export let public_env = {};
12
12
  /** @param {string} stack */
13
13
  export let fix_stack_trace = (stack) => stack;
14
14
 
15
- /** @param {{ base: string, assets: string }} paths */
16
- export function set_paths(paths) {
17
- base = paths.base;
18
- assets = paths.assets || base;
19
- }
20
-
21
15
  /** @param {boolean} value */
22
16
  export function set_building(value) {
23
17
  building = value;
@@ -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
  ]);
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @template {'prerender' | 'ssr' | 'csr' | 'trailingSlash'} Option
3
+ * @template {Option extends 'prerender' ? import('types').PrerenderOption : Option extends 'trailingSlash' ? import('types').TrailingSlash : boolean} Value
4
+ *
5
+ * @param {Array<import('types').SSRNode | undefined>} nodes
6
+ * @param {Option} option
7
+ *
8
+ * @returns {Value | undefined}
9
+ */
10
+ export function get_option(nodes, option) {
11
+ return nodes.reduce((value, node) => {
12
+ return /** @type {any} TypeScript's too dumb to understand this */ (
13
+ node?.universal?.[option] ?? node?.server?.[option] ?? value
14
+ );
15
+ }, /** @type {Value | undefined} */ (undefined));
16
+ }
@@ -170,6 +170,7 @@ declare module '$app/navigation' {
170
170
  export function disableScrollHandling(): void;
171
171
  /**
172
172
  * Returns a Promise that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) to the specified `url`.
173
+ * For external URLs, use `window.location = url` instead of calling `goto(url)`.
173
174
  *
174
175
  * @param url Where to navigate to. Note that if you've set [`config.kit.paths.base`](https://kit.svelte.dev/docs/configuration#paths) and the URL is root-relative, you need to prepend the base path if you want to navigate within the app.
175
176
  * @param opts Options related to the navigation
@@ -431,3 +432,10 @@ declare module '@sveltejs/kit/vite' {
431
432
  export function sveltekit(): Promise<Plugin[]>;
432
433
  export { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
433
434
  }
435
+
436
+ /** Internal version of $app/paths */
437
+ declare module '$internal/paths' {
438
+ export const base: `/${string}`;
439
+ export let assets: `https://${string}` | `http://${string}`;
440
+ export function set_assets(path: string): void;
441
+ }
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';
@@ -50,9 +52,11 @@ export type AwaitedProperties<input extends Record<string, any> | void> =
50
52
  ? OptionalUnion<AwaitedPropertiesUnion<input>>
51
53
  : AwaitedPropertiesUnion<input>;
52
54
 
53
- export type AwaitedActions<T extends Record<string, (...args: any) => any>> = {
54
- [Key in keyof T]: OptionalUnion<UnpackValidationError<Awaited<ReturnType<T[Key]>>>>;
55
- }[keyof T];
55
+ export type AwaitedActions<T extends Record<string, (...args: any) => any>> = OptionalUnion<
56
+ {
57
+ [Key in keyof T]: UnpackValidationError<Awaited<ReturnType<T[Key]>>>;
58
+ }[keyof T]
59
+ >;
56
60
 
57
61
  // Takes a union type and returns a union type where each type also has all properties
58
62
  // of all possible types (typed as undefined), making accessing them more ergonomic
@@ -83,10 +87,13 @@ export interface Builder {
83
87
  config: ValidatedConfig;
84
88
  /** Information about prerendered pages and assets, if any. */
85
89
  prerendered: Prerendered;
90
+ /** An array of dynamic (not prerendered) routes */
91
+ routes: RouteDefinition[];
86
92
 
87
93
  /**
88
94
  * Create separate functions that map to one or more routes of your app.
89
95
  * @param fn A function that groups a set of routes into an entry point
96
+ * @deprecated Use `builder.routes` instead
90
97
  */
91
98
  createEntries(fn: (route: RouteDefinition) => AdapterEntry): Promise<void>;
92
99
 
@@ -99,7 +106,7 @@ export interface Builder {
99
106
  * Generate a server-side manifest to initialise the SvelteKit [server](https://kit.svelte.dev/docs/types#public-types-server) with.
100
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
101
108
  */
102
- generateManifest(opts: { relativePath: string }): string;
109
+ generateManifest(opts: { relativePath: string; routes?: RouteDefinition[] }): string;
103
110
 
104
111
  /**
105
112
  * Resolve a path to the `name` directory inside `outDir`, e.g. `/path/to/.svelte-kit/my-adapter`.
@@ -453,6 +460,7 @@ export interface KitConfig {
453
460
  * - `(details) => void` — a custom error handler that takes a `details` object with `status`, `path`, `referrer`, `referenceType` and `message` properties. If you `throw` from this function, the build will fail
454
461
  *
455
462
  * ```js
463
+ * /// file: svelte.config.js
456
464
  * /// type: import('@sveltejs/kit').Config
457
465
  * const config = {
458
466
  * kit: {
@@ -956,6 +964,15 @@ export interface ResolveOptions {
956
964
  preload?(input: { type: 'font' | 'css' | 'js' | 'asset'; path: string }): boolean;
957
965
  }
958
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
+
959
976
  export class Server {
960
977
  constructor(manifest: SSRManifest);
961
978
  init(options: ServerInitOptions): Promise<void>;
@@ -1189,3 +1206,11 @@ export interface SubmitFunction<
1189
1206
  }) => void)
1190
1207
  >;
1191
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
+ }
@@ -1,4 +1,3 @@
1
- import { OutputChunk } from 'rollup';
2
1
  import { SvelteComponent } from 'svelte/internal';
3
2
  import {
4
3
  Config,
@@ -30,7 +29,7 @@ export interface ServerModule {
30
29
 
31
30
  export interface ServerInternalModule {
32
31
  set_building(building: boolean): void;
33
- set_paths(paths: { base: string; assets: string }): void;
32
+ set_assets(path: string): void;
34
33
  set_private_env(environment: Record<string, string>): void;
35
34
  set_public_env(environment: Record<string, string>): void;
36
35
  set_version(version: string): void;
@@ -240,6 +239,7 @@ export interface ServerMetadata {
240
239
  {
241
240
  prerender: PrerenderOption | undefined;
242
241
  methods: HttpMethod[];
242
+ config: any;
243
243
  }
244
244
  >;
245
245
  }
@@ -280,6 +280,7 @@ export interface SSRNode {
280
280
  ssr?: boolean;
281
281
  csr?: boolean;
282
282
  trailingSlash?: TrailingSlash;
283
+ config?: any;
283
284
  };
284
285
 
285
286
  server: {
@@ -289,6 +290,7 @@ export interface SSRNode {
289
290
  csr?: boolean;
290
291
  trailingSlash?: TrailingSlash;
291
292
  actions?: Actions;
293
+ config?: any;
292
294
  };
293
295
 
294
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;