@sveltejs/kit 3.0.0-next.2 → 3.0.0-next.20

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 (192) hide show
  1. package/package.json +58 -36
  2. package/src/cli.js +14 -11
  3. package/src/constants.js +6 -1
  4. package/src/core/adapt/builder.js +54 -60
  5. package/src/core/adapt/index.js +2 -5
  6. package/src/core/config/index.js +141 -76
  7. package/src/core/config/options.js +260 -260
  8. package/src/core/config/types.d.ts +1 -1
  9. package/src/core/env.js +143 -13
  10. package/src/core/generate_manifest/index.js +18 -15
  11. package/src/core/postbuild/analyse.js +7 -19
  12. package/src/core/postbuild/crawl.js +22 -6
  13. package/src/core/postbuild/entities.js +8 -2
  14. package/src/core/postbuild/fallback.js +4 -2
  15. package/src/core/postbuild/prerender.js +193 -69
  16. package/src/core/postbuild/queue.js +2 -1
  17. package/src/core/sync/create_manifest_data/conflict.js +1 -1
  18. package/src/core/sync/create_manifest_data/index.js +72 -52
  19. package/src/core/sync/sync.js +35 -26
  20. package/src/core/sync/ts.js +1 -1
  21. package/src/core/sync/utils.js +22 -2
  22. package/src/core/sync/{write_non_ambient.js → write_app_types.js} +105 -56
  23. package/src/core/sync/write_client_manifest.js +13 -28
  24. package/src/core/sync/write_env.js +7 -5
  25. package/src/core/sync/write_server.js +25 -33
  26. package/src/core/sync/write_tsconfig/index.js +274 -0
  27. package/src/core/sync/write_tsconfig/test-app/package.json +7 -0
  28. package/src/core/sync/write_tsconfig/utils.js +77 -0
  29. package/src/core/sync/write_tsconfig/validate.js +128 -0
  30. package/src/core/sync/write_types/index.js +119 -116
  31. package/src/core/utils.js +30 -5
  32. package/src/exports/env/index.js +77 -0
  33. package/src/exports/env/public.d.ts +55 -0
  34. package/src/exports/hooks/index.js +3 -9
  35. package/src/exports/hooks/public.d.ts +194 -0
  36. package/src/exports/hooks/sequence.js +9 -6
  37. package/src/exports/index.js +53 -20
  38. package/src/exports/internal/client.js +5 -0
  39. package/src/exports/internal/env.js +8 -5
  40. package/src/exports/internal/index.js +1 -90
  41. package/src/exports/internal/{event.js → server/event.js} +1 -2
  42. package/src/exports/internal/server/index.js +37 -0
  43. package/src/exports/internal/server/telemetry.js +95 -0
  44. package/src/exports/internal/shared.js +90 -0
  45. package/src/exports/node/index.js +64 -22
  46. package/src/exports/params/index.js +70 -0
  47. package/src/exports/params/public.d.ts +63 -0
  48. package/src/exports/public.d.ts +219 -1118
  49. package/src/exports/url.js +86 -0
  50. package/src/exports/vite/build/build_server.js +53 -64
  51. package/src/exports/vite/build/remote.js +24 -19
  52. package/src/exports/vite/build/utils.js +0 -8
  53. package/src/exports/vite/dev/index.js +199 -125
  54. package/src/exports/vite/index.js +1514 -833
  55. package/src/exports/vite/module_ids.js +2 -2
  56. package/src/exports/vite/preview/index.js +32 -24
  57. package/src/exports/vite/utils.js +83 -45
  58. package/src/pathname.js +55 -0
  59. package/src/runner.js +15 -0
  60. package/src/runtime/app/env/internal.js +4 -4
  61. package/src/runtime/app/env/types.d.ts +1 -1
  62. package/src/runtime/app/environment/index.js +3 -3
  63. package/src/runtime/app/{forms.js → forms/index.js} +73 -42
  64. package/src/runtime/app/forms/public.d.ts +2 -0
  65. package/src/runtime/app/forms/types.d.ts +56 -0
  66. package/src/runtime/app/internal/transport.js +53 -0
  67. package/src/runtime/app/manifest/index.js +1 -0
  68. package/src/runtime/app/{navigation.js → navigation/index.js} +2 -1
  69. package/src/runtime/app/navigation/public.d.ts +237 -0
  70. package/src/runtime/app/paths/client.js +37 -37
  71. package/src/runtime/app/paths/index.js +1 -1
  72. package/src/runtime/app/paths/internal/client.js +34 -2
  73. package/src/runtime/app/paths/internal/server.js +6 -23
  74. package/src/runtime/app/paths/internal.d.ts +3 -0
  75. package/src/runtime/app/paths/public.d.ts +1 -29
  76. package/src/runtime/app/paths/server.js +36 -17
  77. package/src/runtime/app/paths/types.d.ts +11 -19
  78. package/src/runtime/app/server/index.js +2 -2
  79. package/src/runtime/app/server/public.d.ts +519 -0
  80. package/src/runtime/app/server/remote/command.js +13 -11
  81. package/src/runtime/app/server/remote/form.js +61 -39
  82. package/src/runtime/app/server/remote/prerender.js +45 -45
  83. package/src/runtime/app/server/remote/query.js +106 -108
  84. package/src/runtime/app/server/remote/requested.js +27 -19
  85. package/src/runtime/app/server/remote/shared.js +75 -75
  86. package/src/runtime/app/service-worker/index.js +24 -0
  87. package/src/runtime/app/state/client.js +4 -2
  88. package/src/runtime/app/state/index.js +7 -5
  89. package/src/runtime/app/state/public.d.ts +72 -0
  90. package/src/runtime/app/state/server.js +3 -0
  91. package/src/runtime/app/stores.js +15 -78
  92. package/src/runtime/client/bundle.js +1 -1
  93. package/src/runtime/client/client-entry.js +3 -0
  94. package/src/runtime/client/client.js +1336 -648
  95. package/src/runtime/client/constants.js +3 -6
  96. package/src/runtime/client/entry.js +24 -3
  97. package/src/runtime/client/fetcher.js +25 -25
  98. package/src/runtime/client/ndjson.js +1 -1
  99. package/src/runtime/client/parse.js +1 -1
  100. package/src/runtime/client/payload.js +17 -0
  101. package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
  102. package/src/runtime/client/remote-functions/command.svelte.js +37 -61
  103. package/src/runtime/client/remote-functions/form.svelte.js +267 -178
  104. package/src/runtime/client/remote-functions/prerender.svelte.js +32 -11
  105. package/src/runtime/client/remote-functions/query/index.js +10 -17
  106. package/src/runtime/client/remote-functions/query/instance.svelte.js +64 -18
  107. package/src/runtime/client/remote-functions/query/proxy.js +5 -5
  108. package/src/runtime/client/remote-functions/query-batch.svelte.js +61 -70
  109. package/src/runtime/client/remote-functions/query-live/index.js +1 -1
  110. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +63 -21
  111. package/src/runtime/client/remote-functions/query-live/iterator.js +15 -12
  112. package/src/runtime/client/remote-functions/query-live/proxy.js +2 -12
  113. package/src/runtime/client/remote-functions/shared.svelte.js +90 -65
  114. package/src/runtime/client/snapshots.js +147 -0
  115. package/src/runtime/client/state.svelte.js +94 -55
  116. package/src/runtime/client/stream.js +3 -2
  117. package/src/runtime/client/types.d.ts +13 -9
  118. package/src/runtime/client/utils.js +22 -110
  119. package/src/runtime/components/root.svelte +56 -0
  120. package/src/runtime/env/dynamic/private.js +7 -0
  121. package/src/runtime/env/dynamic/public.js +7 -0
  122. package/src/runtime/env/static/private.js +6 -0
  123. package/src/runtime/env/static/public.js +6 -0
  124. package/src/runtime/form-utils.js +172 -61
  125. package/src/runtime/pathname.js +20 -32
  126. package/src/runtime/props.svelte.js +72 -0
  127. package/src/runtime/server/constants.js +0 -3
  128. package/src/runtime/server/cookie.js +70 -53
  129. package/src/runtime/server/csrf.js +65 -0
  130. package/src/runtime/server/data/index.js +48 -52
  131. package/src/runtime/server/dev.js +22 -0
  132. package/src/runtime/server/endpoint.js +3 -4
  133. package/src/runtime/server/env_module.js +0 -5
  134. package/src/runtime/server/errors.js +160 -0
  135. package/src/runtime/server/fetch.js +31 -38
  136. package/src/runtime/server/index.js +90 -64
  137. package/src/runtime/server/internal.js +71 -0
  138. package/src/runtime/server/page/actions.js +84 -64
  139. package/src/runtime/server/page/crypto.js +2 -2
  140. package/src/runtime/server/page/csp.js +88 -104
  141. package/src/runtime/server/page/data_serializer.js +22 -23
  142. package/src/runtime/server/page/index.js +35 -53
  143. package/src/runtime/server/page/load_data.js +47 -54
  144. package/src/runtime/server/page/render.js +172 -238
  145. package/src/runtime/server/page/respond_with_error.js +16 -30
  146. package/src/runtime/server/page/serialize_data.js +2 -13
  147. package/src/runtime/server/page/server_routing.js +85 -26
  148. package/src/runtime/server/remote-functions.js +639 -0
  149. package/src/runtime/server/respond.js +188 -129
  150. package/src/runtime/server/sourcemaps.js +183 -0
  151. package/src/runtime/server/state.js +53 -0
  152. package/src/runtime/server/utils.js +12 -154
  153. package/src/runtime/shared.js +20 -40
  154. package/src/runtime/utils.js +3 -0
  155. package/src/types/ambient-private.d.ts +11 -1
  156. package/src/types/ambient.d.ts +87 -36
  157. package/src/types/global-private.d.ts +25 -25
  158. package/src/types/internal.d.ts +160 -150
  159. package/src/types/private.d.ts +41 -1
  160. package/src/utils/error.js +28 -4
  161. package/src/utils/escape.js +9 -25
  162. package/src/utils/features.js +1 -1
  163. package/src/utils/filesystem.js +1 -23
  164. package/src/utils/fork.js +7 -2
  165. package/src/utils/hash.js +21 -0
  166. package/src/utils/http.js +8 -7
  167. package/src/utils/import.js +9 -2
  168. package/src/utils/imports.js +83 -0
  169. package/src/utils/mime.js +9 -0
  170. package/src/utils/page_nodes.js +6 -7
  171. package/src/utils/params.js +67 -0
  172. package/src/utils/regex.js +9 -0
  173. package/src/utils/routing.js +145 -73
  174. package/src/utils/shared-iterator.js +5 -0
  175. package/src/utils/streaming.js +14 -4
  176. package/src/utils/url.js +33 -2
  177. package/src/version.js +1 -1
  178. package/types/index.d.ts +2291 -2135
  179. package/types/index.d.ts.map +116 -108
  180. package/src/core/sync/write_ambient.js +0 -18
  181. package/src/core/sync/write_root.js +0 -148
  182. package/src/core/sync/write_tsconfig.js +0 -250
  183. package/src/exports/internal/server.js +0 -22
  184. package/src/exports/node/polyfills.js +0 -30
  185. package/src/runtime/server/app.js +0 -9
  186. package/src/runtime/server/remote.js +0 -457
  187. package/src/runtime/shared-server.js +0 -7
  188. package/src/runtime/telemetry/otel.js +0 -21
  189. package/src/runtime/telemetry/record_span.js +0 -65
  190. package/src/types/synthetic/$lib.md +0 -5
  191. /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
  192. /package/src/{runtime/telemetry/noop.js → telemetry.js} +0 -0
