@sveltejs/kit 3.0.0-next.0 → 3.0.0-next.10

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 (127) hide show
  1. package/package.json +33 -21
  2. package/src/core/adapt/builder.js +29 -43
  3. package/src/core/adapt/index.js +1 -4
  4. package/src/core/config/index.js +132 -71
  5. package/src/core/config/options.js +294 -244
  6. package/src/core/config/types.d.ts +1 -1
  7. package/src/core/env.js +121 -5
  8. package/src/core/generate_manifest/index.js +12 -15
  9. package/src/core/postbuild/analyse.js +7 -19
  10. package/src/core/postbuild/crawl.js +22 -6
  11. package/src/core/postbuild/prerender.js +42 -23
  12. package/src/core/sync/create_manifest_data/index.js +53 -50
  13. package/src/core/sync/sync.js +0 -2
  14. package/src/core/sync/write_client_manifest.js +8 -15
  15. package/src/core/sync/write_env.js +2 -1
  16. package/src/core/sync/write_non_ambient.js +12 -9
  17. package/src/core/sync/write_server.js +13 -14
  18. package/src/core/sync/write_tsconfig.js +16 -8
  19. package/src/core/sync/write_tsconfig_test/package.json +7 -0
  20. package/src/core/sync/write_types/index.js +28 -24
  21. package/src/core/utils.js +2 -2
  22. package/src/exports/env/index.js +12 -0
  23. package/src/exports/hooks/index.js +3 -9
  24. package/src/exports/hooks/sequence.js +3 -2
  25. package/src/exports/index.js +35 -13
  26. package/src/exports/internal/client.js +5 -0
  27. package/src/exports/internal/env.js +2 -2
  28. package/src/exports/internal/index.js +1 -90
  29. package/src/exports/internal/{event.js → server/event.js} +1 -2
  30. package/src/exports/internal/server/index.js +33 -0
  31. package/src/exports/internal/shared.js +89 -0
  32. package/src/exports/node/index.js +62 -15
  33. package/src/exports/params.js +63 -0
  34. package/src/exports/public.d.ts +323 -170
  35. package/src/exports/url.js +86 -0
  36. package/src/exports/vite/build/build_server.js +53 -59
  37. package/src/exports/vite/build/remote.js +18 -11
  38. package/src/exports/vite/build/utils.js +0 -8
  39. package/src/exports/vite/dev/index.js +42 -44
  40. package/src/exports/vite/index.js +810 -593
  41. package/src/exports/vite/preview/index.js +29 -22
  42. package/src/exports/vite/static_analysis/index.js +2 -4
  43. package/src/exports/vite/static_analysis/types.d.ts +14 -0
  44. package/src/exports/vite/utils.js +21 -36
  45. package/src/runtime/app/env/internal.js +4 -4
  46. package/src/runtime/app/env/types.d.ts +1 -1
  47. package/src/runtime/app/environment/index.js +6 -0
  48. package/src/runtime/app/forms.js +24 -7
  49. package/src/runtime/app/paths/client.js +4 -10
  50. package/src/runtime/app/paths/internal/client.js +4 -2
  51. package/src/runtime/app/paths/internal/server.js +2 -23
  52. package/src/runtime/app/paths/public.d.ts +0 -28
  53. package/src/runtime/app/paths/server.js +9 -5
  54. package/src/runtime/app/server/remote/command.js +0 -3
  55. package/src/runtime/app/server/remote/form.js +27 -15
  56. package/src/runtime/app/server/remote/prerender.js +29 -36
  57. package/src/runtime/app/server/remote/query.js +101 -99
  58. package/src/runtime/app/server/remote/requested.js +22 -14
  59. package/src/runtime/app/server/remote/shared.js +30 -26
  60. package/src/runtime/app/state/client.js +1 -2
  61. package/src/runtime/app/stores.js +13 -76
  62. package/src/runtime/client/bundle.js +1 -1
  63. package/src/runtime/client/client-entry.js +3 -0
  64. package/src/runtime/client/client.js +411 -318
  65. package/src/runtime/client/entry.js +24 -3
  66. package/src/runtime/client/fetcher.js +3 -2
  67. package/src/runtime/client/ndjson.js +6 -33
  68. package/src/runtime/client/payload.js +17 -0
  69. package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
  70. package/src/runtime/client/remote-functions/command.svelte.js +7 -32
  71. package/src/runtime/client/remote-functions/form.svelte.js +183 -112
  72. package/src/runtime/client/remote-functions/prerender.svelte.js +29 -8
  73. package/src/runtime/client/remote-functions/query/index.js +7 -14
  74. package/src/runtime/client/remote-functions/query/instance.svelte.js +63 -18
  75. package/src/runtime/client/remote-functions/query/proxy.js +3 -3
  76. package/src/runtime/client/remote-functions/query-batch.svelte.js +60 -68
  77. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +60 -18
  78. package/src/runtime/client/remote-functions/query-live/iterator.js +36 -55
  79. package/src/runtime/client/remote-functions/shared.svelte.js +88 -62
  80. package/src/runtime/client/sse.js +32 -0
  81. package/src/runtime/client/state.svelte.js +65 -49
  82. package/src/runtime/client/stream.js +38 -0
  83. package/src/runtime/client/types.d.ts +11 -7
  84. package/src/runtime/client/utils.js +0 -96
  85. package/src/runtime/components/root.svelte +66 -0
  86. package/src/runtime/env/dynamic/private.js +7 -0
  87. package/src/runtime/env/dynamic/public.js +7 -0
  88. package/src/runtime/env/static/private.js +6 -0
  89. package/src/runtime/env/static/public.js +6 -0
  90. package/src/runtime/form-utils.js +100 -22
  91. package/src/runtime/server/cookie.js +69 -52
  92. package/src/runtime/server/csrf.js +65 -0
  93. package/src/runtime/server/data/index.js +14 -18
  94. package/src/runtime/server/env_module.js +0 -5
  95. package/src/runtime/server/index.js +2 -2
  96. package/src/runtime/server/page/actions.js +41 -26
  97. package/src/runtime/server/page/index.js +9 -15
  98. package/src/runtime/server/page/load_data.js +1 -1
  99. package/src/runtime/server/page/render.js +112 -187
  100. package/src/runtime/server/page/respond_with_error.js +7 -8
  101. package/src/runtime/server/page/server_routing.js +27 -18
  102. package/src/runtime/server/remote.js +355 -216
  103. package/src/runtime/server/respond.js +97 -61
  104. package/src/runtime/server/utils.js +32 -9
  105. package/src/runtime/shared.js +83 -13
  106. package/src/runtime/telemetry/otel.js +1 -1
  107. package/src/runtime/types.d.ts +8 -0
  108. package/src/types/ambient.d.ts +10 -5
  109. package/src/types/global-private.d.ts +15 -22
  110. package/src/types/internal.d.ts +89 -74
  111. package/src/types/private.d.ts +33 -1
  112. package/src/utils/error.js +24 -4
  113. package/src/utils/import.js +6 -1
  114. package/src/utils/imports.js +83 -0
  115. package/src/utils/mime.js +9 -0
  116. package/src/utils/params.js +66 -0
  117. package/src/utils/routing.js +90 -44
  118. package/src/utils/shared-iterator.js +5 -0
  119. package/src/utils/streaming.js +14 -4
  120. package/src/utils/url.js +20 -2
  121. package/src/version.js +1 -1
  122. package/types/index.d.ts +467 -537
  123. package/types/index.d.ts.map +22 -39
  124. package/src/core/sync/write_root.js +0 -148
  125. package/src/exports/internal/server.js +0 -22
  126. package/src/types/synthetic/$lib.md +0 -5
  127. /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
