@sveltejs/kit 3.0.0-next.1 → 3.0.0-next.11

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 (144) hide show
  1. package/package.json +33 -21
  2. package/src/constants.js +2 -0
  3. package/src/core/adapt/builder.js +32 -49
  4. package/src/core/adapt/index.js +1 -4
  5. package/src/core/config/index.js +137 -71
  6. package/src/core/config/options.js +236 -244
  7. package/src/core/config/types.d.ts +1 -1
  8. package/src/core/env.js +121 -5
  9. package/src/core/generate_manifest/index.js +12 -15
  10. package/src/core/postbuild/analyse.js +7 -19
  11. package/src/core/postbuild/crawl.js +22 -6
  12. package/src/core/postbuild/fallback.js +2 -1
  13. package/src/core/postbuild/prerender.js +114 -48
  14. package/src/core/postbuild/queue.js +2 -1
  15. package/src/core/sync/create_manifest_data/index.js +53 -50
  16. package/src/core/sync/sync.js +0 -2
  17. package/src/core/sync/write_client_manifest.js +8 -15
  18. package/src/core/sync/write_env.js +2 -1
  19. package/src/core/sync/write_non_ambient.js +12 -9
  20. package/src/core/sync/write_server.js +14 -15
  21. package/src/core/sync/write_tsconfig.js +16 -8
  22. package/src/core/sync/write_tsconfig_test/package.json +7 -0
  23. package/src/core/sync/write_types/index.js +28 -24
  24. package/src/core/utils.js +30 -5
  25. package/src/exports/env/index.js +77 -0
  26. package/src/exports/hooks/index.js +3 -9
  27. package/src/exports/hooks/sequence.js +3 -2
  28. package/src/exports/index.js +35 -13
  29. package/src/exports/internal/client.js +5 -0
  30. package/src/exports/internal/env.js +8 -5
  31. package/src/exports/internal/index.js +1 -90
  32. package/src/exports/internal/{event.js → server/event.js} +1 -2
  33. package/src/exports/internal/server/index.js +33 -0
  34. package/src/exports/internal/shared.js +89 -0
  35. package/src/exports/node/index.js +62 -15
  36. package/src/exports/params.js +68 -0
  37. package/src/exports/public.d.ts +363 -189
  38. package/src/exports/url.js +86 -0
  39. package/src/exports/vite/build/build_server.js +53 -59
  40. package/src/exports/vite/build/remote.js +18 -11
  41. package/src/exports/vite/build/utils.js +0 -8
  42. package/src/exports/vite/dev/index.js +92 -50
  43. package/src/exports/vite/index.js +903 -681
  44. package/src/exports/vite/module_ids.js +0 -2
  45. package/src/exports/vite/preview/index.js +32 -24
  46. package/src/exports/vite/static_analysis/index.js +2 -4
  47. package/src/exports/vite/static_analysis/types.d.ts +14 -0
  48. package/src/exports/vite/utils.js +40 -35
  49. package/src/runtime/app/env/internal.js +4 -4
  50. package/src/runtime/app/env/types.d.ts +1 -1
  51. package/src/runtime/app/environment/index.js +3 -3
  52. package/src/runtime/app/forms.js +24 -7
  53. package/src/runtime/app/paths/client.js +4 -10
  54. package/src/runtime/app/paths/internal/client.js +4 -2
  55. package/src/runtime/app/paths/internal/server.js +2 -23
  56. package/src/runtime/app/paths/public.d.ts +0 -28
  57. package/src/runtime/app/paths/server.js +10 -6
  58. package/src/runtime/app/server/index.js +1 -1
  59. package/src/runtime/app/server/remote/command.js +0 -3
  60. package/src/runtime/app/server/remote/form.js +27 -15
  61. package/src/runtime/app/server/remote/prerender.js +29 -36
  62. package/src/runtime/app/server/remote/query.js +101 -99
  63. package/src/runtime/app/server/remote/requested.js +22 -14
  64. package/src/runtime/app/server/remote/shared.js +30 -26
  65. package/src/runtime/app/state/client.js +1 -2
  66. package/src/runtime/app/stores.js +13 -76
  67. package/src/runtime/client/bundle.js +1 -1
  68. package/src/runtime/client/client-entry.js +3 -0
  69. package/src/runtime/client/client.js +411 -318
  70. package/src/runtime/client/entry.js +24 -3
  71. package/src/runtime/client/fetcher.js +3 -2
  72. package/src/runtime/client/ndjson.js +6 -33
  73. package/src/runtime/client/payload.js +17 -0
  74. package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
  75. package/src/runtime/client/remote-functions/command.svelte.js +7 -32
  76. package/src/runtime/client/remote-functions/form.svelte.js +183 -112
  77. package/src/runtime/client/remote-functions/prerender.svelte.js +29 -8
  78. package/src/runtime/client/remote-functions/query/index.js +7 -14
  79. package/src/runtime/client/remote-functions/query/instance.svelte.js +63 -18
  80. package/src/runtime/client/remote-functions/query/proxy.js +3 -3
  81. package/src/runtime/client/remote-functions/query-batch.svelte.js +60 -68
  82. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +60 -18
  83. package/src/runtime/client/remote-functions/query-live/iterator.js +36 -55
  84. package/src/runtime/client/remote-functions/shared.svelte.js +88 -62
  85. package/src/runtime/client/sse.js +32 -0
  86. package/src/runtime/client/state.svelte.js +65 -49
  87. package/src/runtime/client/stream.js +39 -0
  88. package/src/runtime/client/types.d.ts +11 -7
  89. package/src/runtime/client/utils.js +0 -96
  90. package/src/runtime/components/root.svelte +66 -0
  91. package/src/runtime/env/dynamic/private.js +7 -0
  92. package/src/runtime/env/dynamic/public.js +7 -0
  93. package/src/runtime/env/static/private.js +6 -0
  94. package/src/runtime/env/static/public.js +6 -0
  95. package/src/runtime/form-utils.js +100 -22
  96. package/src/runtime/server/cookie.js +69 -52
  97. package/src/runtime/server/csrf.js +65 -0
  98. package/src/runtime/server/data/index.js +15 -19
  99. package/src/runtime/server/env_module.js +0 -5
  100. package/src/runtime/server/errors.js +135 -0
  101. package/src/runtime/server/fetch.js +1 -1
  102. package/src/runtime/server/index.js +22 -12
  103. package/src/runtime/server/internal.js +25 -0
  104. package/src/runtime/server/page/actions.js +43 -27
  105. package/src/runtime/server/page/data_serializer.js +10 -10
  106. package/src/runtime/server/page/index.js +12 -17
  107. package/src/runtime/server/page/load_data.js +2 -2
  108. package/src/runtime/server/page/render.js +114 -193
  109. package/src/runtime/server/page/respond_with_error.js +11 -11
  110. package/src/runtime/server/page/server_routing.js +27 -18
  111. package/src/runtime/server/remote-functions.js +596 -0
  112. package/src/runtime/server/respond.js +100 -68
  113. package/src/runtime/server/sourcemaps.js +183 -0
  114. package/src/runtime/server/utils.js +2 -134
  115. package/src/runtime/shared.js +83 -13
  116. package/src/runtime/telemetry/otel.js +1 -1
  117. package/src/runtime/types.d.ts +8 -0
  118. package/src/types/ambient-private.d.ts +2 -0
  119. package/src/types/ambient.d.ts +10 -5
  120. package/src/types/global-private.d.ts +13 -25
  121. package/src/types/internal.d.ts +95 -80
  122. package/src/types/private.d.ts +41 -1
  123. package/src/utils/error.js +24 -4
  124. package/src/utils/escape.js +9 -25
  125. package/src/utils/features.js +1 -1
  126. package/src/utils/fork.js +7 -2
  127. package/src/utils/import.js +6 -1
  128. package/src/utils/imports.js +83 -0
  129. package/src/utils/mime.js +9 -0
  130. package/src/utils/page_nodes.js +3 -5
  131. package/src/utils/params.js +66 -0
  132. package/src/utils/routing.js +90 -44
  133. package/src/utils/shared-iterator.js +5 -0
  134. package/src/utils/streaming.js +14 -4
  135. package/src/utils/url.js +20 -2
  136. package/src/version.js +1 -1
  137. package/types/index.d.ts +537 -557
  138. package/types/index.d.ts.map +24 -39
  139. package/src/core/sync/write_root.js +0 -148
  140. package/src/exports/internal/server.js +0 -22
  141. package/src/runtime/server/remote.js +0 -457
  142. package/src/runtime/shared-server.js +0 -7
  143. package/src/types/synthetic/$lib.md +0 -5
  144. /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