@@ -1,8 +1,9 @@
1
- /** @import { Adapter } from '@sveltejs/kit' */
2
- import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
1
+ import process from 'node:process';
2
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
3
3
  import { dirname, join } from 'node:path';
4
+ import { clearLine, moveCursor } from 'node:readline';
4
5
  import { pathToFileURL } from 'node:url';
5
- import { mkdirp, walk } from '../../utils/filesystem.js';
6
+ import { walk } from '../../utils/filesystem.js';
6
7
  import { posixify } from '../../utils/os.js';
7
8
  import { noop } from '../../utils/functions.js';
8
9
  import { decode_uri, is_root_relative, resolve } from '../../utils/url.js';
@@ -17,6 +18,7 @@ import * as devalue from 'devalue';
17
18
  import { createReadableStream } from '@sveltejs/kit/node';
18
19
  import generate_fallback from './fallback.js';
19
20
  import { stringify_remote_arg } from '../../runtime/shared.js';
21
+ import { matches_content_type } from '../../utils/http.js';
20
22
 
21
23
  export default forked(import.meta.url, prerender);
22
24
 
@@ -35,46 +37,94 @@ const SPECIAL_HASHLINKS = new Set(['', 'top']);
35
37
  * verbose: boolean;
36
38
  * env: Record<string, string>;
