@sveltejs/kit 3.0.0-next.13 → 3.0.0-next.16

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.
Files changed (86) hide show
  1. package/package.json +7 -4
  2. package/src/cli.js +8 -5
  3. package/src/core/adapt/builder.js +20 -10
  4. package/src/core/config/index.js +4 -5
  5. package/src/core/env.js +3 -3
  6. package/src/core/generate_manifest/index.js +6 -0
  7. package/src/core/postbuild/entities.js +8 -2
  8. package/src/core/postbuild/fallback.js +2 -1
  9. package/src/core/postbuild/prerender.js +18 -13
  10. package/src/core/sync/create_manifest_data/conflict.js +1 -1
  11. package/src/core/sync/create_manifest_data/index.js +33 -2
  12. package/src/core/sync/sync.js +3 -2
  13. package/src/core/sync/utils.js +2 -2
  14. package/src/core/sync/write_app_types.js +72 -27
  15. package/src/core/sync/write_tsconfig/index.js +73 -54
  16. package/src/core/sync/write_tsconfig/utils.js +0 -61
  17. package/src/core/sync/write_tsconfig/validate.js +128 -0
  18. package/src/core/sync/write_types/index.js +7 -4
  19. package/src/exports/node/index.js +2 -7
  20. package/src/exports/public.d.ts +25 -18
  21. package/src/exports/vite/build/build_server.js +2 -3
  22. package/src/exports/vite/dev/index.js +44 -36
  23. package/src/exports/vite/index.js +91 -16
  24. package/src/exports/vite/utils.js +62 -15
  25. package/src/runner.js +4 -2
  26. package/src/runtime/app/forms.js +4 -7
  27. package/src/runtime/app/internal/transport.js +53 -0
  28. package/src/runtime/app/paths/client.js +2 -2
  29. package/src/runtime/app/paths/internal/client.js +2 -2
  30. package/src/runtime/app/server/remote/prerender.js +6 -10
  31. package/src/runtime/app/server/remote/query.js +3 -9
  32. package/src/runtime/app/server/remote/requested.js +2 -2
  33. package/src/runtime/app/server/remote/shared.js +1 -16
  34. package/src/runtime/client/client.js +177 -63
  35. package/src/runtime/client/fetcher.js +2 -13
  36. package/src/runtime/client/parse.js +1 -1
  37. package/src/runtime/client/remote-functions/command.svelte.js +1 -2
  38. package/src/runtime/client/remote-functions/form.svelte.js +3 -7
  39. package/src/runtime/client/remote-functions/prerender.svelte.js +2 -2
  40. package/src/runtime/client/remote-functions/query/index.js +1 -1
  41. package/src/runtime/client/remote-functions/query/proxy.js +2 -2
  42. package/src/runtime/client/remote-functions/query-batch.svelte.js +1 -1
  43. package/src/runtime/client/remote-functions/query-live/proxy.js +2 -12
  44. package/src/runtime/client/remote-functions/shared.svelte.js +1 -1
  45. package/src/runtime/client/utils.js +1 -0
  46. package/src/runtime/form-utils.js +4 -6
  47. package/src/runtime/pathname.js +44 -1
  48. package/src/runtime/server/data/index.js +5 -8
  49. package/src/runtime/server/dev.js +22 -0
  50. package/src/runtime/server/endpoint.js +3 -4
  51. package/src/runtime/server/fetch.js +25 -30
  52. package/src/runtime/server/index.js +64 -46
  53. package/src/runtime/server/internal.js +12 -5
  54. package/src/runtime/server/page/actions.js +18 -43
  55. package/src/runtime/server/page/crypto.js +2 -2
  56. package/src/runtime/server/page/csp.js +2 -1
  57. package/src/runtime/server/page/data_serializer.js +12 -13
  58. package/src/runtime/server/page/index.js +16 -32
  59. package/src/runtime/server/page/load_data.js +28 -44
  60. package/src/runtime/server/page/render.js +28 -12
  61. package/src/runtime/server/page/respond_with_error.js +8 -20
  62. package/src/runtime/server/page/serialize_data.js +2 -13
  63. package/src/runtime/server/page/server_routing.js +58 -9
  64. package/src/runtime/server/remote-functions.js +11 -15
  65. package/src/runtime/server/respond.js +44 -56
  66. package/src/runtime/server/state.js +60 -0
  67. package/src/runtime/server/utils.js +0 -20
  68. package/src/runtime/shared.js +20 -40
  69. package/src/runtime/utils.js +3 -0
  70. package/src/types/ambient-private.d.ts +1 -1
  71. package/src/types/ambient.d.ts +58 -7
  72. package/src/types/global-private.d.ts +1 -1
  73. package/src/types/internal.d.ts +47 -53
  74. package/src/utils/filesystem.js +1 -23
  75. package/src/utils/hash.js +21 -0
  76. package/src/utils/http.js +8 -7
  77. package/src/utils/import.js +2 -1
  78. package/src/utils/params.js +1 -1
  79. package/src/utils/regex.js +9 -0
  80. package/src/utils/routing.js +52 -27
  81. package/src/utils/url.js +1 -0
  82. package/src/version.js +1 -1
  83. package/types/index.d.ts +98 -28
  84. package/types/index.d.ts.map +1 -1
  85. package/src/exports/node/polyfills.js +0 -30
  86. package/src/runtime/server/app.js +0 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "3.0.0-next.13",