@@ -1,4 +1,3 @@
1
- /** @import { Adapter } from '@sveltejs/kit' */
2
1
  import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
3
2
  import { dirname, join } from 'node:path';
4
3
  import { pathToFileURL } from 'node:url';
@@ -17,6 +16,7 @@ import * as devalue from 'devalue';
17
16
  import { createReadableStream } from '@sveltejs/kit/node';
18
17
  import generate_fallback from './fallback.js';
19
18
  import { stringify_remote_arg } from '../../runtime/shared.js';
19
+ import { log_response } from '../../exports/vite/utils.js';
20
20
 
21
21
  export default forked(import.meta.url, prerender);
22
22
 
@@ -44,37 +44,74 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
44
44
  /** @type {import('types').ServerInternalModule} */
45
45
  const internal = await import(pathToFileURL(`${out}/server/internal.js`).href);
46
46
 
47
- /** @type {import('types').ServerModule} */
48
- const { Server } = await import(pathToFileURL(`${out}/server/index.js`).href);
49
-
50
47
  // configure `import { building } from `$app/env` —
51
48
  // essential we do this before analysing the code
52
49
  internal.set_building();
53
50
  internal.set_prerendering();
54
51
 
52
+ // `set_env` and `Server` live in modules that import the user's `src/env` config. We import them
53
+ // *after* `set_building()` so that `building`-dependent expressions resolve correctly
54
+ /** @type {import('__sveltekit/env')} */
55
+ const { set_env } = await import(pathToFileURL(`${out}/server/env.js`).href);
56
+ set_env(env);
57
+
58
+ /** @type {import('types').ServerModule} */
59
+ const { Server } = await import(pathToFileURL(`${out}/server/index.js`).href);
60
+
61
+ const throw_handled = () => {
62
+ throw new Error('__handled__');
63
+ };
64
+
55
65
  /**
56
66
  * @template {{message: string}} T
57
67
  * @template {Omit<T, 'message'>} K
58
- * @param {import('types').Logger} log
59
- * @param {'fail' | 'warn' | 'ignore' | ((details: T) => void)} input
68
+ * @param {string} name
69
+ * @param {'fail' | 'warn' | 'ignore' | undefined | ((details: T) => void)} input
60
70
  * @param {(details: K) => string} format
61
- * @returns {(details: K) => void}
71
+ * @returns {(details: K & { error?: unknown }) => void}
62
72
  */