37
39
  * vite_config_file: string | undefined;
40
+ * is_tty: boolean | undefined;
38
41
  * }} opts
39
42
  */
40
- async function prerender({ hash, out, manifest_path, metadata, verbose, env, vite_config_file }) {
43
+ async function prerender({
44
+ hash,
45
+ out,
46
+ manifest_path,
47
+ metadata,
48
+ verbose,
49
+ env,
50
+ vite_config_file,
51
+ is_tty
52
+ }) {
41
53
  /** @type {import('@sveltejs/kit').SSRManifest} */
42
54
  const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
43
55
 
44
56
  /** @type {import('types').ServerInternalModule} */
45
- const internal = await import(pathToFileURL(`${out}/server/internal.js`).href);
57
+ const { set_building, set_prerendering, set_manifest, set_read_implementation, log_response } =
58
+ await import(pathToFileURL(`${out}/server/internal.js`).href);
59
+
60
+ // configure `import { building } from `$app/env` —
61
+ // essential we do this before analysing the code
62
+ set_building();
63
+ set_prerendering();
64
+
65
+ // `set_env` and `Server` live in modules that import the user's `src/env` config. We import them
66
+ // *after* `set_building()` so that `building`-dependent expressions resolve correctly
67
+ /** @type {typeof import('__sveltekit/env')} */
68
+ const { set_env } = await import(pathToFileURL(`${out}/server/env.js`).href);
69
+ set_env(env);
46
70
 
47
71
  /** @type {import('types').ServerModule} */
48
72
  const { Server } = await import(pathToFileURL(`${out}/server/index.js`).href);
49
73
 
50
- // configure `import { building } from `$app/env` —
51
- // essential we do this before analysing the code
52
- internal.set_building();
53
- internal.set_prerendering();
74
+ const throw_handled = () => {
75
+ throw new Error('__handled__');
76
+ };
54
77
 
55
78
  /**
56
79
  * @template {{message: string}} T
57
80
  * @template {Omit<T, 'message'>} K
58
- * @param {import('types').Logger} log
59
- * @param {'fail' | 'warn' | 'ignore' | ((details: T) => void)} input
81
+ * @param {string} name
82
+ * @param {'fail' | 'warn' | 'ignore' | undefined | ((details: T) => void)} input
60
83
  * @param {(details: K) => string} format
61
- * @returns {(details: K) => void}
84
+ * @returns {(details: K & { error?: unknown }) => void}
62
85
  */
63
- function normalise_error_handler(log, input, format) {
86
+ function normalise_error_handler(name, input, format) {
87
+ /**
88
+ * @param {any} details
89
+ */
90
+ function log_failure(details) {
91
+ const message = format(details);
92
+ log.error(`\n${message}\n`);
93
+ }
94
+
64
95
  switch (input) {
65
96
  case 'fail':
66
97
  return (details) => {
67
- throw new Error(format(details));
98
+ log_failure(details);
99
+ throw_handled();
68
100
  };
69
101
  case 'warn':
70
- return (details) => {
71
- log.error(format(details));
72
- };
102
+ return log_failure;
73
103
  case 'ignore':
74
104
  return noop;
105
+ case undefined: {
106
+ return (details) => {
107
+ log_failure(details);
108
+
109
+ log.err(
110
+ `To suppress or handle this error, implement \`${name}\` in https://svelte.dev/docs/kit/configuration#prerender\n`
111
+ );
112
+
113
+ throw_handled();
114
+ };
115
+ }
75
116
  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) });
117
+ return (details) => {
118
+ const message = format(details);
119
+
120
+ try {
121
+ // @ts-expect-error TS thinks T might be of a different kind, but it's not
122
+ input({ ...details, message });
123
+ } catch (error) {
124
+ log.prettyError(error, import.meta.dirname);
125
+ throw_handled();
126
+ }
127
+ };
78
128
  }
79
129
  }