3
+ "version": "3.0.0-next.16",
4
4
  "description": "SvelteKit is the fastest way to build Svelte apps",
5
5
  "keywords": [
6
6
  "framework",
@@ -30,12 +30,12 @@
30
30
  },
31
31
  "devDependencies": {
32
32
  "@opentelemetry/api": "^1.9.0",
33
- "@playwright/test": "~1.61.0",
33
+ "@playwright/test": "^1.62.0",
34
34
  "@sveltejs/vite-plugin-svelte": "^7.0.0",
35
35
  "@types/connect": "^3.4.38",
36
36
  "@types/node": "^22.19.19",
37
37
  "dts-buddy": "^0.8.3",
38
- "jsdom": "^29.1.1",
38
+ "jsdom": "^30.0.0",
39
39
  "rolldown": "^1.2.0",
40
40
  "svelte": "^5.56.3",
41
41
  "svelte-preprocess": "^6.0.5",
@@ -71,6 +71,9 @@
71
71
  "svelte-kit.js"
72
72
  ],
73
73
  "imports": {
74
+ "#app/internal/transport": {
75
+ "default": "./src/runtime/app/internal/transport.js"
76
+ },
74
77
  "#app/paths": {
75
78
  "workerd": "./src/runtime/app/paths/server.js",
76
79
  "browser": "./src/runtime/app/paths/client.js",
@@ -127,7 +130,7 @@
127
130
  },
128
131
  "types": "types/index.d.ts",
129
132
  "engines": {
130
- "node": ">=22"
133
+ "node": ">=22.17"
131
134
  },
132
135
  "scripts": {
133
136
  "lint": "prettier --config ../../.prettierrc --check .",
package/src/cli.js CHANGED
@@ -2,8 +2,9 @@ import fs from 'node:fs';
2
2
  import process from 'node:process';
3
3
  import { parseArgs, styleText } from 'node:util';
4
4
  import { extract_svelte_config, load_vite_config } from './core/config/index.js';
5
- import { coalesce_to_error } from './utils/error.js';
6
5
  import { resolve_explicit_env_entry } from './core/env.js';
6
+ import { coalesce_to_error } from './utils/error.js';
7
+ import { import_peer } from './utils/import.js';
7
8
 
8
9
  /** @param {unknown} e */
9
10
  function handle_error(e) {
@@ -75,10 +76,10 @@ if (!command) {
75
76
  }
76
77
 
77
78
  if (command === 'sync') {
78
- // create placeholder node_modules/$app/tsconfig/tsconfig.json if necessary, to squelch warnings.
79
+ // create placeholder node_modules/$app/tsconfig.json if necessary, to squelch warnings.
79
80
  // this isn't bulletproof — if someone has some esoteric config, it will continue
80
81
  // to harmlessly warn — but we handle the 90% case and clean up after ourselves
81
- const dir = 'node_modules/$app/tsconfig';
82
+ const dir = 'node_modules/$app';
82
83
  const base_tsconfig = `${dir}/tsconfig.json`;
83
84
  const base_tsconfig_json = '{}';
84
85
 
@@ -96,14 +97,16 @@ if (command === 'sync') {
96
97
  }
97
98
 
98
99
  try {
99
- const vite_config = await load_vite_config(values.config);
100
+ const vite = /** @type {import('vite')} */ (await import_peer('vite', process.cwd()));
101
+
102
+ const vite_config = await load_vite_config(values.config, vite);
100
103
  const sveltekit_config = extract_svelte_config(vite_config);
101
104
 
102
105
  const sync = await import('./core/sync/sync.js');
103
106
  sync.all_types(sveltekit_config, vite_config.root);
104
107
 
105
108
  const explicit_env_entry = resolve_explicit_env_entry(sveltekit_config.kit);
106
- await sync.env(sveltekit_config.kit, explicit_env_entry, vite_config.root, values.mode);
109
+ await sync.env(vite, sveltekit_config.kit, explicit_env_entry, vite_config.root, values.mode);
107
110
  } catch (error) {
108
111
  handle_error(error);
109
112
  } finally {
@@ -5,12 +5,19 @@
5
5
  /** @import { RouteData, ValidatedConfig, BuildData, ServerMetadata, ServerMetadataRoute, Prerendered, PrerenderMap, Logger, RemoteChunk } from 'types' */
6
6
  import { loadEnv } from 'vite';
7
7
  import * as devalue from 'devalue';
8
- import { createReadStream, createWriteStream, existsSync, statSync } from 'node:fs';
8
+ import {
9
+ createReadStream,
10
+ createWriteStream,
11
+ existsSync,
12
+ mkdirSync,
13
+ rmSync,
14
+ statSync
15
+ } from 'node:fs';
9
16
  import { extname, resolve, join, dirname, relative } from 'node:path';
10
17
  import { pipeline } from 'node:stream';
11
18
  import { promisify } from 'node:util';
12
19
  import zlib from 'node:zlib';
13
- import { copy, rimraf, mkdirp } from '../../utils/filesystem.js';
20
+ import { copy } from '../../utils/filesystem.js';
14
21
  import { posixify } from '../../utils/os.js';
15
22
  import { generate_manifest } from '../generate_manifest/index.js';
16
23
  import { get_route_segments } from '../../utils/routing.js';
@@ -99,8 +106,8 @@ export function create_builder({
99
106
 
100
107
  return {
101
108
  log,
102
- rimraf,
103
- mkdirp,
109
+ rimraf: (dir) => rmSync(dir, { force: true, recursive: true }),
110
+ mkdirp: (dir) => mkdirSync(dir, { recursive: true }),
104
111
  copy,
105
112
 
106
113
  config,
@@ -109,16 +116,19 @@ export function create_builder({
109
116
 
110
117
  async compress(directory) {
111
118
  if (!existsSync(directory)) {
112
- return;
119
+ return [];
113
120
  }
114
121
 
115
- const files = list_files(directory, (file) => extensions.includes(extname(file))).map(
116
- (file) => resolve(directory, file)
117
- );
122
+ const files = list_files(directory, (file) => extensions.includes(extname(file)));
118
123
 
119
124
  await Promise.all(
120
- files.flatMap((file) => [compress_file(file, 'gz'), compress_file(file, 'br')])
125
+ files.flatMap((file) => {
126
+ const abs = resolve(directory, file);
127
+ return [compress_file(abs, 'gz'), compress_file(abs, 'br')];
128
+ })
121
129
  );
130
+
131
+ return files;
122
132
  },
123
133
 
124
134
  findServerAssets(route_data) {
@@ -268,7 +278,7 @@ export function create_builder({
268
278
  exports: module.exports
269
279
  });
270
280
 
271
- rimraf(entrypoint);
281
+ rmSync(entrypoint, { force: true, recursive: true });
272
282
  write(entrypoint, facade);
273
283
  }
274
284
  };
@@ -120,13 +120,12 @@ export function load_error_page(config) {
120
120
 
121
121
  /**
122
122
  * @param {string} [config]
123
+ * @param {typeof import('vite')} [vite]
123
124
  */
124
- export async function load_vite_config(config) {
125
- const { resolveConfig } = /** @type {import('vite')} */ (
126
- await import_peer('vite', process.cwd())
127
- );
125
+ export async function load_vite_config(config, vite) {
126
+ vite ??= /** @type {import('vite')} */ (await import_peer('vite', process.cwd()));
128
127
 
129
- return resolveConfig({ configFile: config }, 'build', process.env.MODE ?? 'production');
128
+ return vite.resolveConfig({ configFile: config }, 'build', process.env.MODE ?? 'production');
130
129
  }
131
130
 
132
131
  /**
package/src/core/env.js CHANGED
@@ -2,7 +2,6 @@
2
2
  /** @import { EnvVarConfig } from '@sveltejs/kit' */
3
3
  /** @import { ValidatedKitConfig } from 'types' */
4
4
  import path from 'node:path';
5
- import * as vite from 'vite';
6
5
  import * as devalue from 'devalue';
7
6
  import { GENERATED_COMMENT } from '../constants.js';
8
7
  import { dedent } from './sync/utils.js';
@@ -25,13 +24,14 @@ export function resolve_explicit_env_entry(config) {
25
24
  }
26
25
 
27
26
  /**
27
+ * @param {typeof import('vite')} vite
28
28
  * @param {ValidatedKitConfig} kit
29
29
  * @param {string | null} file
30
30
  * @param {string} root
31
31
  * @param {string} mode
32
32
  * @returns {Promise<Record<string, EnvVarConfig<any>> | null>}
33
33
  */
34
- export async function load_explicit_env(kit, file, root, mode) {
34
+ export async function load_explicit_env(vite, kit, file, root, mode) {
35
35
  if (!file) return null;
36
36
 
37
37
  const server = await vite.createServer({
@@ -53,7 +53,7 @@ export async function load_explicit_env(kit, file, root, mode) {
53
53
  /** @type {Record<string, EnvVarConfig<any>>} */
54
54
  let variables;
55
55
 
56
- const runner = get_runner(server);
56
+ const runner = get_runner(vite, server);
57
57
 
58
58
  /** @type {import('../runtime/app/env/internal.js')} */ (
59
59
  await runner.import(`${runtime_directory}/app/env/internal.js`)
@@ -94,6 +94,12 @@ export function generate_manifest({
94
94
  mime_types[ext] ??= mime_lookup(ext) || '';
95
95
  }
96
96
 
97
+ // record extensions that only exist in prerendered output, e.g. a prerendered favicon.ico
98
+ for (const pathname of prerendered) {
99
+ const ext = path.extname(pathname);
100
+ if (ext) mime_types[ext] ??= mime_lookup(ext) || '';
101
+ }
102
+
97
103
  // prettier-ignore
98
104
  // String representation of
99
105
  /** @template {import('@sveltejs/kit').SSRManifest} T */
@@ -2236,7 +2236,7 @@ const entities = {
2236
2236
  'zwnj;': '‌'
2237
2237
  };
2238
2238
 
2239
- const numeric = /&#(x)?([0-9a-f]+);/i;
2239
+ const numeric = /&#(x)?([0-9a-f]+);/gi;
2240
2240
  const named = new RegExp(
2241
2241
  `&(${Object.keys(entities)
2242
2242
  .sort((a, b) => b.length - a.length)
@@ -2247,6 +2247,12 @@ const named = new RegExp(
2247
2247
  /** @param {string} str */
2248
2248
  export function decode(str) {
2249
2249
  return str
2250
- .replace(numeric, (_match, hex, code) => String.fromCharCode(hex ? parseInt(code, 16) : +code))
2250
+ .replace(numeric, (_match, hex, code) => {
2251
+ const codepoint = hex ? parseInt(code, 16) : +code;
2252
+ // mirror the HTML parser, which replaces invalid numeric references with U+FFFD
2253
+ return Number.isInteger(codepoint) && codepoint <= 0x10ffff
2254
+ ? String.fromCodePoint(codepoint)
2255
+ : '\ufffd';
2256
+ })
2251
2257
  .replace(named, (_match, entity) => entities[entity]);
2252
2258
  }
@@ -39,7 +39,8 @@ async function generate_fallback({ manifest_path, env, out_dir, origin, assets }
39
39
  prerendering: {
40
40
  fallback: true,
41
41
  dependencies: new Map(),
42
- remote_responses: new Map()
42
+ remote_responses: new Map(),
43
+ resolved_route_ids: new Set()
43
44
  },
44
45
  read: (file) => readFileSync(join(assets, file))
45
46
  });
@@ -1,7 +1,7 @@
1
- import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
1
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
2
  import { dirname, join } from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
- import { mkdirp, walk } from '../../utils/filesystem.js';
4
+ import { walk } from '../../utils/filesystem.js';
5
5
  import { posixify } from '../../utils/os.js';
6
6
  import { noop } from '../../utils/functions.js';
7
7
  import { decode_uri, is_root_relative, resolve } from '../../utils/url.js';
@@ -17,6 +17,7 @@ import { createReadableStream } from '@sveltejs/kit/node';
17
17
  import generate_fallback from './fallback.js';
18
18
  import { stringify_remote_arg } from '../../runtime/shared.js';
19
19
  import { log_response } from '../../exports/vite/utils.js';
20
+ import { matches_content_type } from '../../utils/http.js';
20
21
 
21
22
  export default forked(import.meta.url, prerender);
22
23
 
@@ -156,7 +157,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
156
157
  const file = output_filename('/', true);
157
158
  const dest = `${config.outDir}/output/prerendered/pages/${file}`;
158
159
 
159
- mkdirp(dirname(dest));
160
+ mkdirSync(dirname(dest), { recursive: true });
160
161
  writeFileSync(dest, fallback);
161
162
 
162
163
  prerendered.pages.set('/', { file });
@@ -262,6 +263,9 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
262
263
  /** @type {Map<string, Promise<any>>} */
263
264
  const remote_responses = new Map();
264
265
 
266
+ /** @type {Set<string>} */
267
+ const resolved_route_ids = new Set();
268
+
265
269
  /** @type {Map<string, Set<string>>} */
266
270
  const expected_hashlinks = new Map();
267
271
 
@@ -307,7 +311,8 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
307
311
  },
308
312
  prerendering: {
309
313
  dependencies,
310
- remote_responses
314
+ remote_responses,
315
+ resolved_route_ids
311
316
  },
312
317
  read: (file) => {
313
318
  // stuff we just wrote
@@ -387,7 +392,11 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
387
392
  const headers = Object.fromEntries(response.headers);
388
393
 
389
394
  // if it's a 200 HTML response, crawl it. Skip error responses, as we don't save those
390
- if (response.ok && config.prerender.crawl && headers['content-type'] === 'text/html') {
395
+ if (
396
+ response.ok &&
397
+ config.prerender.crawl &&
398
+ matches_content_type(headers['content-type'], 'text/html')
399
+ ) {
391
400
  const { ids, hrefs, invalid } = crawl(body.toString(), decoded);
392
401
 
393
402
  for (const href of invalid) {
@@ -444,7 +453,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
444
453
  const headers = Object.fromEntries(response.headers);
445
454
 
446
455
  const type = headers['content-type'];
447
- const is_html = response_type === REDIRECT || type === 'text/html';
456
+ const is_html = response_type === REDIRECT || matches_content_type(type, 'text/html');
448
457
 
449
458
  if (!is_html && response.status === 200 && decoded.slice(config.paths.base.length + 1) === '') {
450
459
  throw new Error(
@@ -471,7 +480,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
471
480
  }
472
481
 
473
482
  if (!headers['x-sveltekit-normalize']) {
474
- mkdirp(dirname(dest));
483
+ mkdirSync(dirname(dest), { recursive: true });
475
484
 
476
485
  writeFileSync(
477
486
  dest,
@@ -517,7 +526,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
517
526
  );
518
527
  }
519
528
 
520
- mkdirp(dir);
529
+ mkdirSync(dir, { recursive: true });
521
530
 
522
531
  writeFileSync(dest, body);
523
532
  written.add(file);
@@ -620,14 +629,10 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
620
629
  }
621
630
  }
622
631
 
623
- const transport = (await internal.get_hooks()).transport ?? {};
624
632
  for (const internals of prerender_functions) {
625
633
  if (internals.has_arg) {
626
634
  for (const arg of (await internals.inputs?.()) ?? []) {
627
- void enqueue(
628
- null,
629
- remote_prefix + internals.id + '/' + stringify_remote_arg(arg, transport)
630
- );
635
+ void enqueue(null, remote_prefix + internals.id + '/' + stringify_remote_arg(arg));
631
636
  }
632
637
  } else {
633
638
  void enqueue(null, remote_prefix + internals.id);
@@ -60,7 +60,7 @@ function normalize_route_id(id) {
60
60
  .replace(/(?<=^|\/)\(.+?\)(?=$|\/)/g, '')
61
61
 
62
62
  .replace(/\[[ux]\+([0-9a-f]+)\]/g, (_, x) =>
63
- String.fromCharCode(parseInt(x, 16)).replace(/\//g, '%2f')
63
+ String.fromCodePoint(parseInt(x, 16)).replace(/\//g, '%2f')
64
64
  )
65
65
 
66
66
  // replace `[param]` with `<*>`, `[param=x]` with `<x>`, and `[[param]]` with `<?*>`
@@ -41,6 +41,34 @@ export default function create_manifest_data({
41
41
  };
42
42
  }
43
43
 
44
+ /**
45
+ * Whether this route has a `+page`. Independent of `is_endpoint_route` — a route can be both.
46
+ * @param {import('types').RouteData} route
47
+ * @returns {boolean}
48
+ */
49
+ export function is_page_route(route) {
50
+ return !!route.page;
51
+ }
52
+
53
+ /**
54
+ * Whether this route has a `+server`. Independent of `is_page_route` — a route can be both.
55
+ * @param {import('types').RouteData} route
56
+ * @returns {boolean}
57
+ */
58
+ export function is_endpoint_route(route) {
59
+ return !!route.endpoint;
60
+ }
61
+
62
+ /**
63
+ * Whether the router can match this route. `manifest_data.routes` also contains entries for
64
+ * directories with layouts or errors, which never reach `event.route.id`.
65
+ * @param {import('types').RouteData} route
66
+ * @returns {boolean}
67
+ */
68
+ export function is_app_route(route) {
69
+ return is_page_route(route) || is_endpoint_route(route);
70
+ }
71
+
44
72
  /**
45
73
  * Returns a list of files in the `static` directory.
46
74
  * @param {import('types').ValidatedConfig} config
@@ -85,7 +113,7 @@ function resolve_params(config, cwd) {
85
113
  */
86
114
  function create_routes_and_nodes(cwd, config, fallback) {
87
115
  /** @type {import('types').RouteData[]} */
88
- const routes = [];
116
+ let routes = [];
89
117
 
90
118
  const routes_base = posixify(path.relative(cwd, config.kit.files.routes));
91
119
 
@@ -125,7 +153,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
125
153
  );
126
154
  }
127
155
 
128
- return String.fromCharCode(parseInt(code, 16));
156
+ return String.fromCodePoint(parseInt(code, 16));
129
157
  }
130
158
  });
131
159
 
@@ -440,6 +468,9 @@ function create_routes_and_nodes(cwd, config, fallback) {
440
468
  }
441
469
  }
442
470
 
471
+ // remove route objects with no route file
472
+ routes = routes.filter((route) => route.endpoint || route.leaf || route.layout || route.error);
473
+
443
474
  // add parents to error nodes so that we can compute which page options apply to them
444
475
  for (const route of routes) {
445
476
  if (!route.error) continue;
@@ -89,13 +89,14 @@ export function all_types(config, root) {
89
89
 
90
90
  /**
91
91
  * Generate modules and types for explicit env vars
92
+ * @param {typeof import('vite')} vite
92
93
  * @param {import('types').ValidatedKitConfig} kit
93
94
  * @param {string | null} entry
94
95
  * @param {string} root The Vite root
95
96
  * @param {string} mode The Vite mode
96
97
  */
97
- export async function env(kit, entry, root, mode) {
98
- const env_config = await load_explicit_env(kit, entry, root, mode);
98
+ export async function env(vite, kit, entry, root, mode) {
99
+ const env_config = await load_explicit_env(vite, kit, entry, root, mode);
99
100
 
100
101
  write_env(entry, env_config, root);
101
102
 
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { styleText } from 'node:util';
4
- import { mkdirp, resolve_entry } from '../../utils/filesystem.js';
4
+ import { resolve_entry } from '../../utils/filesystem.js';
5
5
 
6
6
  /** @type {Map<string, string>} */
7
7
  const previous_contents = new Map();
@@ -22,7 +22,7 @@ export function write_if_changed(file, code) {
22
22
  */
23
23
  export function write(file, code) {
24
24
  previous_contents.set(file, code);
25
- mkdirp(path.dirname(file));
25
+ fs.mkdirSync(path.dirname(file), { recursive: true });
26
26
  fs.writeFileSync(file, code);
27
27
  }
28
28
 
@@ -4,15 +4,42 @@ import { resolve_entry } from '../../utils/filesystem.js';
4
4
  import { posixify } from '../../utils/os.js';
5
5
  import { write_if_changed } from './utils.js';
6
6
  import { s } from '../../utils/misc.js';
7
- import { get_route_segments } from '../../utils/routing.js';
8
-
9
- const replace_optional_params = (/** @type {string} */ id) =>
10
- id.replace(/\/\[\[[^\]]+\]\]/g, '${string}');
11
- const replace_required_params = (/** @type {string} */ id) =>
12
- id.replace(/\/\[[^\]]+\]/g, '/${string}');
13
- /** Convert route ID to pathname by removing layout groups */
14
- const remove_group_segments = (/** @type {string} */ id) => {
15
- return '/' + get_route_segments(id).join('/');
7
+ import {
8
+ decode_escape_sequence,
9
+ encode_pathname_chars,
10
+ get_route_segments,
11
+ segment_pattern
12
+ } from '../../utils/routing.js';
13
+ import { is_app_route, is_endpoint_route, is_page_route } from './create_manifest_data/index.js';
14
+
15
+ const optional_param_pattern = /^\[\[[\w-]+(?:=[\w-]+)?\]\]$/;
16
+ const rest_param_pattern = /^\[\.\.\.[\w-]+(?:=[\w-]+)?\]$/;
17
+
18
+ /**
19
+ * Convert a route ID to the pathnames it can match (relative to the base path), in which each
20
+ * param is replaced with `${string}` and each escape sequence is expanded. A param that fills an
21
+ * entire segment can be absent, so it contributes a pathname with the segment and one without,
22
+ * rather than one that absorbs the `/`
23
+ * @param {string} id
24
+ */
25
+ const get_pathname_patterns = (id) => {
26
+ let pathnames = [''];
27
+
28
+ for (const segment of get_route_segments(id)) {
29
+ const omittable = optional_param_pattern.test(segment) || rest_param_pattern.test(segment);
30
+ const content = omittable
31
+ ? '${string}'
32
+ : segment.replace(segment_pattern, (_, escape_type, escape_code) =>
33
+ escape_type ? encode_pathname_chars(decode_escape_sequence(escape_code)) : '${string}'
34
+ );
35
+
36
+ pathnames = pathnames.flatMap((pathname) => {
37
+ const joined = pathname === '' ? content : `${pathname}/${content}`;
38
+ return omittable ? [joined, pathname] : [joined];
39
+ });
40
+ }
41
+
42
+ return [...new Set(pathnames)];
16
43
  };
17
44
 
18
45
  /**
@@ -142,6 +169,15 @@ function generate_app_types(manifest_data, config, dir) {
142
169
  /** @type {Set<string>} */
143
170
  const pathnames = new Set();
144
171
 
172
+ /** @type {string[]} */
173
+ const page_route_ids = [];
174
+
175
+ /** @type {string[]} */
176
+ const endpoint_route_ids = [];
177
+
178
+ /** @type {string[]} */
179
+ const app_route_ids = [];
180
+
145
181
  /** @type {string[]} */
146
182
  const dynamic_routes = [];
147
183
 
@@ -150,15 +186,17 @@ function generate_app_types(manifest_data, config, dir) {
150
186
 
151
187
  /** @type {Map<string, Map<string, { optional: boolean, matchers: Set<string> | null }>>} */
152
188
  const layout_params_by_route = new Map(
153
- manifest_data.routes.map((route) => [
154
- route.id,
155
- new Map(
156
- route.params.map((p) => [
157
- p.name,
158
- { optional: p.optional, matchers: p.matcher ? new Set([p.matcher]) : null }
159
- ])
160
- )
161
- ])
189
+ manifest_data.routes
190
+ .filter((route) => route.layout)
191
+ .map((route) => [
192
+ route.id,
193
+ new Map(
194
+ route.params.map((p) => [
195
+ p.name,
196
+ { optional: p.optional, matchers: p.matcher ? new Set([p.matcher]) : null }
197
+ ])
198
+ )
199
+ ])
162
200
  );
163
201
 
164
202
  for (const route of manifest_data.routes) {
@@ -192,8 +230,11 @@ function generate_app_types(manifest_data, config, dir) {
192
230
  }
193
231
 
194
232
  for (const route of manifest_data.routes) {
195
- const pathname = remove_group_segments(route.id);
196
- let normalized_pathname = pathname.slice(1);
233
+ const id = s(route.id);
234
+
235
+ if (is_page_route(route)) page_route_ids.push(id);
236
+ if (is_endpoint_route(route)) endpoint_route_ids.push(id);
237
+ if (is_app_route(route)) app_route_ids.push(id);
197
238
 
198
239
  /** @type {(path: string) => string} */
199
240
  let serialise = s;
@@ -203,16 +244,18 @@ function generate_app_types(manifest_data, config, dir) {
203
244
  const type = get_matcher_type(p.matcher);
204
245
  return `${/^\w+$/.test(p.name) ? p.name : `'${p.name}'`}${p.optional ? '?:' : ':'} ${type}${p.optional ? ' | undefined' : ''}`;
205
246
  });
206
- const route_type = `${s(route.id)}: { ${params.join('; ')} }`;
207
247
 
208
- dynamic_routes.push(route_type);
248
+ if (is_app_route(route)) {
249
+ dynamic_routes.push(`${s(route.id)}: { ${params.join('; ')} }`);
250
+ }
209
251
 
210
- normalized_pathname = replace_required_params(replace_optional_params(pathname)).slice(1);
211
252
  serialise = (p) => `\`${p}\` & {}`;
212
253
  }
213
254
 
214
- for (const p of get_pathnames_for_trailing_slash(normalized_pathname, route)) {
215
- pathnames.add(serialise(p));
255
+ for (const pathname of get_pathname_patterns(route.id)) {
256
+ for (const p of get_pathnames_for_trailing_slash(pathname, route)) {
257
+ pathnames.add(serialise(p));
258
+ }
216
259
  }
217
260
 
218
261
  let layout_type = 'Record<string, never>';
@@ -229,7 +272,7 @@ function generate_app_types(manifest_data, config, dir) {
229
272
  if (params.length > 0) layout_type = `{ ${params} }`;
230
273
  }
231
274
 
232
- layouts.push(`${s(route.id)}: ${layout_type}`);
275
+ if (route.layout) layouts.push(`${s(route.id)}: ${layout_type}`);
233
276
  }
234
277
 
235
278
  const assets = manifest_data.assets.map((asset) => s(asset.file));
@@ -237,7 +280,9 @@ function generate_app_types(manifest_data, config, dir) {
237
280
  return [
238
281
  'declare module "$app/types" {',
239
282
  '\texport interface AppTypes {',
240
- `\t\tRouteId(): ${manifest_data.routes.map((r) => s(r.id)).join(' | ')};`,
283
+ `\t\tPageRouteId(): ${page_route_ids.join(' | ') || 'never'};`,
284
+ `\t\tEndpointRouteId(): ${endpoint_route_ids.join(' | ') || 'never'};`,
285
+ `\t\tRouteId(): ${app_route_ids.join(' | ') || 'never'};`,
241
286
  `\t\tRouteParams(): {\n\t\t\t${dynamic_routes.join(';\n\t\t\t')}\n\t\t};`,
242
287
  `\t\tLayoutParams(): {\n\t\t\t${layouts.join(';\n\t\t\t')}\n\t\t};`,
243
288
  `\t\tPath(): ${Array.from(pathnames).join(' | ')};`,