63
- function normalise_error_handler(log, input, format) {
73
+ function normalise_error_handler(name, input, format) {
74
+ /**
75
+ * @param {any} details
76
+ */
77
+ function log_failure(details) {
78
+ const message = format(details);
79
+ log.error(`\n${message}\n`);
80
+ }
81
+
64
82
  switch (input) {
65
83
  case 'fail':
66
84
  return (details) => {
67
- throw new Error(format(details));
85
+ log_failure(details);
86
+ throw_handled();
68
87
  };
69
88
  case 'warn':
70
- return (details) => {
71
- log.error(format(details));
72
- };
89
+ return log_failure;
73
90
  case 'ignore':
74
91
  return noop;
92
+ case undefined: {
93
+ return (details) => {
94
+ log_failure(details);
95
+
96
+ log.err(
97
+ `To suppress or handle this error, implement \`${name}\` in https://svelte.dev/docs/kit/configuration#prerender\n`
98
+ );
99
+
100
+ throw_handled();
101
+ };
102
+ }
75
103
  default:
76
- // @ts-expect-error TS thinks T might be of a different kind, but it's not
77
- return (details) => input({ ...details, message: format(details) });
104
+ return (details) => {
105
+ const message = format(details);
106
+
107
+ try {
108
+ // @ts-expect-error TS thinks T might be of a different kind, but it's not
109
+ input({ ...details, message });
110
+ } catch (error) {
111
+ log.prettyError(error, import.meta.dirname);
112
+ throw_handled();
113
+ }
114
+ };
78
115
  }
79
116
  }
80
117
 
@@ -105,12 +142,14 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
105
142
 
106
143
  const config = extract_svelte_config(vite_config).kit;
107
144
 
145
+ const prerender_origin = config.paths.origin || 'http://sveltekit-prerender';
146
+
108
147
  if (hash) {
109
148
  const fallback = await generate_fallback({
110
149
  manifest_path,
111
150
  env,
112
151
  out_dir: config.outDir,
113
- origin: config.prerender.origin,
152
+ origin: prerender_origin,
114
153
  assets: config.files.assets
115
154
  });
116
155
 
@@ -125,13 +164,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
125
164
  return { prerendered, prerender_map };
126
165
  }
127
166
 
128
- // TODO this can just be config.adapter?
129
- /** @type {Adapter | undefined} */
130
- const adapter = vite_config.plugins.find(
131
- (plugin) => plugin.name === 'vite-plugin-sveltekit-adapter'
132
- )?.api?.adapter;
133
-
134
- const emulator = await adapter?.emulate?.();
167
+ const emulator = await config.adapter?.emulate?.();
135
168
 
136
169
  /** @type {import('types').Logger} */
137
170
  const log = logger({ verbose });
@@ -140,43 +173,58 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
140
173
  const saved = new Map();
141
174
 
142
175
  const handle_http_error = normalise_error_handler(
143
- log,
176
+ 'handleHttpError',
144
177
  config.prerender.handleHttpError,
145
178
  ({ status, path, referrer, referenceType }) => {
146
- const message =
147
- status === 404 && !path.startsWith(config.paths.base)
148
- ? `${path} does not begin with \`base\`, which is configured in \`paths.base\` and can be imported from \`$app/paths\` - see https://svelte.dev/docs/kit/configuration#paths for more info`
149
- : path;
179
+ let message = `Failed to prerender ${path}`;
150
180
 
151
- return `${status} ${message}${referrer ? ` (${referenceType} from ${referrer})` : ''}`;
181
+ if (status === 404) {
182
+ if (!path.startsWith(config.paths.base)) {
183
+ message = referrer ? `${path} (${referenceType} from ${referrer})` : path;
184
+
185
+ message += ` does not begin with \`base\`. You can fix this by using \`resolve('${path}')\` from \`$app/paths\`. The base path is configurable from \`paths.base\``;
186
+ } else if (referrer) {
187
+ message = `${path} was ${referenceType} from ${referrer}`;
188
+ }
189
+ }
190
+
191
+ return message;
152
192
  }
153
193
  );
154
194
 
155
195
  const handle_missing_id = normalise_error_handler(
156
- log,
196
+ 'handleMissingId',
157
197
  config.prerender.handleMissingId,
158
198
  ({ path, id, referrers }) => {
159
199
  return (
160
- `The following pages contain links to ${path}#${id}, but no element with id="${id}" exists on ${path} - see the \`handleMissingId\` option in https://svelte.dev/docs/kit/configuration#prerender for more info:` +
200
+ `The following pages contain links to ${path}#${id}, but no element with id="${id}" exists on ${path}:` +
161
201
  referrers.map((l) => `\n - ${l}`).join('')
162
202
  );
163
203
  }
164
204
  );
165
205
 
166
206
  const handle_entry_generator_mismatch = normalise_error_handler(
167
- log,
207
+ 'handleEntryGeneratorMismatch',
168
208
  config.prerender.handleEntryGeneratorMismatch,
169
209
  ({ generatedFromId, entry, matchedId }) => {
170
- return `The entries export from ${generatedFromId} generated entry ${entry}, which was matched by ${matchedId} - see the \`handleEntryGeneratorMismatch\` option in https://svelte.dev/docs/kit/configuration#prerender for more info.`;
210
+ return `The entries export from ${generatedFromId} generated entry ${entry}, which was matched by ${matchedId === entry ? 'a static route' : matchedId}`;
171
211
  }
172
212
  );
173
213
 
174
214
  const handle_not_prerendered_route = normalise_error_handler(
175
- log,
215
+ 'handleUnseenRoutes',
176
216
  config.prerender.handleUnseenRoutes,
177
217
  ({ routes }) => {
178
218
  const list = routes.map((id) => ` - ${id}`).join('\n');
179
- return `The following routes were marked as prerenderable, but were not prerendered because they were not found while crawling your app:\n${list}\n\nSee the \`handleUnseenRoutes\` option in https://svelte.dev/docs/kit/configuration#prerender for more info.`;
219
+ return `The following routes were marked as prerenderable, but were not prerendered because they were not found while crawling your app:\n${list}`;
220
+ }
221
+ );
222
+
223
+ const handle_invalid_url = normalise_error_handler(
224
+ 'handleInvalidUrl',
225
+ config.prerender.handleInvalidUrl,
226
+ ({ href, referrer }) => {
227
+ return `Invalid URL ${href}${referrer ? ` (linked from ${referrer})` : ''}`;
180
228
  }
181
229
  );
182
230
 
@@ -210,6 +258,8 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
210
258
 
211
259
  const seen = new Set();
212
260
  const written = new Set();
261
+
262
+ /** @type {Map<string, Promise<any>>} */
213
263
  const remote_responses = new Map();
214
264
 
215
265
  /** @type {Map<string, Set<string>>} */
@@ -249,7 +299,9 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
249
299
  /** @type {Map<string, import('types').PrerenderDependency>} */
250
300
  const dependencies = new Map();
251
301
 
252
- const response = await server.respond(new Request(config.prerender.origin + encoded), {
302
+ const request = new Request(prerender_origin + encoded);
303
+
304
+ const response = await server.respond(request, {
253
305
  getClientAddress() {
254
306
  throw new Error('Cannot read clientAddress during prerendering');
255
307
  },
@@ -287,6 +339,10 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
287
339
  });
288
340
  }
289
341
 
342
+ if (response.status !== 204) {
343
+ log_response(response.status, request);
344
+ }
345
+
290
346
  const body = Buffer.from(await response.arrayBuffer());
291
347
 
292
348
  const category = decoded.startsWith(remote_prefix) ? 'data' : 'pages';
@@ -332,16 +388,20 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
332
388
 
333
389
  // if it's a 200 HTML response, crawl it. Skip error responses, as we don't save those
334
390
  if (response.ok && config.prerender.crawl && headers['content-type'] === 'text/html') {
335
- const { ids, hrefs } = crawl(body.toString(), decoded);
391
+ const { ids, hrefs, invalid } = crawl(body.toString(), decoded);
392
+
393
+ for (const href of invalid) {
394
+ handle_invalid_url({ href, referrer: decoded });
395
+ }
336
396
 
337
397
  actual_hashlinks.set(decoded, ids);
338
398
 
339
399
  /** @param {string} href */
340
400
  const removePrerenderOrigin = (href) => {
341
- if (href.startsWith(config.prerender.origin)) {
342
- if (href === config.prerender.origin) return '/';
343
- if (href.at(config.prerender.origin.length) !== '/') return href;
344
- return href.slice(config.prerender.origin.length);
401
+ if (href.startsWith(prerender_origin)) {
402
+ if (href === prerender_origin) return '/';
403
+ if (href.at(prerender_origin.length) !== '/') return href;
404
+ return href.slice(prerender_origin.length);
345
405
  }
346
406
  return href;
347
407
  };
@@ -386,6 +446,12 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
386
446
  const type = headers['content-type'];
387
447
  const is_html = response_type === REDIRECT || type === 'text/html';
388
448
 
449
+ if (!is_html && response.status === 200 && decoded.slice(config.paths.base.length + 1) === '') {
450
+ throw new Error(
451
+ `Cannot prerender a root +server.js that returns a non-HTML response - static hosts always serve an HTML file for \`${config.paths.base || '/'}\``
452
+ );
453
+ }
454
+
389
455
  const file = output_filename(decoded, is_html);
390
456
  const dest = `${config.outDir}/output/prerendered/${category}/${file}`;
391
457
 
@@ -407,8 +473,6 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
407
473
  if (!headers['x-sveltekit-normalize']) {
408
474
  mkdirp(dirname(dest));
409
475
 
410
- log.warn(`${response.status} ${decoded} -> ${location}`);
411
-
412
476
  writeFileSync(
413
477
  dest,
414
478
  `<script>location.href=${devalue.uneval(
@@ -455,7 +519,6 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
455
519
 
456
520
  mkdirp(dir);
457
521
 
458
- log.info(`${response.status} ${decoded}`);
459
522
  writeFileSync(dest, body);
460
523
  written.add(file);
461
524
 
@@ -471,7 +534,12 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
471
534
 
472
535
  prerendered.paths.push(decoded);
473
536
  } else if (response_type !== OK) {
474
- handle_http_error({ status: response.status, path: decoded, referrer, referenceType });
537
+ handle_http_error({
538
+ status: response.status,
539
+ path: decoded,
540
+ referrer,
541
+ referenceType
542
+ });
475
543
  }
476
544
 
477
545
  manifest.assets.add(file);
@@ -495,10 +563,8 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
495
563
  }
496
564
  }
497
565
 
498
- // the user's remote function modules may reference environment variables,
499
- // `read` or the `manifest` at the top-level so we need to set them before
500
- // evaluating those modules to avoid potential runtime errors
501
- internal.set_env(env);
566
+ // the user's remote function modules may reference `read` or the `manifest` at the top-level
567
+ // so we need to set them before evaluating those modules to avoid potential runtime errors
502
568
  internal.set_manifest(manifest);
503
569
  internal.set_read_implementation((file) => createReadableStream(`${out}/server/${file}`));
504
570
 
@@ -28,7 +28,7 @@ export function queue(concurrency) {
28
28
 
29
29
  if (task) {
30
30
  current += 1;
31
- const promise = Promise.resolve(task.fn());
31
+ const promise = (async () => task.fn())(); // could throw synchronously
32
32
 
33
33
  void promise
34
34
  .then(task.fulfil, (err) => {
@@ -54,6 +54,7 @@ export function queue(concurrency) {
54
54
  const promise = new Promise((fulfil, reject) => {
55
55
  tasks.push({ fn, fulfil, reject });
56
56
  });
57
+ promise.catch(() => {});
57
58
 
58
59
  dequeue();
59
60
  return promise;
@@ -1,4 +1,4 @@
1
- import { lookup } from 'mrmime';
1
+ import { lookup } from '../../../utils/mime.js';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { styleText } from 'node:util';
@@ -29,27 +29,20 @@ export default function create_manifest_data({
29
29
  }) {
30
30
  const assets = create_assets(config);
31
31
  const hooks = create_hooks(config, cwd);
32
- const matchers = create_matchers(config, cwd);
32
+ const params = resolve_params(config, cwd);
33
33
  const { nodes, routes } = create_routes_and_nodes(cwd, config, fallback);
34
34
 
35
- for (const route of routes) {
36
- for (const param of route.params) {
37
- if (param.matcher && !matchers[param.matcher]) {
38
- throw new Error(`No matcher found for parameter '${param.matcher}' in route ${route.id}`);
39
- }
40
- }
41
- }
42
-
43
35
  return {
44
36
  assets,
45
37
  hooks,
46
- matchers,
38
+ params,
47
39
  nodes,
48
40
  routes
49
41
  };
50
42
  }
51
43
 
52
44
  /**
45
+ * Returns a list of files in the `static` directory.
53
46
  * @param {import('types').ValidatedConfig} config
54
47
  */
55
48
  export function create_assets(config) {
@@ -80,38 +73,9 @@ function create_hooks(config, cwd) {
80
73
  * @param {import('types').ValidatedConfig} config
81
74
  * @param {string} cwd
82
75
  */
83
- function create_matchers(config, cwd) {
84
- const params_base = path.relative(cwd, config.kit.files.params);
85
-
86
- /** @type {Record<string, string>} */
87
- const matchers = {};
88
- if (fs.existsSync(config.kit.files.params)) {
89
- for (const file of fs.readdirSync(config.kit.files.params)) {
90
- const ext = path.extname(file);
91
- if (!config.kit.moduleExtensions.includes(ext)) continue;
92
- const type = file.slice(0, -ext.length);
93
-
94
- if (/^\w+$/.test(type)) {
95
- const matcher_file = path.join(params_base, file);
96
-
97
- // Disallow same matcher with different extensions
98
- if (matchers[type]) {
99
- throw new Error(`Duplicate matchers: ${matcher_file} and ${matchers[type]}`);
100
- } else {
101
- matchers[type] = matcher_file;
102
- }
103
- } else {
104
- // Allow for matcher test collocation
105
- if (type.endsWith('.test') || type.endsWith('.spec')) continue;
106
-
107
- throw new Error(
108
- `Matcher names can only have underscores and alphanumeric characters — "${file}" is invalid`
109
- );
110
- }
111
- }
112
- }
113
-
114
- return matchers;
76
+ function resolve_params(config, cwd) {
77
+ const params_file = resolve_entry(config.kit.files.params);
78
+ return params_file ? posixify(path.relative(cwd, params_file)) : null;
115
79
  }
116
80
 
117
81
  /**
@@ -130,6 +94,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
130
94
  /** @type {import('types').PageNode[]} */
131
95
  const nodes = [];
132
96
 
97
+ // create route data by processing files in `src/routes`
133
98
  if (fs.existsSync(config.kit.files.routes)) {
134
99
  /**
135
100
  * @param {number} depth
@@ -177,7 +142,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
177
142
  throw new Error(`Route ${id} should be renamed to ${id.replace(/#/g, '[x+23]')}`);
178
143
  }
179
144
 
180
- if (/\[\.\.\.\w+\]\/\[\[/.test(id)) {
145
+ if (/\[\.\.\.[\w-]+\]\/\[\[/.test(id)) {
181
146
  throw new Error(
182
147
  `Invalid route ${id} — an [[optional]] route segment cannot follow a [...rest] route segment`
183
148
  );
@@ -218,10 +183,17 @@ function create_routes_and_nodes(cwd, config, fallback) {
218
183
 
219
184
  // We can't use withFileTypes because of a NodeJs bug which returns wrong results
220
185
  // with isDirectory() in case of symlinks: https://github.com/nodejs/node/issues/30646
221
- const files = fs.readdirSync(dir).map((name) => ({
222
- is_dir: fs.statSync(path.join(dir, name)).isDirectory(),
223
- name
224
- }));
186
+ // We sort the entries because `readdirSync` order is not guaranteed and differs
187
+ // between runtimes (e.g. Node returns entries alphabetically, Bun in directory
188
+ // order). Node indices are assigned from this traversal order, so without sorting
189
+ // the SSR and client manifests can disagree, causing hydration mismatches.
190
+ const files = fs
191
+ .readdirSync(dir)
192
+ .sort()
193
+ .map((name) => ({
194
+ is_dir: fs.statSync(path.join(dir, name)).isDirectory(),
195
+ name
196
+ }));
225
197
 
226
198
  // process files first
227
199
  for (const file of files) {
@@ -365,7 +337,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
365
337
  const root = routes[0];
366
338
  if (!root.leaf && !root.error && !root.layout && !root.endpoint) {
367
339
  throw new Error(
368
- 'No routes found. If you are using a custom src/routes directory, make sure it is specified in your Svelte config file'
340
+ 'No routes found. If you are using a custom src/routes directory, make sure it is specified in your SvelteKit Vite plugin options'
369
341
  );
370
342
  }
371
343
  }
@@ -388,6 +360,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
388
360
 
389
361
  prevent_conflicts(routes);
390
362
 
363
+ // fallback root layout and root error components
391
364
  const root = routes[0];
392
365
 
393
366
  if (!root.layout?.component) {
@@ -396,10 +369,11 @@ function create_routes_and_nodes(cwd, config, fallback) {
396
369
  }
397
370
 
398
371
  if (!root.error?.component) {
399
- if (!root.error) root.error = { depth: 0 };
372
+ if (!root.error) root.error = { depth: 0, parent: root.layout };
400
373
  root.error.component = posixify(path.relative(cwd, `${fallback}/error.svelte`));
401
374
  }
402
375
 
376
+ // populate the page nodes list
403
377
  // we do layouts/errors first as they are more likely to be reused,
404
378
  // and smaller indexes take fewer bytes. also, this guarantees that
405
379
  // the default error/layout are 0/1
@@ -421,6 +395,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
421
395
 
422
396
  const node_analyser = create_node_analyser(cwd);
423
397
 
398
+ // add the related layout, page, and error nodes for a route
424
399
  for (const route of routes) {
425
400
  if (!route.leaf) continue;
426
401
 
@@ -465,6 +440,22 @@ function create_routes_and_nodes(cwd, config, fallback) {
465
440
  }
466
441
  }
467
442
 
443
+ // add parents to error nodes so that we can compute which page options apply to them
444
+ for (const route of routes) {
445
+ if (!route.error) continue;
446
+
447
+ /** @type {import('types').RouteData | null} */
448
+ let current_route = route;
449
+ while (current_route) {
450
+ if (current_route.layout) {
451
+ route.error.parent = current_route.layout;
452
+ break;
453
+ }
454
+ current_route = current_route.parent;
455
+ }
456
+ }
457
+
458
+ // compute the final page options for each page node
468
459
  for (const node of nodes) {
469
460
  node.page_options = node_analyser.get_page_options(node);
470
461
  }
@@ -473,6 +464,17 @@ function create_routes_and_nodes(cwd, config, fallback) {
473
464
  if (route.endpoint) {
474
465
  route.endpoint.page_options = get_page_options(route.endpoint.file, cwd);
475
466
  }
467
+
468
+ if (route.page && route.endpoint) {
469
+ const page = nodes[route.page.leaf];
470
+ if (page.page_options?.prerender || route.endpoint.page_options?.prerender) {
471
+ const endpoint_file = route.endpoint.file.split('/').pop();
472
+
473
+ throw new Error(
474
+ `Cannot prerender a route (${route.id}) with both a \`+page.svelte\` and a \`${endpoint_file}\``
475
+ );
476
+ }
477
+ }
476
478
  }
477
479
 
478
480
  return {
@@ -482,6 +484,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
482
484
  }
483
485
 
484
486
  /**
487
+ * Determine if and how the file is relevant to the routing system.
485
488
  * @param {string} project_relative
486
489
  * @param {string} file
487
490
  * @param {string[]} component_extensions
@@ -2,7 +2,6 @@ import path from 'node:path';
2
2
  import process from 'node:process';
3
3
  import create_manifest_data from './create_manifest_data/index.js';
4
4
  import { write_client_manifest } from './write_client_manifest.js';
5
- import { write_root } from './write_root.js';
6
5
  import { write_tsconfig } from './write_tsconfig.js';
7
6
  import { write_types, write_all_types } from './write_types/index.js';
8
7
  import { write_ambient } from './write_ambient.js';
@@ -37,7 +36,6 @@ export function create(config, root) {
37
36
 
38
37
  write_client_manifest(config.kit, manifest_data, `${output}/client`);
39
38
  write_server(config, output, root);
40
- write_root(manifest_data, config, output);
41
39
  write_all_types(config, manifest_data, root);
42
40
  write_non_ambient(config.kit, manifest_data);
43
41
 
@@ -176,26 +176,19 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
176
176
 
177
177
  export const decode = (type, value) => decoders[type](value);
178
178
 
179
- export { default as root } from '../root.js';
179
+ export const get_error_template = () => import('../shared/error-template.js').then(m => m.default);
180
180
  `
181
181
  );
182
182
 
183
183
  if (client_routing) {
184
- // write matchers to a separate module so that we don't
185
- // need to worry about name conflicts
186
- const imports = [];
187
- const matchers = [];
188
-
189
- for (const key in manifest_data.matchers) {
190
- const src = manifest_data.matchers[key];
191
-
192
- imports.push(`import { match as ${key} } from ${s(relative_path(output, src))};`);
193
- matchers.push(key);
194
- }
184
+ const uses_matchers = manifest_data.routes.some((route) =>
185
+ route.params.some((param) => param.matcher)
186
+ );
195
187
 
196
- const module = imports.length
197
- ? `${imports.join('\n')}\n\nexport const matchers = { ${matchers.join(', ')} };`
198
- : 'export const matchers = {};';
188
+ const module =
189
+ !manifest_data.params || !uses_matchers
190
+ ? 'export const matchers = {};'
191
+ : `import { params as matchers } from ${s(relative_path(output, manifest_data.params))};\n\nexport { matchers };`;
199
192
 
200
193
  write_if_changed(`${output}/matchers.js`, module);
201
194
  }
@@ -2,6 +2,7 @@
2
2
  import path from 'node:path';
3
3
  import { create_explicit_env_types } from '../env.js';
4
4
  import { write_if_changed } from './utils.js';
5
+ import { posixify } from '../../utils/os.js';
5
6
 
6
7
  const DOCS = '// See https://svelte.dev/docs/kit/environment-variables for more information';
7
8
 
@@ -18,7 +19,7 @@ export function write_env(kit, entry, env_config) {
18
19
  const out = path.join(kit.outDir, 'env.d.ts');
19
20
 
20
21
  if (entry && env_config) {
21
- const relative = path.relative(kit.outDir, entry);
22
+ const relative = posixify(path.relative(kit.outDir, entry));
22
23
  content.push(
23
24
  `// This file is generated from ${relative}.\n${DOCS}`,
24
25
  create_explicit_env_types(env_config, relative, 'private'),
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { GENERATED_COMMENT } from '../../constants.js';
3
+ import { resolve_entry } from '../../utils/filesystem.js';
3
4
  import { posixify } from '../../utils/os.js';
4
5
  import { write_if_changed } from './utils.js';
5
6
  import { s } from '../../utils/misc.js';
@@ -85,10 +86,6 @@ export {};
85
86
  * @param {import('types').ValidatedKitConfig} config
86
87
  */
87
88
  function generate_app_types(manifest_data, config) {
88
- /** @param {string} matcher */
89
- const path_to_matcher = (matcher) =>
90
- posixify(path.relative(config.outDir, path.join(config.files.params, matcher + '.js')));
91
-
92
89
  /** @type {Map<string, string>} */
93
90
  const matcher_types = new Map();
94
91
 
@@ -98,7 +95,15 @@ function generate_app_types(manifest_data, config) {
98
95
 
99
96
  let type = matcher_types.get(matcher);
100
97
  if (!type) {
101
- type = `MatcherParam<typeof import('${path_to_matcher(matcher)}').match>`;
98
+ const path_to_params = () => {
99
+ const params_file =
100
+ resolve_entry(config.files.params) ??
101
+ config.files.params.replace(/\.(js|ts)$/, '') + '.js';
102
+
103
+ return posixify(path.relative(config.outDir, params_file));
104
+ };
105
+
106
+ type = `import('@sveltejs/kit').MatcherParam<(typeof import('${path_to_params()}').params)[${JSON.stringify(matcher)}]>`;
102
107
  matcher_types.set(matcher, type);
103
108
  }
104
109
 
@@ -204,7 +209,7 @@ function generate_app_types(manifest_data, config) {
204
209
  if (route.params.length > 0) {
205
210
  const params = route.params.map((p) => {
206
211
  const type = get_matcher_type(p.matcher);
207
- return `${p.name}${p.optional ? '?:' : ':'} ${type}${p.optional ? ' | undefined' : ''}`;
212
+ return `${/^\w+$/.test(p.name) ? p.name : `'${p.name}'`}${p.optional ? '?:' : ':'} ${type}${p.optional ? ' | undefined' : ''}`;
208
213
  });
209
214
  const route_type = `${s(route.id)}: { ${params.join('; ')} }`;
210
215
 
@@ -225,7 +230,7 @@ function generate_app_types(manifest_data, config) {
225
230
  const params = Array.from(layout_params)
226
231
  .map(([name, { optional, matchers }]) => {
227
232
  const type = get_matchers_type(matchers);
228
- return `${name}${optional ? '?:' : ':'} ${type}${optional ? ' | undefined' : ''}`;
233
+ return `${/^\w+$/.test(name) ? name : `'${name}'`}${optional ? '?:' : ':'} ${type}${optional ? ' | undefined' : ''}`;
229
234
  })
230
235
  .join('; ');
231
236
 
@@ -239,8 +244,6 @@ function generate_app_types(manifest_data, config) {
239
244
 
240
245
  return [
241
246
  'declare module "$app/types" {',
242
- '\ttype MatcherParam<M> = M extends (param : string) => param is (infer U extends string) ? U : string;',
243
- '',
244
247
  '\texport interface AppTypes {',
245
248
  `\t\tRouteId(): ${manifest_data.routes.map((r) => s(r.id)).join(' | ')};`,
246
249
  `\t\tRouteParams(): {\n\t\t\t${dynamic_routes.join(';\n\t\t\t')}\n\t\t};`,