80
130
 
@@ -105,19 +155,21 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
105
155
 
106
156
  const config = extract_svelte_config(vite_config).kit;
107
157
 
158
+ const prerender_origin = config.paths.origin || 'http://sveltekit-prerender';
159
+
108
160
  if (hash) {
109
161
  const fallback = await generate_fallback({
110
162
  manifest_path,
111
163
  env,
112
164
  out_dir: config.outDir,
113
- origin: config.prerender.origin,
165
+ origin: prerender_origin,
114
166
  assets: config.files.assets
115
167
  });
116
168
 
117
169
  const file = output_filename('/', true);
118
170
  const dest = `${config.outDir}/output/prerendered/pages/${file}`;
119
171
 
120
- mkdirp(dirname(dest));
172
+ mkdirSync(dirname(dest), { recursive: true });
121
173
  writeFileSync(dest, fallback);
122
174
 
123
175
  prerendered.pages.set('/', { file });
@@ -125,13 +177,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
125
177
  return { prerendered, prerender_map };
126
178
  }
127
179
 
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?.();
180
+ const emulator = await config.adapter?.emulate?.();
135
181
 
136
182
  /** @type {import('types').Logger} */
137
183
  const log = logger({ verbose });
@@ -140,43 +186,58 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
140
186
  const saved = new Map();