package/src/core/env.js CHANGED
@@ -38,6 +38,7 @@ export async function load_explicit_env(kit, file, root, mode) {
38
38
  logLevel: 'silent',
39
39
  mode,
40
40
  define: {
41
+ __SVELTEKIT_PAYLOAD__: 'undefined', // coming in through static import in env/internal.js but will end up unused
41
42
  __SVELTEKIT_APP_VERSION__: JSON.stringify(kit.version.name) // needed by $app/env
42
43
  },
43
44
  resolve: {
@@ -51,6 +52,10 @@ export async function load_explicit_env(kit, file, root, mode) {
51
52
  /** @type {Record<string, EnvVarConfig<any>>} */
52
53
  let variables;
53
54
 
55
+ /** @type {import('../runtime/app/env/internal.js')} */ (
56
+ await server.ssrLoadModule(`${runtime_directory}/app/env/internal.js`)
57
+ ).set_building();
58
+
54
59
  try {
55
60
  ({ variables } = await server.ssrLoadModule(file));
56
61
 
@@ -87,11 +92,12 @@ export async function load_explicit_env(kit, file, root, mode) {
87
92
 
88
93
  /**
89
94
  * Creates the `__sveltekit/env` module
90
- * @param {Record<string, EnvVarConfig<any>> | null} variables
95
+ * @param {Record<string, EnvVarConfig<any> | undefined> | null} variables
91
96
  * @param {Record<string, string>} env
92
97
  * @param {string | null} entry
98
+ * @param {boolean} is_dev
93
99
  */
94
- export function create_sveltekit_env(variables, env, entry) {
100
+ export function create_sveltekit_env(variables, env, entry, is_dev) {
95
101
  const imports = entry
96
102
  ? [
97
103
  `import { variables } from ${JSON.stringify(entry)};`,
@@ -106,7 +112,7 @@ export function create_sveltekit_env(variables, env, entry) {
106
112
  const issues = {};
107
113
 
108
114
  for (const [name, config] of Object.entries(variables ?? {})) {
109
- if (config.static) {
115
+ if (config?.static) {
110
116
  if (config.public) {
111
117
  const value = validate(variables ?? {}, env[name], name, issues);
112
118
  declarations.push(`explicit_public_env.${name} = ${devalue.uneval(value)};`);
@@ -116,7 +122,7 @@ export function create_sveltekit_env(variables, env, entry) {
116
122
  `const ${name} = validate(variables, env.${name}, ${JSON.stringify(name)}, issues);`
117
123
  );
118
124
 
119
- if (config.public) {
125
+ if (config?.public) {
120
126
  setters.push(`explicit_public_env.${name} = ${name};`);
121
127
  setters.push(`rendered_env.${name} = ${name};`);
122
128
  } else {
@@ -145,6 +151,19 @@ export function create_sveltekit_env(variables, env, entry) {
145
151
  }`
146
152
  ];
147
153
 
154
+ // In dev, initialise the env immediately. Tools like `vite-node` load modules
155
+ // through the Vite config but don't run the SvelteKit dev server, which is what
156
+ // normally calls `set_env`. Without this, dynamic env vars imported from
157
+ // `$app/env/public` and `$app/env/private` would be `undefined` in such contexts.
158
+ if (is_dev) {
159
+ /** @type {Record<string, string>} */
160
+ const dev_env = {};
161
+ for (const name of Object.keys(variables ?? {})) {
162
+ if (name in env) dev_env[name] = env[name];
163
+ }
164
+ blocks.push(`set_env(${devalue.uneval(dev_env)});`);
165
+ }
166
+
148
167
  const module = blocks.join('\n\n');
149
168
 
150
169
  return module;
@@ -213,9 +232,36 @@ export function create_sveltekit_env_public(variables, env, prelude) {
213
232
  return `${prelude}\n\n${exports.join('')}`;
214
233
  }
215
234
 
235
+ /**
236
+ * Creates the `__sveltekit/env/service-worker` module used in production. When an app uses
237
+ * dynamic public env vars, they're loaded at runtime via an import of the prerendered
238
+ * `env.js`. If there are none, values are inlined.
239
+ * @param {Record<string, EnvVarConfig<any>> | null} variables
240
+ * @param {Record<string, string>} env
241
+ * @param {string} global
242
+ * @param {string} base
243
+ * @param {string} app_dir
244
+ */
245
+ export function create_sveltekit_env_service_worker(variables, env, global, base, app_dir) {
246
+ const has_dynamic_public_env = Object.values(variables ?? {}).some(
247
+ (config) => config.public && !config.static
248
+ );
249
+
250
+ if (!has_dynamic_public_env) {
251
+ return create_sveltekit_env_service_worker_dev(variables, env, global);
252
+ }
253
+
254
+ return dedent`
255
+ import { env } from '${base}/${app_dir}/env.js';
256
+
257
+ globalThis.__SVELTEKIT_EXPERIMENTAL_EXPLICIT_ENVIRONMENT_VARIABLES__ = true;
258
+
259
+ ${global} = { env };
260
+ `;
261
+ }
262
+
216
263
  /**
217
264
  * Creates the `__sveltekit/env/service-worker` module used in development
218
- * (but not in prod, which goes through build_service_worker instead)
219
265
  * @param {Record<string, EnvVarConfig<any>> | null} variables
220
266
  * @param {Record<string, string>} env
221
267
  * @param {string} global
@@ -330,3 +376,73 @@ export const reserved = new Set([
330
376
  ]);
331
377
 
332
378
  export const valid_identifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
379
+
380
+ /**
381
+ * Generates `export const` declarations (and, for reserved-word names that need
382
+ * aliasing, `const` + re-export specifiers) for a set of named exports.
383
+ *
384
+ * For regular names, emits a single efficient `export const name = expr;` statement.
385
+ * For reserved-word names (e.g. `delete`, `class`), emits `const alias = expr;` plus
386
+ * a re-export specifier (`alias as name`), since reserved words can't be `const`
387
+ * binding names but CAN appear in export specifiers.
388
+ *
389
+ * You can do evil things like `export { c as class }`. In order to import/re-export
390
+ * these, you need to alias the binding, then un-alias it when re-exporting:
391
+ *
392
+ * const _0 = ...; // safe binding name
393
+ * export { _0 as class }; // valid — `class` is allowed in export specifiers
394
+ *
395
+ * Aliases are chosen to avoid collisions with any of the supplied names. The
396
+ * namespace binding (used to hold the imported module) is likewise chosen to
397
+ * avoid collisions.
398
+ *
399
+ * @param {Iterable<string>} names — the export names
400
+ * @param {(name: string, namespace: string) => string} build_expression —
401
+ * called for each name to produce the right-hand side of the declaration;
402
+ * receives the chosen namespace binding so it can reference the imported module
403
+ * @param {string} namespace_prefix — the preferred binding name for the namespace
404
+ * (suffixed with a number if it collides with any export name)
405
+ * @returns {{ namespace: string, declarations: string[], reexports: string[] }}
406
+ */
407
+ export function create_exported_declarations(names, build_expression, namespace_prefix) {
408
+ /** @type {Set<string>} */
409
+ const set = new Set(names);
410
+
411
+ let namespace = namespace_prefix;
412
+ let namespace_index = 0;
413
+ while (set.has(namespace)) {
414
+ namespace = `${namespace_prefix}${namespace_index++}`;
415
+ }
416
+
417
+ let alias_index = 0;
418
+ /** @type {Map<string, string>} */
419
+ const aliases = new Map();
420
+
421
+ for (const name of set) {
422
+ if (!reserved.has(name)) continue;
423
+
424
+ let alias = `_${alias_index++}`;
425
+ while (set.has(alias)) {
426
+ alias = `_${alias_index++}`;
427
+ }
428
+ aliases.set(name, alias);
429
+ }
430
+
431
+ /** @type {string[]} */
432
+ const declarations = [];
433
+ /** @type {string[]} */
434
+ const reexports = [];
435
+
436
+ for (const name of set) {
437
+ const alias = aliases.get(name);
438
+ const expr = build_expression(name, namespace);
439
+ if (alias) {
440
+ declarations.push(`const ${alias} = ${expr};`);
441
+ reexports.push(`${alias} as ${name}`);
442
+ } else {
443
+ declarations.push(`export const ${name} = ${expr};`);
444
+ }
445
+ }
446
+
447
+ return { namespace, declarations, reexports };
448
+ }
@@ -1,7 +1,7 @@
1
1
  /** @import { RemoteChunk } from 'types' */
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
- import * as mime from 'mrmime';
4
+ import { lookup as mime_lookup } from '../../utils/mime.js';
5
5
  import { s } from '../../utils/misc.js';
6
6
  import { get_mime_lookup } from '../utils.js';
7
7
  import { resolve_symlinks } from '../../exports/vite/build/utils.js';
@@ -69,10 +69,8 @@ export function generate_manifest({
69
69
  assets.push(build_data.service_worker);
70
70
  }
71
71
 
72
- // In case of server-side route resolution, we need to include all matchers. Prerendered routes are not part
73
- // of the server manifest, and they could reference matchers that then would not be included.
74
- const matchers = new Set(
75
- build_data.client?.nodes ? Object.keys(build_data.manifest_data.matchers) : undefined
72
+ const uses_matchers = build_data.manifest_data.routes.some((route) =>
73
+ route.params.some((param) => param.matcher)
76
74
  );
77
75
 
78
76
  /** @param {Array<number | undefined>} indexes */
@@ -93,7 +91,7 @@ export function generate_manifest({
93
91
  files[file] = fs.statSync(path.resolve(build_data.out_dir, 'server', file)).size;
94
92
 
95
93
  const ext = path.extname(file);
96
- mime_types[ext] ??= mime.lookup(ext) || '';
94
+ mime_types[ext] ??= mime_lookup(ext) || '';
97
95
  }
98
96
 
99
97
  // prettier-ignore
@@ -117,10 +115,6 @@ export function generate_manifest({
117
115
  ${routes.map(route => {
118
116
  if (!route.page && !route.endpoint) return;
119
117
 
120
- route.params.forEach(param => {
121
- if (param.matcher) matchers.add(param.matcher);
122
- });
123
-
124
118
  return dedent`
125
119
  {
126
120
  id: ${s(route.id)},
@@ -134,11 +128,14 @@ export function generate_manifest({
134
128
  ],
135
129
  prerendered_routes: new Set(${s(prerendered)}),
136
130
  matchers: async () => {
137
- ${Array.from(
138
- matchers,
139
- type => `const { match: ${type} } = await import ('${(join_relative(relative_path, `/entries/matchers/${type}.js`))}')`
140
- ).join('\n')}
141
- return { ${Array.from(matchers).join(', ')} };
131
+ ${
132
+ uses_matchers && build_data.manifest_data.params
133
+ ? dedent`
134
+ const { params } = await import('${join_relative(relative_path, '/entries/params.js')}');
135
+ return params;
136
+ `
137
+ : 'return {};'
138
+ }
142
139
  },
143
140
  server_assets: ${s(files)}
144
141
  }
@@ -1,4 +1,3 @@
1
- /** @import { Adapter } from '@sveltejs/kit' */
2
1
  /** @import { RemoteChunk } from 'types' */
3
2
  import { join } from 'node:path';
4
3
  import { pathToFileURL } from 'node:url';
@@ -10,7 +9,6 @@ import { has_server_load, resolve_route } from '../../utils/routing.js';
10
9
  import { check_feature } from '../../utils/features.js';
11
10
  import { createReadableStream } from '@sveltejs/kit/node';
12
11
  import { PageNodes } from '../../utils/page_nodes.js';
13
- import { build_server_nodes } from '../../exports/vite/build/build_server.js';
14
12
 
15
13
  export default forked(import.meta.url, analyse);
16
14
 
@@ -22,9 +20,7 @@ export default forked(import.meta.url, analyse);
22
20
  * server_manifest: import('vite').Manifest;
23
21
  * tracked_features: Record<string, string[]>;
24
22
  * env: Record<string, string>;
25
- * out: string;
26
23
  * remotes: RemoteChunk[];
27
- * root: string;
28
24
  * vite_config_file: string | undefined;
29
25
  * }} opts
30
26
  */
@@ -35,24 +31,14 @@ async function analyse({
35
31
  server_manifest,
36
32
  tracked_features,
37
33
  env,
38
- out,
39
34
  remotes,
40
- root,
41
35
  vite_config_file
42
36
  }) {
43
37
  /** @type {import('@sveltejs/kit').SSRManifest} */
44
38
  const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
45
39
 
46
40
  const vite_config = await load_vite_config(vite_config_file);
47
-
48
41
  const config = extract_svelte_config(vite_config).kit;
49
-
50
- // TODO i think this can just be config.adapter?
51
- /** @type {Adapter | undefined} */
52
- const adapter = vite_config.plugins.find(
53
- (plugin) => plugin.name === 'vite-plugin-sveltekit-adapter'
54
- )?.api?.adapter;
55
-
56
42
  const server_root = join(config.outDir, 'output');
57
43
 
58
44
  /** @type {import('types').ServerInternalModule} */
@@ -62,13 +48,15 @@ async function analyse({
62
48
  // essential we do this before analysing the code
63
49
  internal.set_building();
64
50
 
65
- // set env, `read`, and `manifest`, in case they're used in initialisation
66
- internal.set_env(env);
51
+ // set `read` and `manifest`, in case they're used in initialisation
67
52
  internal.set_manifest(manifest);
68
53
  internal.set_read_implementation((file) => createReadableStream(`${server_root}/server/${file}`));
69
54
 
70
- // first, build server nodes without the client manifest so we can analyse it
71
- build_server_nodes(out, config, manifest_data, server_manifest, null, null, null, root);
55
+ // `set_env` lives in a separate module that imports the user's `src/env` config. We import it
56
+ // *after* `set_building()` so that `building`-dependent expressions resolve correctly
57
+ /** @type {import('__sveltekit/env')} */
58
+ const { set_env } = await import(pathToFileURL(`${server_root}/server/env.js`).href);
59
+ set_env(env);
72
60
 
73
61
  /** @type {import('types').ServerMetadata} */
74
62
  const metadata = {
@@ -134,7 +122,7 @@ async function analyse({
134
122
  server_manifest,
135
123
  tracked_features
136
124
  )) {
137
- check_feature(route.id, route_config, feature, adapter);
125
+ check_feature(route.id, route_config, feature, config.adapter);
138
126
  }
139
127
  }
140
128
 
@@ -38,6 +38,18 @@ export function crawl(html, base) {
38
38
  /** @type {string[]} */
39
39
  const hrefs = [];
40
40
 
41
+ /** @type {string[]} */
42
+ const invalid = [];
43
+
44
+ /** @param {string} url */
45
+ const push_href = (url) => {
46
+ try {
47
+ hrefs.push(resolve(base, url));
48
+ } catch {
49
+ invalid.push(url);
50
+ }
51
+ };
52
+
41
53
  let i = 0;
42
54
  main: while (i < html.length) {
43
55
  const char = html[i];
@@ -186,9 +198,13 @@ export function crawl(html, base) {
186
198
 
187
199
  if (href) {
188
200
  if (tag === 'BASE') {
189
- base = resolve(base, href);
201
+ try {
202
+ base = resolve(base, href);
203
+ } catch {
204
+ invalid.push(href);
205
+ }
190
206
  } else if (!rel || !/\bexternal\b/i.test(rel)) {
191
- hrefs.push(resolve(base, href));
207
+ push_href(href);
192
208
  }
193
209
  }
194
210
 
@@ -201,7 +217,7 @@ export function crawl(html, base) {
201
217
  }
202
218
 
203
219
  if (src) {
204
- hrefs.push(resolve(base, src));
220
+ push_href(src);
205
221
  }
206
222
 
207
223
  if (srcset) {
@@ -222,7 +238,7 @@ export function crawl(html, base) {
222
238
  candidates.push(value);
223
239
  for (const candidate of candidates) {
224
240
  const src = candidate.split(WHITESPACE)[0];
225
- if (src) hrefs.push(resolve(base, src));
241
+ if (src) push_href(src);
226
242
  }
227
243
  }
228
244
 
@@ -230,7 +246,7 @@ export function crawl(html, base) {
230
246
  const attr = name ?? property;
231
247
 
232
248
  if (attr && CRAWLABLE_META_NAME_ATTRS.has(attr)) {
233
- hrefs.push(resolve(base, content));
249
+ push_href(content);
234
250
  }
235
251
  }
236
252
  }
@@ -239,5 +255,5 @@ export function crawl(html, base) {
239
255
  i += 1;
240
256
  }
241
257
 
242
- return { ids, hrefs };
258
+ return { ids, hrefs, invalid };
243
259
  }
@@ -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';
@@ -44,14 +43,20 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
44
43
  /** @type {import('types').ServerInternalModule} */
45
44
  const internal = await import(pathToFileURL(`${out}/server/internal.js`).href);
46
45
 
47
- /** @type {import('types').ServerModule} */
48
- const { Server } = await import(pathToFileURL(`${out}/server/index.js`).href);
49
-
50
46
  // configure `import { building } from `$app/env` —
51
47
  // essential we do this before analysing the code
52
48
  internal.set_building();
53
49
  internal.set_prerendering();
54
50
 
51
+ // `set_env` and `Server` live in modules that import the user's `src/env` config. We import them
52
+ // *after* `set_building()` so that `building`-dependent expressions resolve correctly
53
+ /** @type {import('__sveltekit/env')} */
54
+ const { set_env } = await import(pathToFileURL(`${out}/server/env.js`).href);
55
+ set_env(env);
56
+
57
+ /** @type {import('types').ServerModule} */
58
+ const { Server } = await import(pathToFileURL(`${out}/server/index.js`).href);
59
+
55
60
  /**
56
61
  * @template {{message: string}} T
57
62
  * @template {Omit<T, 'message'>} K
@@ -105,12 +110,14 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
105
110
 
106
111
  const config = extract_svelte_config(vite_config).kit;
107
112
 
113
+ const prerender_origin = config.paths.origin || 'http://sveltekit-prerender';
114
+
108
115
  if (hash) {
109
116
  const fallback = await generate_fallback({
110
117
  manifest_path,
111
118
  env,
112
119
  out_dir: config.outDir,
113
- origin: config.prerender.origin,
120
+ origin: prerender_origin,
114
121
  assets: config.files.assets
115
122
  });
116
123
 
@@ -125,13 +132,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
125
132
  return { prerendered, prerender_map };
126
133
  }
127
134
 
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?.();
135
+ const emulator = await config.adapter?.emulate?.();
135
136
 
136
137
  /** @type {import('types').Logger} */
137
138
  const log = logger({ verbose });
@@ -145,7 +146,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
145
146
  ({ status, path, referrer, referenceType }) => {
146
147
  const message =
147
148
  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} does not begin with \`base\`. You can fix this by using \`resolve('${path}')\` from \`$app/paths\`. The base path is configurable from \`paths.base\` - see https://svelte.dev/docs/kit/configuration#paths for more info`
149
150
  : path;
150
151
 
151
152
  return `${status} ${message}${referrer ? ` (${referenceType} from ${referrer})` : ''}`;
@@ -180,6 +181,14 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
180
181
  }
181
182
  );
182
183
 
184
+ const handle_invalid_url = normalise_error_handler(
185
+ log,
186
+ config.prerender.handleInvalidUrl,
187
+ ({ href, referrer }) => {
188
+ return `Invalid URL ${href}${referrer ? ` (linked from ${referrer})` : ''}`;
189
+ }
190
+ );
191
+
183
192
  const q = queue(config.prerender.concurrency);
184
193
 
185
194
  /**
@@ -210,6 +219,8 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
210
219
 
211
220
  const seen = new Set();
212
221
  const written = new Set();
222
+
223
+ /** @type {Map<string, Promise<any>>} */
213
224
  const remote_responses = new Map();
214
225
 
215
226
  /** @type {Map<string, Set<string>>} */
@@ -249,7 +260,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
249
260
  /** @type {Map<string, import('types').PrerenderDependency>} */
250
261
  const dependencies = new Map();
251
262
 
252
- const response = await server.respond(new Request(config.prerender.origin + encoded), {
263
+ const response = await server.respond(new Request(prerender_origin + encoded), {
253
264
  getClientAddress() {
254
265
  throw new Error('Cannot read clientAddress during prerendering');
255
266
  },
@@ -332,16 +343,20 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
332
343
 
333
344
  // if it's a 200 HTML response, crawl it. Skip error responses, as we don't save those
334
345
  if (response.ok && config.prerender.crawl && headers['content-type'] === 'text/html') {
335
- const { ids, hrefs } = crawl(body.toString(), decoded);
346
+ const { ids, hrefs, invalid } = crawl(body.toString(), decoded);
347
+
348
+ for (const href of invalid) {
349
+ handle_invalid_url({ href, referrer: decoded });
350
+ }
336
351
 
337
352
  actual_hashlinks.set(decoded, ids);
338
353
 
339
354
  /** @param {string} href */
340
355
  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);
356
+ if (href.startsWith(prerender_origin)) {
357
+ if (href === prerender_origin) return '/';
358
+ if (href.at(prerender_origin.length) !== '/') return href;
359
+ return href.slice(prerender_origin.length);
345
360
  }
346
361
  return href;
347
362
  };
@@ -386,6 +401,12 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
386
401
  const type = headers['content-type'];
387
402
  const is_html = response_type === REDIRECT || type === 'text/html';
388
403
 
404
+ if (!is_html && response.status === 200 && decoded.slice(config.paths.base.length + 1) === '') {
405
+ throw new Error(
406
+ `Cannot prerender a root +server.js that returns a non-HTML response - static hosts always serve an HTML file for \`${config.paths.base || '/'}\``
407
+ );
408
+ }
409
+
389
410
  const file = output_filename(decoded, is_html);
390
411
  const dest = `${config.outDir}/output/prerendered/${category}/${file}`;
391
412
 
@@ -495,10 +516,8 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
495
516
  }
496
517
  }
497
518
 
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);
519
+ // the user's remote function modules may reference `read` or the `manifest` at the top-level
520
+ // so we need to set them before evaluating those modules to avoid potential runtime errors
502
521
  internal.set_manifest(manifest);
503
522
  internal.set_read_implementation((file) => createReadableStream(`${out}/server/${file}`));
504
523