141
187
 
142
188
  const handle_http_error = normalise_error_handler(
143
- log,
189
+ 'handleHttpError',
144
190
  config.prerender.handleHttpError,
145
191
  ({ 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;
192
+ let message = `Failed to prerender ${path}`;
150
193
 
151
- return `${status} ${message}${referrer ? ` (${referenceType} from ${referrer})` : ''}`;
194
+ if (status === 404) {
195
+ if (!path.startsWith(config.paths.base)) {
196
+ message = referrer ? `${path} (${referenceType} from ${referrer})` : path;
197
+
198
+ 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\``;
199
+ } else if (referrer) {
200
+ message = `${path} was ${referenceType} from ${referrer}`;
201
+ }
202
+ }
203
+
204
+ return message;
152
205
  }
153
206
  );
154
207
 
155
208
  const handle_missing_id = normalise_error_handler(
156
- log,
209
+ 'handleMissingId',
157
210
  config.prerender.handleMissingId,
158
211
  ({ path, id, referrers }) => {
159
212
  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:` +
213
+ `The following pages contain links to ${path}#${id}, but no element with id="${id}" exists on ${path}:` +
161
214
  referrers.map((l) => `\n - ${l}`).join('')
162
215
  );
163
216
  }
164
217
  );
165
218
 
166
219
  const handle_entry_generator_mismatch = normalise_error_handler(
167
- log,
220
+ 'handleEntryGeneratorMismatch',
168
221
  config.prerender.handleEntryGeneratorMismatch,
169
222
  ({ 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.`;
223
+ return `The entries export from ${generatedFromId} generated entry ${entry}, which was matched by ${matchedId === entry ? 'a static route' : matchedId}`;
171
224
  }
172
225
  );
173
226
 
174
227
  const handle_not_prerendered_route = normalise_error_handler(
175
- log,
228
+ 'handleUnseenRoutes',
176
229
  config.prerender.handleUnseenRoutes,
177
230
  ({ routes }) => {
178
231
  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.`;
232
+ return `The following routes were marked as prerenderable, but were not prerendered because they were not found while crawling your app:\n${list}`;
233
+ }
234
+ );
235
+
236
+ const handle_invalid_url = normalise_error_handler(
237
+ 'handleInvalidUrl',
238
+ config.prerender.handleInvalidUrl,
239
+ ({ href, referrer }) => {
240
+ return `Invalid URL ${href}${referrer ? ` (linked from ${referrer})` : ''}`;
180
241
  }
181
242
  );
182
243
 
@@ -210,12 +271,56 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
210
271
 
211
272
  const seen = new Set();
212
273
  const written = new Set();
274
+
275
+ /** @type {Map<string, Promise<any>>} */
213
276
  const remote_responses = new Map();
214
277
 
278
+ /** @type {(path: string) => void} */
279
+ let progress_line = noop;
280
+
281
+ if (is_tty) {
282
+ // Where possible, provide progress feedback by showing the path we're
283
+ // currently requesting, then clearing the line once the response comes in.
284
+ // This avoids the wall of text that happens when you prerender
285
+ // many pages and log each response
286
+ let current = false;
287
+ const stdout_write = process.stdout.write;
288
+ const stderr_write = process.stderr.write;
289
+
290
+ process.stdout.write = new Proxy(stdout_write, {
291
+ apply(target, this_arg, args) {
292
+ current = false;
293
+ return Reflect.apply(target, this_arg, args);
294
+ }
295
+ });
296
+
297
+ process.stderr.write = new Proxy(stderr_write, {
298
+ apply(target, this_arg, args) {
299
+ current = false;
300
+ return Reflect.apply(target, this_arg, args);
301
+ }
302
+ });
303
+
304
+ progress_line = (path) => {
305
+ // If app code writes to stdout or stderr, don't move the cursor to clear
306
+ // the previous progress log, because that will corrupt things
307
+ if (current) {
308
+ moveCursor(process.stdout, 0, -1);
309
+ clearLine(process.stdout, 0);
310
+ }
311
+
312
+ stdout_write.call(process.stdout, `rendering ${path}...\n`);
313
+ current = true;
314
+ };
315
+ }
316
+
317
+ /** @type {Set<string>} */
318
+ const resolved_route_ids = new Set();
319
+
215
320
  /** @type {Map<string, Set<string>>} */
216
321
  const expected_hashlinks = new Map();
217
322
 
218
- /** @type {Map<string, string[]>} */
323
+ /** @type {Map<string, Set<string>>} */
219
324
  const actual_hashlinks = new Map();
220
325
 
221
326
  /**
@@ -249,13 +354,18 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
249
354
  /** @type {Map<string, import('types').PrerenderDependency>} */
250
355
  const dependencies = new Map();
251
356
 
252
- const response = await server.respond(new Request(config.prerender.origin + encoded), {
357
+ progress_line(decoded);
358
+
359
+ const request = new Request(prerender_origin + encoded);
360
+
361
+ const response = await server.respond(request, {
253
362
  getClientAddress() {
254
363
  throw new Error('Cannot read clientAddress during prerendering');
255
364
  },
256
365
  prerendering: {
257
366
  dependencies,
258
- remote_responses
367
+ remote_responses,
368
+ resolved_route_ids
259
369
  },
260
370
  read: (file) => {
261
371
  // stuff we just wrote
@@ -287,6 +397,10 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
287
397
  });
288
398
  }
289
399
 
400
+ if (response.status >= 400) {
401
+ log_response(response.status, request);
402
+ }
403
+
290
404
  const body = Buffer.from(await response.arrayBuffer());
291
405
 
292
406
  const category = decoded.startsWith(remote_prefix) ? 'data' : 'pages';
@@ -331,17 +445,25 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
331
445
  const headers = Object.fromEntries(response.headers);
332
446
 
333
447
  // if it's a 200 HTML response, crawl it. Skip error responses, as we don't save those
334
- if (response.ok && config.prerender.crawl && headers['content-type'] === 'text/html') {
335
- const { ids, hrefs } = crawl(body.toString(), decoded);
448
+ if (
449
+ response.ok &&
450
+ config.prerender.crawl &&
451
+ matches_content_type(headers['content-type'], 'text/html')
452
+ ) {
453
+ const { ids, hrefs, invalid } = crawl(body.toString(), decoded);
454
+
455
+ for (const href of invalid) {
456
+ handle_invalid_url({ href, referrer: decoded });
457
+ }
336
458
 
337
- actual_hashlinks.set(decoded, ids);
459
+ actual_hashlinks.set(decoded, new Set(ids));
338
460
 
339
461
  /** @param {string} href */
340
462
  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);
463
+ if (href.startsWith(prerender_origin)) {
464
+ if (href === prerender_origin) return '/';
465
+ if (href.at(prerender_origin.length) !== '/') return href;
466
+ return href.slice(prerender_origin.length);
345
467
  }
346
468
  return href;
347
469
  };
@@ -384,7 +506,13 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
384
506
  const headers = Object.fromEntries(response.headers);
385
507
 
386
508
  const type = headers['content-type'];
387
- const is_html = response_type === REDIRECT || type === 'text/html';
509
+ const is_html = response_type === REDIRECT || matches_content_type(type, 'text/html');
510
+
511
+ if (!is_html && response.status === 200 && decoded.slice(config.paths.base.length + 1) === '') {
512
+ throw new Error(
513
+ `Cannot prerender a root +server.js that returns a non-HTML response - static hosts always serve an HTML file for \`${config.paths.base || '/'}\``
514
+ );
515
+ }
388
516
 
389
517
  const file = output_filename(decoded, is_html);
390
518
  const dest = `${config.outDir}/output/prerendered/${category}/${file}`;
@@ -405,9 +533,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
405
533
  }
406
534
 
407
535
  if (!headers['x-sveltekit-normalize']) {
408
- mkdirp(dirname(dest));
409
-
410
- log.warn(`${response.status} ${decoded} -> ${location}`);
536
+ mkdirSync(dirname(dest), { recursive: true });
411
537
 
412
538
  writeFileSync(
413
539
  dest,
@@ -453,9 +579,8 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
453
579
  );
454
580
  }
455
581
 
456
- mkdirp(dir);
582
+ mkdirSync(dir, { recursive: true });
457
583
 
458
- log.info(`${response.status} ${decoded}`);
459
584
  writeFileSync(dest, body);
460
585
  written.add(file);
461
586
 
@@ -471,7 +596,12 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
471
596
 
472
597
  prerendered.paths.push(decoded);
473
598
  } else if (response_type !== OK) {
474
- handle_http_error({ status: response.status, path: decoded, referrer, referenceType });
599
+ handle_http_error({
600
+ status: response.status,
601
+ path: decoded,
602
+ referrer,
603
+ referenceType
604
+ });
475
605
  }
476
606
 
477
607
  manifest.assets.add(file);
@@ -495,12 +625,10 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
495
625
  }
496
626
  }
497
627
 
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);
502
- internal.set_manifest(manifest);
503
- internal.set_read_implementation((file) => createReadableStream(`${out}/server/${file}`));
628
+ // the user's remote function modules may reference `read` or the `manifest` at the top-level
629
+ // so we need to set them before evaluating those modules to avoid potential runtime errors
630
+ set_manifest(manifest);
631
+ set_read_implementation((file) => createReadableStream(`${out}/server/${file}`));
504
632
 
505
633
  /** @type {Array<import('types').RemotePrerenderInternals>} */
506
634
  const prerender_functions = [];
@@ -554,14 +682,10 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
554
682
  }
555
683
  }
556
684
 
557
- const transport = (await internal.get_hooks()).transport ?? {};
558
685
  for (const internals of prerender_functions) {
559
686
  if (internals.has_arg) {
560
687
  for (const arg of (await internals.inputs?.()) ?? []) {
561
- void enqueue(
562
- null,
563
- remote_prefix + internals.id + '/' + stringify_remote_arg(arg, transport)
564
- );
688
+ void enqueue(null, remote_prefix + internals.id + '/' + stringify_remote_arg(arg));
565
689
  }
566
690
  } else {
567
691
  void enqueue(null, remote_prefix + internals.id);
@@ -580,7 +704,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
580
704
  // ignore fragment links to pages that were not prerendered
581
705
  if (!hashlinks) continue;
582
706
 
583
- if (!hashlinks.includes(id) && !SPECIAL_HASHLINKS.has(id)) {
707
+ if (!hashlinks.has(id) && !SPECIAL_HASHLINKS.has(id)) {
584
708
  handle_missing_id({ id, path, referrers: Array.from(referrers) });
585
709
  }
586
710
  }
@@ -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;
@@ -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 `<?*>`
@@ -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,46 @@ 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
- // validate matcher names used in parameterised routes
36
- for (const route of routes) {
37
- for (const param of route.params) {
38
- if (param.matcher && !matchers[param.matcher]) {
39
- throw new Error(`No matcher found for parameter '${param.matcher}' in route ${route.id}`);
40
- }
41
- }
42
- }
43
-
44
35
  return {
45
36
  assets,
46
37
  hooks,
47
- matchers,
38
+ params,
48
39
  nodes,
49
40
  routes
50
41
  };
51
42
  }
52
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
+
53
72
  /**
54
73
  * Returns a list of files in the `static` directory.
55
74
  * @param {import('types').ValidatedConfig} config
@@ -82,38 +101,9 @@ function create_hooks(config, cwd) {
82
101
  * @param {import('types').ValidatedConfig} config
83
102
  * @param {string} cwd
84
103
  */
85
- function create_matchers(config, cwd) {
86
- const params_base = path.relative(cwd, config.kit.files.params);
87
-
88
- /** @type {Record<string, string>} */
89
- const matchers = {};
90
- if (fs.existsSync(config.kit.files.params)) {
91
- for (const file of fs.readdirSync(config.kit.files.params)) {
92
- const ext = path.extname(file);
93
- if (!config.kit.moduleExtensions.includes(ext)) continue;
94
- const type = file.slice(0, -ext.length);
95
-
96
- if (/^\w+$/.test(type)) {
97
- const matcher_file = path.join(params_base, file);
98
-
99
- // Disallow same matcher with different extensions
100
- if (matchers[type]) {
101
- throw new Error(`Duplicate matchers: ${matcher_file} and ${matchers[type]}`);
102
- } else {
103
- matchers[type] = matcher_file;
104
- }
105
- } else {
106
- // Allow for matcher test collocation
107
- if (type.endsWith('.test') || type.endsWith('.spec')) continue;
108
-
109
- throw new Error(
110
- `Matcher names can only have underscores and alphanumeric characters — "${file}" is invalid`
111
- );
112
- }
113
- }
114
- }
115
-
116
- return matchers;
104
+ function resolve_params(config, cwd) {
105
+ const params_file = resolve_entry(config.kit.files.params);
106
+ return params_file ? posixify(path.relative(cwd, params_file)) : null;
117
107
  }
118
108
 
119
109
  /**
@@ -123,7 +113,7 @@ function create_matchers(config, cwd) {
123
113
  */
124
114
  function create_routes_and_nodes(cwd, config, fallback) {
125
115
  /** @type {import('types').RouteData[]} */
126
- const routes = [];
116
+ let routes = [];
127
117
 
128
118
  const routes_base = posixify(path.relative(cwd, config.kit.files.routes));
129
119
 
@@ -163,7 +153,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
163
153
  );
164
154
  }
165
155
 
166
- return String.fromCharCode(parseInt(code, 16));
156
+ return String.fromCodePoint(parseInt(code, 16));
167
157
  }
168
158
  });
169
159
 
@@ -180,7 +170,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
180
170
  throw new Error(`Route ${id} should be renamed to ${id.replace(/#/g, '[x+23]')}`);
181
171
  }
182
172
 
183
- if (/\[\.\.\.\w+\]\/\[\[/.test(id)) {
173
+ if (/\[\.\.\.[\w-]+\]\/\[\[/.test(id)) {
184
174
  throw new Error(
185
175
  `Invalid route ${id} — an [[optional]] route segment cannot follow a [...rest] route segment`
186
176
  );
@@ -221,10 +211,17 @@ function create_routes_and_nodes(cwd, config, fallback) {
221
211
 
222
212
  // We can't use withFileTypes because of a NodeJs bug which returns wrong results
223
213
  // with isDirectory() in case of symlinks: https://github.com/nodejs/node/issues/30646
224
- const files = fs.readdirSync(dir).map((name) => ({
225
- is_dir: fs.statSync(path.join(dir, name)).isDirectory(),
226
- name
227
- }));
214
+ // We sort the entries because `readdirSync` order is not guaranteed and differs
215
+ // between runtimes (e.g. Node returns entries alphabetically, Bun in directory
216
+ // order). Node indices are assigned from this traversal order, so without sorting
217
+ // the SSR and client manifests can disagree, causing hydration mismatches.
218
+ const files = fs
219
+ .readdirSync(dir)
220
+ .sort()
221
+ .map((name) => ({
222
+ is_dir: fs.statSync(path.join(dir, name)).isDirectory(),
223
+ name
224
+ }));
228
225
 
229
226
  // process files first
230
227
  for (const file of files) {
@@ -254,6 +251,15 @@ function create_routes_and_nodes(cwd, config, fallback) {
254
251
  continue;
255
252
  }
256
253
 
254
+ // allow e.g. `+page.stories.svelte` or `+server.test.ts`
255
+ if (
256
+ file.name.includes('.test.') ||
257
+ file.name.includes('.spec.') ||
258
+ file.name.includes('.stories.')
259
+ ) {
260
+ continue;
261
+ }
262
+
257
263
  if (file.name.endsWith('.d.ts')) {
258
264
  let name = file.name.slice(0, -5);
259
265
  const ext = valid_extensions.find((ext) => name.endsWith(ext));
@@ -368,7 +374,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
368
374
  const root = routes[0];
369
375
  if (!root.leaf && !root.error && !root.layout && !root.endpoint) {
370
376
  throw new Error(
371
- 'No routes found. If you are using a custom src/routes directory, make sure it is specified in your Svelte config file'
377
+ 'No routes found. If you are using a custom src/routes directory, make sure it is specified in your SvelteKit Vite plugin options'
372
378
  );
373
379
  }
374
380
  }
@@ -471,6 +477,9 @@ function create_routes_and_nodes(cwd, config, fallback) {
471
477
  }
472
478
  }
473
479
 
480
+ // remove route objects with no route file
481
+ routes = routes.filter((route) => route.endpoint || route.leaf || route.layout || route.error);
482
+
474
483
  // add parents to error nodes so that we can compute which page options apply to them
475
484
  for (const route of routes) {
476
485
  if (!route.error) continue;
@@ -495,6 +504,17 @@ function create_routes_and_nodes(cwd, config, fallback) {
495
504
  if (route.endpoint) {
496
505
  route.endpoint.page_options = get_page_options(route.endpoint.file, cwd);
497
506
  }
507
+
508
+ if (route.page && route.endpoint) {
509
+ const page = nodes[route.page.leaf];
510
+ if (page.page_options?.prerender || route.endpoint.page_options?.prerender) {
511
+ const endpoint_file = route.endpoint.file.split('/').pop();
512
+
513
+ throw new Error(
514
+ `Cannot prerender a route (${route.id}) with both a \`+page.svelte\` and a \`${endpoint_file}\``
515
+ );
516
+ }
517
+ }
498
518
  }
499
519
 
500
520
  return {