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

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 (195) hide show
  1. package/package.json +62 -36
  2. package/src/cli.js +15 -12
  3. package/src/constants.js +6 -1
  4. package/src/core/adapt/builder.js +66 -72
  5. package/src/core/adapt/index.js +17 -6
  6. package/src/core/config/index.js +134 -80
  7. package/src/core/config/options.js +295 -273
  8. package/src/core/config/types.d.ts +1 -1
  9. package/src/core/env.js +146 -16
  10. package/src/core/generate_manifest/index.js +18 -15
  11. package/src/core/postbuild/analyse.js +8 -20
  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 +222 -70
  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 +82 -62
  19. package/src/core/sync/sync.js +40 -31
  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} +107 -58
  23. package/src/core/sync/write_client_manifest.js +14 -29
  24. package/src/core/sync/write_env.js +7 -5
  25. package/src/core/sync/write_server.js +37 -45
  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 +127 -123
  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 +195 -0
  36. package/src/exports/hooks/sequence.js +9 -6
  37. package/src/exports/index.js +52 -29
  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} +2 -3
  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 +50 -1733
  49. package/src/exports/remote/index.js +11 -0
  50. package/src/exports/remote/public.d.ts +519 -0
  51. package/src/exports/url.js +86 -0
  52. package/src/exports/vite/build/build_server.js +54 -65
  53. package/src/exports/vite/build/remote.js +24 -19
  54. package/src/exports/vite/build/utils.js +0 -8
  55. package/src/exports/vite/dev/index.js +218 -146
  56. package/src/exports/vite/index.js +1590 -858
  57. package/src/exports/vite/module_ids.js +2 -2
  58. package/src/exports/vite/preview/index.js +40 -32
  59. package/src/exports/vite/public.d.ts +588 -0
  60. package/src/exports/vite/utils.js +84 -46
  61. package/src/pathname.js +55 -0
  62. package/src/runner.js +15 -0
  63. package/src/runtime/app/env/internal.js +4 -4
  64. package/src/runtime/app/env/types.d.ts +1 -1
  65. package/src/runtime/app/environment/index.js +3 -3
  66. package/src/runtime/app/{forms.js → forms/index.js} +73 -42
  67. package/src/runtime/app/forms/public.d.ts +2 -0
  68. package/src/runtime/app/forms/types.d.ts +56 -0
  69. package/src/runtime/app/internal/transport.js +53 -0
  70. package/src/runtime/app/manifest/index.js +1 -0
  71. package/src/runtime/app/{navigation.js → navigation/index.js} +2 -1
  72. package/src/runtime/app/navigation/public.d.ts +237 -0
  73. package/src/runtime/app/paths/client.js +37 -37
  74. package/src/runtime/app/paths/index.js +1 -1
  75. package/src/runtime/app/paths/internal/client.js +34 -2
  76. package/src/runtime/app/paths/internal/server.js +6 -23
  77. package/src/runtime/app/paths/internal.d.ts +3 -0
  78. package/src/runtime/app/paths/public.d.ts +1 -29
  79. package/src/runtime/app/paths/server.js +36 -17
  80. package/src/runtime/app/paths/types.d.ts +11 -19
  81. package/src/runtime/app/server/index.js +2 -2
  82. package/src/runtime/app/server/public.d.ts +201 -0
  83. package/src/runtime/app/server/remote/command.js +13 -11
  84. package/src/runtime/app/server/remote/form.js +61 -39
  85. package/src/runtime/app/server/remote/prerender.js +45 -45
  86. package/src/runtime/app/server/remote/query.js +106 -108
  87. package/src/runtime/app/server/remote/requested.js +27 -19
  88. package/src/runtime/app/server/remote/shared.js +76 -76
  89. package/src/runtime/app/service-worker/index.js +24 -0
  90. package/src/runtime/app/state/client.js +4 -2
  91. package/src/runtime/app/state/index.js +7 -5
  92. package/src/runtime/app/state/public.d.ts +72 -0
  93. package/src/runtime/app/state/server.js +3 -0
  94. package/src/runtime/app/stores.js +15 -78
  95. package/src/runtime/client/bundle.js +1 -1
  96. package/src/runtime/client/client-entry.js +3 -0
  97. package/src/runtime/client/client.js +1336 -648
  98. package/src/runtime/client/constants.js +3 -6
  99. package/src/runtime/client/entry.js +24 -3
  100. package/src/runtime/client/fetcher.js +25 -25
  101. package/src/runtime/client/ndjson.js +1 -1
  102. package/src/runtime/client/parse.js +1 -1
  103. package/src/runtime/client/payload.js +17 -0
  104. package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
  105. package/src/runtime/client/remote-functions/command.svelte.js +37 -61
  106. package/src/runtime/client/remote-functions/form.svelte.js +267 -178
  107. package/src/runtime/client/remote-functions/prerender.svelte.js +32 -11
  108. package/src/runtime/client/remote-functions/query/index.js +10 -17
  109. package/src/runtime/client/remote-functions/query/instance.svelte.js +64 -18
  110. package/src/runtime/client/remote-functions/query/proxy.js +5 -5
  111. package/src/runtime/client/remote-functions/query-batch.svelte.js +61 -70
  112. package/src/runtime/client/remote-functions/query-live/index.js +1 -1
  113. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +63 -21
  114. package/src/runtime/client/remote-functions/query-live/iterator.js +15 -12
  115. package/src/runtime/client/remote-functions/query-live/proxy.js +2 -12
  116. package/src/runtime/client/remote-functions/shared.svelte.js +90 -65
  117. package/src/runtime/client/snapshots.js +147 -0
  118. package/src/runtime/client/state.svelte.js +94 -55
  119. package/src/runtime/client/stream.js +3 -2
  120. package/src/runtime/client/types.d.ts +13 -9
  121. package/src/runtime/client/utils.js +22 -110
  122. package/src/runtime/components/root.svelte +56 -0
  123. package/src/runtime/env/dynamic/private.js +7 -0
  124. package/src/runtime/env/dynamic/public.js +7 -0
  125. package/src/runtime/env/static/private.js +6 -0
  126. package/src/runtime/env/static/public.js +6 -0
  127. package/src/runtime/form-utils.js +172 -61
  128. package/src/runtime/pathname.js +20 -32
  129. package/src/runtime/props.svelte.js +72 -0
  130. package/src/runtime/server/constants.js +0 -3
  131. package/src/runtime/server/cookie.js +72 -55
  132. package/src/runtime/server/csrf.js +65 -0
  133. package/src/runtime/server/data/index.js +49 -53
  134. package/src/runtime/server/dev.js +22 -0
  135. package/src/runtime/server/endpoint.js +6 -7
  136. package/src/runtime/server/env_module.js +0 -5
  137. package/src/runtime/server/errors.js +160 -0
  138. package/src/runtime/server/fetch.js +32 -39
  139. package/src/runtime/server/index.js +90 -64
  140. package/src/runtime/server/internal.js +71 -0
  141. package/src/runtime/server/page/actions.js +85 -64
  142. package/src/runtime/server/page/crypto.js +2 -2
  143. package/src/runtime/server/page/csp.js +88 -104
  144. package/src/runtime/server/page/data_serializer.js +24 -25
  145. package/src/runtime/server/page/index.js +36 -53
  146. package/src/runtime/server/page/load_data.js +50 -57
  147. package/src/runtime/server/page/render.js +173 -239
  148. package/src/runtime/server/page/respond_with_error.js +17 -31
  149. package/src/runtime/server/page/serialize_data.js +2 -13
  150. package/src/runtime/server/page/server_routing.js +85 -26
  151. package/src/runtime/server/remote-functions.js +640 -0
  152. package/src/runtime/server/respond.js +190 -131
  153. package/src/runtime/server/sourcemaps.js +183 -0
  154. package/src/runtime/server/state.js +53 -0
  155. package/src/runtime/server/utils.js +13 -155
  156. package/src/runtime/shared.js +20 -40
  157. package/src/runtime/utils.js +3 -0
  158. package/src/types/ambient-private.d.ts +11 -1
  159. package/src/types/ambient.d.ts +87 -36
  160. package/src/types/global-private.d.ts +25 -25
  161. package/src/types/internal.d.ts +163 -157
  162. package/src/types/private.d.ts +41 -1
  163. package/src/utils/error.js +28 -4
  164. package/src/utils/escape.js +9 -25
  165. package/src/utils/features.js +1 -1
  166. package/src/utils/filesystem.js +1 -23
  167. package/src/utils/fork.js +7 -2
  168. package/src/utils/hash.js +21 -0
  169. package/src/utils/http.js +8 -7
  170. package/src/utils/import.js +9 -2
  171. package/src/utils/imports.js +83 -0
  172. package/src/utils/mime.js +9 -0
  173. package/src/utils/page_nodes.js +6 -7
  174. package/src/utils/params.js +67 -0
  175. package/src/utils/regex.js +9 -0
  176. package/src/utils/routing.js +145 -73
  177. package/src/utils/shared-iterator.js +5 -0
  178. package/src/utils/streaming.js +14 -4
  179. package/src/utils/url.js +33 -2
  180. package/src/version.js +1 -1
  181. package/types/index.d.ts +2616 -2433
  182. package/types/index.d.ts.map +120 -106
  183. package/src/core/sync/write_ambient.js +0 -18
  184. package/src/core/sync/write_root.js +0 -148
  185. package/src/core/sync/write_tsconfig.js +0 -250
  186. package/src/exports/internal/server.js +0 -22
  187. package/src/exports/node/polyfills.js +0 -30
  188. package/src/runtime/server/app.js +0 -9
  189. package/src/runtime/server/remote.js +0 -457
  190. package/src/runtime/shared-server.js +0 -7
  191. package/src/runtime/telemetry/otel.js +0 -21
  192. package/src/runtime/telemetry/record_span.js +0 -65
  193. package/src/types/synthetic/$lib.md +0 -5
  194. /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
  195. /package/src/{runtime/telemetry/noop.js → telemetry.js} +0 -0
@@ -1,63 +1,84 @@
1
- /** @import { EnvVarConfig } from '@sveltejs/kit' */
2
- /** @import { Options, SvelteConfig } from '@sveltejs/vite-plugin-svelte' */
1
+ /** @import { TopLevelFilterExpression } from '@rolldown/pluginutils' */
2
+ /** @import { EnvVarConfig } from '@sveltejs/kit/env' */
3
+ /** @import { Options } from '@sveltejs/vite-plugin-svelte' */
3
4
  /** @import { PreprocessorGroup } from 'svelte/compiler' */
4
- /** @import { Plugin, ResolvedConfig, UserConfig, ViteDevServer } from 'vite' */
5
+ /** @import { Asset, BuildData, ManifestData, Prerendered, RemoteChunk, RemoteInternals, RouteData, ServerMetadata, ValidatedConfig } from 'types' */
6
+ /** @import { Manifest, Plugin, ResolvedConfig, Rolldown, UserConfig, ViteDevServer } from 'vite' */
5
7
  import fs from 'node:fs';
6
8
  import path from 'node:path';
7
9
  import process from 'node:process';
8
10
  import { styleText } from 'node:util';
9
- import { loadEnv } from 'vite';
10
- import { exactRegex, prefixRegex } from 'rolldown/filter';
11
11
 
12
- import { copy, mkdirp, read, resolve_entry, rimraf } from '../../utils/filesystem.js';
12
+ import {
13
+ and,
14
+ code,
15
+ exactRegex,
16
+ importerId,
17
+ include,
18
+ not,
19
+ prefixRegex
20
+ } from '@rolldown/pluginutils';
21
+ import MagicString from 'magic-string';
22
+
23
+ import { copy, read, resolve_entry } from '../../utils/filesystem.js';
13
24
  import { posixify } from '../../utils/os.js';
25
+ import { to_fs } from '../../utils/vite.js';
14
26
  import {
15
27
  create_sveltekit_env,
16
28
  create_sveltekit_env_public,
17
29
  resolve_explicit_env_entry,
30
+ create_sveltekit_env_service_worker,
18
31
  create_sveltekit_env_service_worker_dev,
19
- create_sveltekit_env_private
32
+ create_sveltekit_env_private,
33
+ create_exported_declarations
20
34
  } from '../../core/env.js';
21
35
  import * as sync from '../../core/sync/sync.js';
22
- import { create_assets } from '../../core/sync/create_manifest_data/index.js';
36
+ import { load_and_validate_params } from '../../utils/params.js';
23
37
  import { runtime_directory, logger } from '../../core/utils.js';
24
38
  import { generate_manifest } from '../../core/generate_manifest/index.js';
25
39
  import { build_server_nodes } from './build/build_server.js';
26
- import { assets_base, find_deps, resolve_symlinks } from './build/utils.js';
27
- import { dev } from './dev/index.js';
40
+ import { find_deps, resolve_symlinks } from './build/utils.js';
41
+ import { dev, invalidate_module } from './dev/index.js';
28
42
  import { preview } from './preview/index.js';
29
43
  import {
30
44
  error_for_missing_config,
31
45
  get_config_aliases,
46
+ is_remote_module,
32
47
  normalize_id,
33
- strip_virtual_prefix
48
+ remote_module_pattern,
49
+ server_only_directory_pattern,
50
+ server_only_module_pattern
34
51
  } from './utils.js';
35
52
  import { stackless } from '../../utils/error.js';
53
+ import { adapt } from '../../core/adapt/index.js';
36
54
  import { write_client_manifest } from '../../core/sync/write_client_manifest.js';
37
55
  import prerender from '../../core/postbuild/prerender.js';
38
56
  import analyse from '../../core/postbuild/analyse.js';
39
57
  import { s } from '../../utils/misc.js';
40
58
  import { hash } from '../../utils/hash.js';
41
59
  import { dedent } from '../../core/sync/utils.js';
60
+ import {
61
+ is_app_route,
62
+ is_endpoint_route,
63
+ is_page_route
64
+ } from '../../core/sync/create_manifest_data/index.js';
65
+ import { get_import_aliases, get_hash_import_keys } from '../../utils/imports.js';
42
66
  import {
43
67
  app_env_private,
44
68
  app_server,
45
- service_worker,
46
69
  sveltekit_env,
47
70
  sveltekit_env_private,
48
71
  sveltekit_env_service_worker,
49
- sveltekit_server,
72
+ sveltekit_manifest_data,
50
73
  sveltekit_env_public_client,
51
74
  sveltekit_env_public_server
52
75
  } from './module_ids.js';
53
76
  import { import_peer } from '../../utils/import.js';
54
77
  import { compact } from '../../utils/array.js';
55
78
  import { should_ignore, has_children } from './static_analysis/utils.js';
56
- import { process_config } from '../../core/config/index.js';
79
+ import { process_config, split_config, validate_config } from '../../core/config/index.js';
57
80
  import { treeshake_prerendered_remotes } from './build/remote.js';
58
-
59
- /** @type {string} */
60
- let root;
81
+ import { get_runner } from '../../runner.js';
61
82
 
62
83
  /** @type {import('./types.js').EnforcedConfig} */
63
84
  const enforced_config = {
@@ -89,19 +110,34 @@ const enforced_config = {
89
110
  resolve: {
90
111
  alias: {
91
112
  $app: true,
92
- $lib: true,
93
- '$service-worker': true
113
+ $env: true
94
114
  }
95
115
  }
96
116
  };
97
117
 
98
118
  const options_regex = /(export\s+const\s+(prerender|csr|ssr|trailingSlash))\s*=/s;
99
119
 
120
+ const removed_modules = [
121
+ {
122
+ name: '$lib',
123
+ pattern: /^\$lib(?:\/.*|\?.*)?$/,
124
+ message:
125
+ "`$lib` has been removed. Use `#lib` instead: https://svelte.dev/docs/kit/$lib. To keep using `$lib`, add `alias: { '$lib': 'src/lib' }` to your SvelteKit config."
126
+ },
127
+ {
128
+ name: '$service-worker',
129
+ pattern: /^\$service-worker(?:\?.*)?$/,
130
+ message:
131
+ '`$service-worker` has been removed. Use `immutable`, `assets` and `prerendered` from `$app/manifest`, `version` from `$app/env`, and `resolve(...)` from `$app/paths` instead: https://svelte.dev/docs/kit/$service-worker'
132
+ }
133
+ ];
134
+
100
135
  /** @type {Set<string>} */
101
136
  const warned = new Set();
102
137
 
103
138
  /** @type {PreprocessorGroup} */
104
139
  const warning_preprocessor = {
140
+ name: 'sveltekit:warnings',
105
141
  script: ({ content, filename }) => {
106
142
  if (!filename) return;
107
143
 
@@ -112,7 +148,7 @@ const warning_preprocessor = {
112
148
  const fixed = basename.replace('.svelte', '(.server).js/ts');
113
149
 
114
150
  const message =
115
- `\n${styleText(['bold', 'red'], path.relative(root, filename))}\n` +
151
+ `\n${styleText(['bold', 'red'], path.relative(process.cwd(), filename))}\n` +
116
152
  `\`${match[1]}\` will be ignored — move it to ${fixed} instead. See https://svelte.dev/docs/kit/page-options for more information.`;
117
153
 
118
154
  if (!warned.has(message)) {
@@ -129,7 +165,7 @@ const warning_preprocessor = {
129
165
 
130
166
  if (basename.startsWith('+layout.') && !has_children(content, true)) {
131
167
  const message =
132
- `\n${styleText(['bold', 'red'], path.relative(root, filename))}\n` +
168
+ `\n${styleText(['bold', 'red'], path.relative(process.cwd(), filename))}\n` +
133
169
  '`<slot />` or `{@render ...}` tag' +
134
170
  ' missing — inner content will not be rendered';
135
171
 
@@ -145,102 +181,88 @@ const warning_preprocessor = {
145
181
  let vite_plugin_svelte;
146
182
 
147
183
  /**
148
- * Returns the SvelteKit Vite plugins.
149
- * Since version 2.62.0 you can pass [configuration](configuration) directly, in which case `svelte.config.js` is ignored.
150
- * @param {import('@sveltejs/kit').KitConfig & Omit<SvelteConfig, 'onwarn'>} [config]
184
+ * The SvelteKit Vite plugin, which must be added to your `vite.config.js` file along with your project's configuration:
185
+ *
186
+ * ```js
187
+ * /// file: vite.config.js
188
+ * import adapter from '@sveltejs/adapter-auto';
189
+ * import { sveltekit } from '@sveltejs/kit/vite';
190
+ * import { defineConfig } from 'vite';
191
+ *
192
+ * export default defineConfig({
193
+ * plugins: [
194
+ * sveltekit({
195
+ * adapter: adapter(),
196
+ * compilerOptions: {
197
+ * experimental: {
198
+ * async: true
199
+ * }
200
+ * },
201
+ * experimental: {
202
+ * remoteFunctions: true
203
+ * }
204
+ * })
205
+ * ]
206
+ * });
207
+ * ```
208
+ *
209
+ * As well as SvelteKit, the plugin options are used by other tooling that integrates with Svelte such as editor extensions.
210
+ *
211
+ * Any options that don't belong to SvelteKit are passed through to [`vite-plugin-svelte`](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/config.md), so you can set options like `inspector` here too. The `experimental` namespace is shared — SvelteKit reads its own flags and forwards the rest.
212
+ *
213
+ * > [!LEGACY]
214
+ * > Prior to SvelteKit 3, config lived in a `svelte.config.js` file, which is no longer supported. The ability to configure SvelteKit via `vite.config.js` was added in version 2.62.
215
+ *
216
+ * @param {import('./public.js').Config} [config]
151
217
  * @returns {Promise<Plugin[]>}
152
218
  */
153
219
  export async function sveltekit(config) {
154
220
  const cwd = process.cwd();
155
221
 
156
- const { extensions, compilerOptions, vitePlugin, preprocess, ...rest } = config ?? {};
157
- const svelte_config = process_config(
158
- { extensions, compilerOptions, vitePlugin, preprocess, kit: rest },
159
- { cwd, source: 'SvelteKit options from Vite config' }
160
- );
161
-
162
- const config_file = ['svelte.config.js', 'svelte.config.ts'].find((file) => fs.existsSync(file));
163
-
164
- if (config_file) {
165
- const message = `${config_file} is no longer used. Please pass configuration via the \`sveltekit(...)\` plugin in your Vite config.`;
166
-
167
- if (!config) {
168
- throw new Error(message);
169
- }
170
-
171
- console.warn(message);
222
+ // any options passed to the plugin that SvelteKit doesn't use itself are
223
+ // forwarded to vite-plugin-svelte, which does its own validation
224
+ const split = split_config(config ?? {});
225
+ const svelte_config = validate_config(split.svelte_config);
226
+
227
+ if (Array.isArray(svelte_config.preprocess)) {
228
+ svelte_config.preprocess.push(warning_preprocessor);
229
+ } else if (svelte_config.preprocess) {
230
+ svelte_config.preprocess = [svelte_config.preprocess, warning_preprocessor];
231
+ } else {
232
+ svelte_config.preprocess = warning_preprocessor;
172
233
  }
173
234
 
174
- /** @type {Options} */
175
- const vite_plugin_svelte_options = {
176
- // we don't want vite-plugin-svelte to load the config file itself because
177
- // it will try to validate it without knowing that kit options are valid
235
+ vite_plugin_svelte = await import_peer('@sveltejs/vite-plugin-svelte', cwd);
236
+
237
+ /** @type {Partial<Options>} */
238
+ const inline_vps_config = {
239
+ preprocess: svelte_config.preprocess,
240
+ // pass through any options that SvelteKit doesn't use itself, so that
241
+ // the options SvelteKit manages always take precedence
242
+ ...split.vite_plugin_svelte_config,
243
+ // we don't want vite-plugin-svelte to load the svelte.config.js file because
244
+ // we expect options to be passed through the SvelteKit Vite plugin
178
245
  configFile: false
179
246
  };
180
247
 
181
- vite_plugin_svelte = await import_peer('@sveltejs/vite-plugin-svelte', cwd);
248
+ // vite-plugin-svelte inline config options need to be added conditionally
249
+ // because passing undefined causes it to crash
250
+ if (svelte_config.extensions) {
251
+ inline_vps_config.extensions = svelte_config.extensions;
252
+ }
253
+
254
+ if (svelte_config.compilerOptions) {
255
+ inline_vps_config.compilerOptions = svelte_config.compilerOptions;
256
+ }
182
257
 
183
- return [
184
- plugin_svelte_config({ vite_plugin_svelte_options, svelte_config }),
185
- ...vite_plugin_svelte.svelte(vite_plugin_svelte_options),
186
- ...kit({
187
- svelte_config,
188
- adapter: svelte_config.kit.adapter
189
- })
190
- ];
258
+ return [...vite_plugin_svelte.svelte(inline_vps_config), ...kit({ svelte_config })];
191
259
  }
192
260
 
193
- /** @param {import('vite').UserConfig | import('vite').ResolvedConfig} vite_config */
261
+ /** @param {UserConfig | ResolvedConfig} vite_config */
194
262
  function resolve_root(vite_config) {
195
263
  return posixify(vite_config.root ? path.resolve(vite_config.root) : process.cwd());
196
264
  }
197
265
 
198
- /**
199
- * Resolves the Svelte config using the `vite.config.root` setting before any
200
- * of our other plugins try to access the config objects
201
- * @param {{
202
- * vite_plugin_svelte_options: import('@sveltejs/vite-plugin-svelte').Options;
203
- * svelte_config: import('types').ValidatedConfig;
204
- * }} options
205
- * @return {import('vite').Plugin}
206
- */
207
- function plugin_svelte_config({ vite_plugin_svelte_options, svelte_config }) {
208
- return {
209
- name: 'vite-plugin-sveltekit-resolve-svelte-config',
210
- // make sure it runs first
211
- enforce: 'pre',
212
- config: {
213
- order: 'pre',
214
- handler(config) {
215
- root = resolve_root(config);
216
-
217
- /** @type {import('@sveltejs/vite-plugin-svelte').Options['preprocess']} */
218
- let preprocess = svelte_config.preprocess;
219
- if (Array.isArray(preprocess)) {
220
- preprocess = [...preprocess, warning_preprocessor];
221
- } else if (preprocess) {
222
- preprocess = [preprocess, warning_preprocessor];
223
- } else {
224
- preprocess = warning_preprocessor;
225
- }
226
-
227
- vite_plugin_svelte_options.extensions = svelte_config.extensions;
228
- vite_plugin_svelte_options.preprocess = preprocess;
229
- vite_plugin_svelte_options.onwarn = svelte_config.onwarn;
230
- vite_plugin_svelte_options.compilerOptions = { ...svelte_config.compilerOptions };
231
- Object.assign(vite_plugin_svelte_options, svelte_config.vitePlugin);
232
- }
233
- },
234
- // TODO: do we even need to set `root` based on the final Vite config?
235
- configResolved: {
236
- order: 'pre',
237
- handler(config) {
238
- root = resolve_root(config);
239
- }
240
- }
241
- };
242
- }
243
-
244
266
  /**
245
267
  * Returns the SvelteKit Vite plugin. Vite executes Rolldown hooks as well as some of its own.
246
268
  * Background reading is available at:
@@ -252,15 +274,20 @@ function plugin_svelte_config({ vite_plugin_svelte_options, svelte_config }) {
252
274
  * - https://rolldown.rs/apis/plugin-api#output-generation-hooks
253
275
  *
254
276
  * @param {object} opts
255
- * @param {import('types').ValidatedConfig} opts.svelte_config options are only resolved after the Vite `config` hook runs
256
- * @param {import('@sveltejs/kit').Adapter | undefined} opts.adapter
257
- * @return {import('vite').Plugin[]}
277
+ * @param {ValidatedConfig} opts.svelte_config
278
+ * @return {Plugin[]}
258
279
  */
259
- function kit({ svelte_config, adapter }) {
280
+ function kit({ svelte_config }) {
260
281
  /** @type {typeof import('vite')} */
261
282
  let vite;
262
283
 
263
- /** @type {import('types').ValidatedKitConfig} */
284
+ /**
285
+ * The posix-ified root of the project based on the Vite configuration.
286
+ * @type {string}
287
+ */
288
+ let root;
289
+
290
+ /** @type {ValidatedConfig} */
264
291
  let kit;
265
292
  /** @type {string} `kit.outDir` but posix-ified */
266
293
  let out_dir;
@@ -279,45 +306,86 @@ function kit({ svelte_config, adapter }) {
279
306
  /** @type {Record<string, string>} */
280
307
  let env;
281
308
 
282
- /** @type {import('types').ManifestData} */
309
+ /** @type {ManifestData} */
283
310
  let manifest_data;
284
311
 
285
- /** @type {import('types').ServerMetadata | undefined} only set at build time once analysis is finished */
286
- let build_metadata = undefined;
312
+ /** @type {ServerMetadata | null} set at build time once analysis has finished */
313
+ let build_metadata = null;
287
314
 
288
315
  /** @type {UserConfig} */
289
316
  let initial_config;
290
317
 
291
318
  /** @type {string | null} */
292
- let service_worker_entry_file = resolve_entry(svelte_config.kit.files.serviceWorker);
293
- /** @type {import('node:path').ParsedPath} */
294
- let parsed_service_worker;
295
-
319
+ let service_worker_entry_file;
296
320
  /** @type {string} */
297
321
  let normalized_cwd;
298
- /** @type {string} */
299
- let normalized_lib;
322
+ /** @type {Array<{ alias: string, path: string }>} */
323
+ let normalized_aliases;
300
324
  /** @type {string} */
301
325
  let normalized_node_modules;
326
+ /** @type {string} */
327
+ let normalized_routes;
328
+ /** @type {string} */
329
+ let normalized_assets;
302
330
  /**
303
331
  * A map showing which features (such as `$app/server:read`) are defined
304
332
  * in which chunks, so that we can later determine which routes use which features
305
333
  * @type {Record<string, string[]>}
306
334
  */
307
- const tracked_features = {};
335
+ let tracked_features = {};
308
336
 
309
337
  const sourcemapIgnoreList = /** @param {string} relative_path */ (relative_path) =>
310
338
  relative_path.includes('node_modules') || relative_path.includes(kit.outDir);
311
339
 
340
+ /** @type {string} name for `globalThis.__sveltekit_xxx` */
341
+ let kit_global;
342
+
343
+ /** @type {Plugin} */
344
+ const plugin_resolve_root = {
345
+ name: 'vite-plugin-sveltekit-resolve-root',
346
+ // make sure it runs first
347
+ enforce: 'pre',
348
+ config: {
349
+ order: 'pre',
350
+ handler(config) {
351
+ root = resolve_root(config);
352
+
353
+ for (const file of ['svelte.config.js', 'svelte.config.ts']) {
354
+ if (fs.existsSync(path.join(root, file))) {
355
+ throw new Error(
356
+ `${file} is no longer used. Please pass configuration via the \`sveltekit(...)\` plugin in your Vite config.`
357
+ );
358
+ }
359
+ }
360
+ }
361
+ }
362
+ };
363
+
312
364
  /** @type {Plugin} */
313
365
  const plugin_setup = {
314
366
  name: 'vite-plugin-sveltekit-setup',
315
367
  api: {
316
368
  options: svelte_config
317
369
  },
370
+ resolveId: {
371
+ filter: { id: removed_modules.map(({ pattern }) => pattern) },
372
+ async handler(id, importer, options) {
373
+ const resolved = await this.resolve(id, importer, { ...options, skipSelf: true });
374
+ if (resolved) return resolved;
318
375
 
319
- applyToEnvironment(environment) {
320
- return environment.name !== 'serviceWorker';
376
+ const aliases = svelte_config.alias;
377
+ for (const { name, pattern, message } of removed_modules) {
378
+ if (!pattern.test(id)) continue;
379
+
380
+ // If the user re-added an alias for this module (as the migration message
381
+ // suggests), a failed resolution means a genuine missing file rather than
382
+ // use of the removed module. Let Vite report the real "not found" error
383
+ // instead of the misleading migration message.
384
+ if (name in aliases || `${name}/*` in aliases) return;
385
+
386
+ throw stackless(message);
387
+ }
388
+ }
321
389
  },
322
390
 
323
391
  /**
@@ -330,32 +398,56 @@ function kit({ svelte_config, adapter }) {
330
398
  initial_config = config;
331
399
  is_build = config_env.command === 'build';
332
400
 
333
- ({ kit } = svelte_config);
401
+ kit = process_config(svelte_config, root);
334
402
  out_dir = posixify(kit.outDir);
335
403
  out = `${out_dir}/output`;
336
404
 
337
405
  version_hash = hash(kit.version.name);
338
406
 
339
- env = loadEnv(config_env.mode, kit.env.dir, '');
340
-
341
- service_worker_entry_file = resolve_entry(kit.files.serviceWorker);
342
- parsed_service_worker = path.parse(kit.files.serviceWorker);
407
+ kit_global = is_build
408
+ ? `globalThis.__sveltekit_${version_hash}`
409
+ : 'globalThis.__sveltekit_dev';
343
410
 
344
411
  vite = await import_peer('vite', root);
345
412
 
413
+ env = vite.loadEnv(config_env.mode, kit.env.dir, '');
414
+
346
415
  normalized_cwd = vite.normalizePath(root);
347
- normalized_lib = vite.normalizePath(kit.files.lib);
416
+ normalized_aliases = get_import_aliases(root, vite.normalizePath.bind(vite));
348
417
  normalized_node_modules = vite.normalizePath(path.resolve(root, 'node_modules'));
418
+ normalized_routes = vite.normalizePath(path.resolve(root, kit.files.routes));
419
+ normalized_assets = vite.normalizePath(path.resolve(root, kit.files.assets));
420
+
421
+ // Add `#`-prefixed import keys to the enforced config so users are warned
422
+ // if they try to set them in their Vite config's resolve.alias
423
+ const enforced_alias = /** @type {Record<string, true>} */ (
424
+ /** @type {any} */ (enforced_config.resolve).alias
425
+ );
426
+ for (const key of get_hash_import_keys(root)) {
427
+ enforced_alias[key] = true;
428
+ }
349
429
 
350
430
  const allow = new Set([
351
- kit.files.lib,
352
431
  kit.files.routes,
432
+ kit.files.src,
353
433
  kit.outDir,
354
434
  path.resolve(root, kit.files.src),
355
435
  path.resolve(root, 'node_modules'),
356
- path.resolve(process.cwd(), 'node_modules')
436
+ // ensures that the client entry is served even if it's located outside
437
+ // the local node_modules, such as the pnpm global virtual store
438
+ runtime_directory,
439
+ path.resolve('node_modules'),
440
+ // include the directory that contains the workspaces declaration
441
+ // which usually also contains hoisted packages
442
+ // see https://vite.dev/guide/api-javascript#searchforworkspaceroot
443
+ path.resolve(vite.searchForWorkspaceRoot(process.cwd()), 'node_modules')
357
444
  ]);
358
445
 
446
+ // Add directories from `#`-prefixed package.json imports to the allow list
447
+ for (const { path: alias_path } of normalized_aliases) {
448
+ allow.add(alias_path);
449
+ }
450
+
359
451
  // We can only add directories to the allow list, so we find out
360
452
  // if there's a client hooks file and pass its directory
361
453
  const client_hooks = resolve_entry(kit.files.hooks.client);
@@ -366,10 +458,23 @@ function kit({ svelte_config, adapter }) {
366
458
  // dev and preview config can be shared
367
459
  /** @type {UserConfig} */
368
460
  const new_config = {
461
+ environments: {
462
+ ssr: {
463
+ build: {
464
+ sourcemap:
465
+ config.environments?.ssr?.build?.sourcemap ?? config.build?.sourcemap ?? true
466
+ }
467
+ }
468
+ },
369
469
  resolve: {
370
470
  alias: [
371
471
  { find: '__SERVER__', replacement: `${generated}/server` },
372
472
  { find: '$app', replacement: `${runtime_directory}/app` },
473
+ { find: '$env', replacement: `${runtime_directory}/env` },
474
+ {
475
+ find: '__sveltekit/server',
476
+ replacement: `${runtime_directory}/server/internal.js`
477
+ },
373
478
  ...get_config_aliases(kit, root)
374
479
  ]
375
480
  },
@@ -381,7 +486,7 @@ function kit({ svelte_config, adapter }) {
381
486
  sourcemapIgnoreList,
382
487
  watch: {
383
488
  ignored: [
384
- // Ignore all siblings of config.kit.outDir/generated
489
+ // Ignore all siblings of config.outDir/generated
385
490
  `${out_dir}/!(generated)`
386
491
  ]
387
492
  }
@@ -402,7 +507,9 @@ function kit({ svelte_config, adapter }) {
402
507
  // this does not affect app code, just handling of imported libraries that use $app or $env
403
508
  '$app',
404
509
  '$env'
405
- ]
510
+ ],
511
+ // avoid Vite dev server reloading the first time a page is requested
512
+ include: ['@sveltejs/kit > devalue', '@sveltejs/kit > esm-env']
406
513
  },
407
514
  ssr: {
408
515
  noExternal: [
@@ -421,30 +528,34 @@ function kit({ svelte_config, adapter }) {
421
528
  }
422
529
  };
423
530
 
531
+ // externalize .remote.js files to stop dependency tracing during prebundling
424
532
  if (kit.experimental.remoteFunctions) {
425
- // treat .remote.js files as empty for the purposes of prebundling
426
- const remote_id_filter = new RegExp(
427
- `.remote(${kit.moduleExtensions.join('|')})$`.replaceAll('.', '\\.')
428
- );
429
533
  // @ts-expect-error optimizeDeps is already set above
430
534
  new_config.optimizeDeps.rolldownOptions ??= {};
431
535
  // @ts-expect-error
432
536
  new_config.optimizeDeps.rolldownOptions.plugins ??= [];
433
537
  // @ts-expect-error
434
- new_config.optimizeDeps.rolldownOptions.plugins.push({
435
- name: 'vite-plugin-sveltekit-setup:optimize-remote-functions',
436
- load: {
437
- filter: { id: remote_id_filter },
438
- handler() {
439
- return '';
538
+ new_config.optimizeDeps.rolldownOptions.plugins.push(
539
+ /** @type {Rolldown.Plugin} */ ({
540
+ name: 'vite-plugin-sveltekit-setup:optimize-remote-functions',
541
+ resolveId: {
542
+ filter: { id: remote_module_pattern },
543
+ async handler(id, importer) {
544
+ const resolved = await this.resolve(id, importer, { skipSelf: true });
545
+ if (!resolved) return { id, external: true };
546
+ if (!is_remote_module(resolved.id)) return;
547
+ // a servable /@fs url; 'absolute' stops rolldown relativizing it in the deps bundle
548
+ return { id: to_fs(resolved.id), external: 'absolute' };
549
+ }
440
550
  }
441
- }
442
- });
551
+ })
552
+ );
443
553
  }
444
554
 
445
555
  const define = {
446
- __SVELTEKIT_APP_DIR__: s(kit.appDir),
556
+ __SVELTEKIT_APP_DIR__: s(posixify(kit.appDir)),
447
557
  __SVELTEKIT_APP_VERSION__: s(kit.version.name),
558
+ __SVELTEKIT_APP_VERSION_CHECKS_ENABLED__: s(kit.output.bundleStrategy !== 'inline'),
448
559
  __SVELTEKIT_EMBEDDED__: s(kit.embedded),
449
560
  __SVELTEKIT_FORK_PRELOADS__: s(kit.experimental.forkPreloads),
450
561
  __SVELTEKIT_PATHS_ASSETS__: s(kit.paths.assets),
@@ -452,16 +563,17 @@ function kit({ svelte_config, adapter }) {
452
563
  __SVELTEKIT_PATHS_RELATIVE__: s(kit.paths.relative),
453
564
  __SVELTEKIT_CLIENT_ROUTING__: s(kit.router.resolution === 'client'),
454
565
  __SVELTEKIT_HASH_ROUTING__: s(kit.router.type === 'hash'),
455
- __SVELTEKIT_SERVER_TRACING_ENABLED__: s(kit.experimental.tracing.server),
456
- __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__: s(kit.experimental.handleRenderingErrors),
457
- __SVELTEKIT_ROOT__: s(root),
566
+ __SVELTEKIT_SERVER_TRACING_ENABLED__: s(kit.tracing.server),
567
+ __SVELTEKIT_SUPPORTS_ASYNC__: s(
568
+ svelte_config.compilerOptions?.experimental?.async ?? false
569
+ ),
458
570
  __SVELTEKIT_DEV__: s(!is_build)
459
571
  };
460
572
 
461
573
  if (is_build) {
462
574
  new_config.define = {
463
575
  ...define,
464
- __SVELTEKIT_ADAPTER_NAME__: s(adapter?.name),
576
+ __SVELTEKIT_ADAPTER_NAME__: s(kit.adapter?.name),
465
577
  __SVELTEKIT_APP_VERSION_FILE__: s(`${kit.appDir}/version.json`),
466
578
  __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: s(kit.version.pollInterval)
467
579
  };
@@ -471,18 +583,44 @@ function kit({ svelte_config, adapter }) {
471
583
  new_config.define = {
472
584
  ...define,
473
585
  __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0',
474
- __SVELTEKIT_PAYLOAD__: 'globalThis.__sveltekit_dev',
586
+ __SVELTEKIT_PAYLOAD__: kit_global, // only relevant when bundleStrategy !== 'split'
475
587
  __SVELTEKIT_HAS_SERVER_LOAD__: 'true',
476
588
  __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true'
477
589
  };
478
590
 
479
591
  // These Kit dependencies are packaged as CommonJS, which means they must always be externalized.
480
592
  // Without this, the tests will still pass but `pnpm dev` will fail in projects that link `@sveltejs/kit`.
481
- /** @type {NonNullable<import('vite').UserConfig['ssr']>} */ (new_config.ssr).external = [
482
- 'cookie'
593
+ //
594
+ // `@opentelemetry/api` must be externalized so that `instrumentation.server.js` and the
595
+ // SvelteKit runtime share a single instance of the module (the global tracer/propagation
596
+ // is set on that instance — two bundled copies would mean instrumentation hooks are
597
+ // invisible to the runtime). Externalizing also prevents the bundler from colocating
598
+ // `@opentelemetry/api` into a shared chunk that also contains application modules, which
599
+ // would cause those modules to be evaluated before `Server.init()` sets env vars — see
600
+ // https://github.com/sveltejs/kit/issues/16288
601
+ /** @type {NonNullable<UserConfig['ssr']>} */ (new_config.ssr).external = [
602
+ 'cookie',
603
+ '@opentelemetry/api'
483
604
  ];
484
605
  }
485
606
 
607
+ // Vite's `define` is a compile-time text replacement, but Vitest strips
608
+ // user `define` from the server config and reinstalls the values only as
609
+ // `globalThis` properties inside test workers, so anything
610
+ // that runs outside of a test will freak out over
611
+ // them not being defined
612
+ if (process.env.VITEST === 'true') {
613
+ for (const key in new_config.define) {
614
+ const value = new_config.define[key];
615
+ try {
616
+ /** @type {Record<string, any>} */ (globalThis)[key] = JSON.parse(value);
617
+ } catch {
618
+ // `kit_global` isn't JSON, so don't try to parse it. We may one day
619
+ // need to define it in Vitest somehow but for now, ignore it
620
+ }
621
+ }
622
+ }
623
+
486
624
  warn_overridden_config(config, new_config);
487
625
 
488
626
  return new_config;
@@ -494,6 +632,24 @@ function kit({ svelte_config, adapter }) {
494
632
  */
495
633
  configResolved(config) {
496
634
  vite_config = config;
635
+
636
+ const unsupported_plugins = vite_config.plugins.filter((plugin) => plugin.transformIndexHtml);
637
+ if (unsupported_plugins.length) {
638
+ const verbose = vite_config.logLevel === 'info';
639
+ const log = logger({ verbose });
640
+
641
+ const list = unsupported_plugins
642
+ .map((plugin) => ` - ${plugin.name || '(missing plugin name)'}`)
643
+ .join('\n');
644
+
645
+ log.warn(
646
+ dedent`
647
+ The following plugins may not work correctly because they use the \`transformIndexHtml\` hook which is not supported:
648
+
649
+ ${list}
650
+ `
651
+ );
652
+ }
497
653
  }
498
654
  };
499
655
 
@@ -507,13 +663,9 @@ function kit({ svelte_config, adapter }) {
507
663
  const plugin_virtual_modules = {
508
664
  name: 'vite-plugin-sveltekit-virtual-modules',
509
665
 
510
- applyToEnvironment(environment) {
511
- return environment.name !== 'serviceWorker';
512
- },
513
-
514
666
  async configResolved(config) {
515
667
  explicit_env_entry = resolve_explicit_env_entry(kit);
516
- explicit_env_config = await sync.env(kit, explicit_env_entry, config.root, config.mode);
668
+ explicit_env_config = await sync.env(vite, kit, explicit_env_entry, config.root, config.mode);
517
669
  },
518
670
 
519
671
  configureServer(server) {
@@ -527,6 +679,7 @@ function kit({ svelte_config, adapter }) {
527
679
  if (file === explicit_env_entry || file === resolved) {
528
680
  explicit_env_entry = resolved;
529
681
  explicit_env_config = await sync.env(
682
+ vite,
530
683
  kit,
531
684
  explicit_env_entry,
532
685
  vite_config.root,
@@ -534,93 +687,59 @@ function kit({ svelte_config, adapter }) {
534
687
  );
535
688
 
536
689
  for (const id of [sveltekit_env, sveltekit_env_public_client]) {
537
- const module = server.moduleGraph.getModuleById(id);
538
-
539
- if (module) {
540
- server.moduleGraph.invalidateModule(module);
541
- }
690
+ invalidate_module(server, id);
542
691
  }
543
692
 
544
- server.ws.send({ type: 'full-reload' });
693
+ server.hot.send({ type: 'full-reload' });
545
694
  }
546
695
  });
547
696
  },
548
697
 
549
- resolveId(id, importer) {
550
- if (id === '__sveltekit/manifest') {
551
- return `${out_dir}/generated/client-optimized/app.js`;
552
- }
698
+ applyToEnvironment(environment) {
699
+ return environment.name !== 'serviceWorker';
700
+ },
553
701
 
554
- // If importing from a service-worker, only allow $service-worker & $app/env/public, but none of the other virtual modules.
555
- // This check won't catch transitive imports, but it will warn when the import comes from a service-worker directly.
556
- // Transitive imports will be caught during the build.
557
- // TODO move this logic to plugin_guard. add a filter to this resolveId when doing so
558
- // TODO allow $app/env/public
559
- if (importer) {
560
- const parsed_importer = path.parse(importer);
561
-
562
- const importer_is_service_worker =
563
- parsed_importer.dir === parsed_service_worker.dir &&
564
- parsed_importer.name === parsed_service_worker.name;
565
-
566
- if (
567
- importer_is_service_worker &&
568
- id !== '$service-worker' &&
569
- id !== 'virtual:$app/env/public' &&
570
- id !== '__sveltekit/env/service-worker'
571
- ) {
572
- throw new Error(
573
- `Cannot import ${normalize_id(
574
- id,
575
- normalized_lib,
576
- normalized_cwd
577
- )} into service-worker code. Only the modules $service-worker and $app/env/public are available in service workers.`
578
- );
702
+ resolveId: {
703
+ filter: {
704
+ id: prefixRegex('__sveltekit/')
705
+ },
706
+ handler(id) {
707
+ if (id === '__sveltekit/manifest') {
708
+ return `${out_dir}/generated/client-optimized/app.js`;
579
709
  }
580
- }
581
710
 
582
- if (id === '$service-worker') {
583
- // ids with :$ don't work with reverse proxies like nginx
584
- return `\0virtual:${id.substring(1)}`;
585
- }
586
-
587
- if (id === '__sveltekit/remote') {
588
- return `${runtime_directory}/client/remote-functions/index.js`;
589
- }
711
+ if (id === '__sveltekit/remote') {
712
+ return `${runtime_directory}/client/remote-functions/index.js`;
713
+ }
590
714
 
591
- if (id.startsWith('__sveltekit/')) {
592
715
  return `\0virtual:${id}`;
593
716
  }
594
717
  },
718
+
595
719
  load: {
596
720
  filter: {
597
721
  id: [
598
- exactRegex(service_worker),
599
722
  exactRegex(sveltekit_env),
600
723
  exactRegex(sveltekit_env_private),
601
724
  exactRegex(sveltekit_env_public_client),
602
725
  exactRegex(sveltekit_env_public_server),
603
726
  exactRegex(sveltekit_env_service_worker),
604
- exactRegex(sveltekit_server)
727
+ exactRegex(sveltekit_manifest_data)
605
728
  ]
606
729
  },
607
730
  handler(id) {
608
- const global = is_build
609
- ? `globalThis.__sveltekit_${version_hash}`
610
- : 'globalThis.__sveltekit_dev';
611
-
612
731
  switch (id) {
613
- case service_worker:
614
- return create_service_worker_module(svelte_config);
732
+ case sveltekit_manifest_data:
733
+ return create_manifest_data_module(is_build, manifest_data);
615
734
 
616
735
  case sveltekit_env:
617
- return create_sveltekit_env(explicit_env_config, env, explicit_env_entry);
736
+ return create_sveltekit_env(explicit_env_config, env, explicit_env_entry, !is_build);
618
737
 
619
738
  case sveltekit_env_public_client:
620
739
  return create_sveltekit_env_public(
621
740
  explicit_env_config,
622
741
  env,
623
- `const env = ${global}.env;`
742
+ `import { payload } from ${s(`${runtime_directory}/client/payload.js`)};\nconst env = payload.env;`
624
743
  );
625
744
 
626
745
  case sveltekit_env_public_server:
@@ -634,23 +753,21 @@ function kit({ svelte_config, adapter }) {
634
753
  return create_sveltekit_env_private(explicit_env_config, env);
635
754
 
636
755
  case sveltekit_env_service_worker:
637
- return create_sveltekit_env_service_worker_dev(explicit_env_config, env, global);
638
-
639
- case sveltekit_server: {
640
- return dedent`
641
- export let read_implementation = null;
642
-
643
- export let manifest = null;
644
-
645
- export function set_read_implementation(fn) {
646
- read_implementation = fn;
647
- }
648
-
649
- export function set_manifest(_) {
650
- manifest = _;
651
- }
652
- `;
653
- }
756
+ return is_build
757
+ ? create_sveltekit_env_service_worker(
758
+ explicit_env_config,
759
+ env,
760
+ kit.version.name,
761
+ kit_global,
762
+ kit.paths.base,
763
+ kit.appDir
764
+ )
765
+ : create_sveltekit_env_service_worker_dev(
766
+ explicit_env_config,
767
+ env,
768
+ kit.version.name,
769
+ kit_global
770
+ );
654
771
  }
655
772
  }
656
773
  }
@@ -658,11 +775,10 @@ function kit({ svelte_config, adapter }) {
658
775
 
659
776
  /** @type {Map<string, Set<string>>} */
660
777
  const import_map = new Map();
661
- const server_only_pattern = /.*\.server\..+/;
662
778
 
663
779
  /**
664
780
  * Ensures that client-side code can't accidentally import server-side code,
665
- * whether in `*.server.js` files, `$app/server`, `$lib/server`, or `$app/env/private`
781
+ * whether in `*.server.js` files, `$app/server`, any `/server/` directory, or `$app/env/private`
666
782
  * @type {Plugin}
667
783
  */
668
784
  const plugin_guard = {
@@ -673,22 +789,26 @@ function kit({ svelte_config, adapter }) {
673
789
  enforce: 'pre',
674
790
 
675
791
  applyToEnvironment(environment) {
676
- return environment.name !== 'serviceWorker';
792
+ // the import map is only read for client-side violations in `load`, so skip other environments
793
+ return environment.config.consumer === 'client';
677
794
  },
678
795
 
679
796
  resolveId: {
680
- // TODO: use composable filter API here when supported:
681
- // https://github.com/vitejs/rolldown-vite/issues/605
682
- // filter: ([
683
- // exclude(importerId(/index\.html$/)),
684
- // include(importerId(/.+/))
685
- // ]),
797
+ // composable filters are not accepted type-wise but still work during build
798
+ // see https://github.com/vitejs/rolldown-vite/issues/605
799
+ filter: /** @type {any} */ (
800
+ /** @satisfies {TopLevelFilterExpression[]} */ ([
801
+ include(and(importerId(/.+/), not(importerId(/index\.html$/))))
802
+ ])
803
+ ),
686
804
  async handler(id, importer, options) {
805
+ // composable filters only work during build so we still need this guard for dev
806
+ // see https://github.com/vitejs/rolldown-vite/issues/605
687
807
  if (importer && !importer.endsWith('index.html')) {
688
808
  const resolved = await this.resolve(id, importer, { ...options, skipSelf: true });
689
809
 
690
810
  if (resolved) {
691
- const normalized = normalize_id(resolved.id, normalized_lib, normalized_cwd);
811
+ const normalized = normalize_id(resolved.id, normalized_aliases, normalized_cwd);
692
812
 
693
813
  let importers = import_map.get(normalized);
694
814
 
@@ -697,7 +817,7 @@ function kit({ svelte_config, adapter }) {
697
817
  import_map.set(normalized, importers);
698
818
  }
699
819
 
700
- importers.add(normalize_id(importer, normalized_lib, normalized_cwd));
820
+ importers.add(normalize_id(importer, normalized_aliases, normalized_cwd));
701
821
  }
702
822
  }
703
823
  }
@@ -708,31 +828,29 @@ function kit({ svelte_config, adapter }) {
708
828
  id: [
709
829
  exactRegex(app_server),
710
830
  exactRegex(app_env_private),
711
- /\/server\//,
712
- new RegExp(`${server_only_pattern.source}$`)
831
+ server_only_module_pattern,
832
+ server_only_directory_pattern
713
833
  ]
714
834
  },
715
835
  handler(id) {
716
- if (this.environment.config.consumer !== 'client') return;
717
-
718
- // skip .server.js files outside the cwd or in node_modules, as the filename might not mean 'server-only module' in this context
719
- const is_internal =
720
- id.startsWith(normalized_cwd) && !id.startsWith(normalized_node_modules);
721
-
722
- const normalized = normalize_id(id, normalized_lib, normalized_cwd);
836
+ const normalized = normalize_id(id, normalized_aliases, normalized_cwd);
723
837
 
724
- const is_server_only =
725
- normalized === '$app/env/private' ||
726
- normalized === '$app/server' ||
727
- normalized.startsWith('$lib/server/') ||
728
- (is_internal && server_only_pattern.test(path.basename(id)));
838
+ let is_server_only = normalized === '$app/env/private' || normalized === '$app/server';
729
839
 
730
840
  // skip .server.js files outside the cwd or in node_modules, as the filename might not mean 'server-only module' in this context
731
- // TODO: address https://github.com/sveltejs/kit/issues/12529
732
- if (!is_server_only) {
733
- return;
841
+ if (id.startsWith(normalized_cwd) && !id.startsWith(normalized_node_modules)) {
842
+ // e.g. `server.ts` or `foo.server.ts`
843
+ is_server_only ||= server_only_module_pattern.test(id);
844
+
845
+ // e.g. `server/foo.ts`, unless in `src/routes` or `static`
846
+ is_server_only ||=
847
+ server_only_directory_pattern.test(id) &&
848
+ !id.startsWith(normalized_routes + '/') &&
849
+ !id.startsWith(normalized_assets + '/');
734
850
  }
735
851
 
852
+ if (!is_server_only) return;
853
+
736
854
  // in dev, this doesn't exist, so we need to create it
737
855
  manifest_data ??= sync.all(svelte_config, root).manifest_data;
738
856
 
@@ -746,58 +864,78 @@ function kit({ svelte_config, adapter }) {
746
864
  if (manifest_data.hooks.client) entrypoints.add(manifest_data.hooks.client);
747
865
  if (manifest_data.hooks.universal) entrypoints.add(manifest_data.hooks.universal);
748
866
 
749
- const chain = [normalized];
750
-
751
- let current = normalized;
752
- let includes_remote_file = false;
867
+ if (service_worker_entry_file) {
868
+ entrypoints.add(posixify(path.relative(root, service_worker_entry_file)));
869
+ }
753
870
 
754
- while (true) {
871
+ // Walk up the import graph from the server-only module, looking for a chain
872
+ // that leads back to a client entrypoint. We search all candidates (not just
873
+ // the first) because a module can be imported by both server and client code,
874
+ // and a greedy first-match could follow a server-only branch that never
875
+ // reaches an entrypoint — see https://github.com/sveltejs/kit/issues/16232
876
+ /** @type {Set<string>} */
877
+ const visited = new Set([normalized]);
878
+
879
+ /**
880
+ * @param {string} current
881
+ * @param {string[]} chain
882
+ * @returns {string[] | null}
883
+ */
884
+ function find_chain(current, chain) {
755
885
  const importers = import_map.get(current);
756
- if (!importers) break;
757
-
758
- const candidates = Array.from(importers).filter((importer) => !chain.includes(importer));
759
- if (candidates.length === 0) break;
886
+ if (!importers) return null;
760
887
 
761
- chain.push((current = candidates[0]));
762
-
763
- includes_remote_file ||= svelte_config.kit.moduleExtensions.some((ext) => {
764
- return current.endsWith(`.remote${ext}`);
765
- });
888
+ for (const importer of importers) {
889
+ if (visited.has(importer)) continue;
890
+ visited.add(importer);
766
891
 
767
- if (entrypoints.has(current)) {
768
- const pyramid = chain
769
- .reverse()
770
- .map((id, i) => {
771
- return `${' '.repeat(i + 1)}${id}`;
772
- })
773
- .join(' imports\n');
774
-
775
- if (includes_remote_file) {
776
- error_for_missing_config(
777
- 'remote functions',
778
- 'kit.experimental.remoteFunctions',
779
- 'true'
780
- );
892
+ const next_chain = [...chain, importer];
893
+ if (entrypoints.has(importer)) {
894
+ return next_chain;
781
895
  }
896
+ const result = find_chain(importer, next_chain);
897
+ if (result) return result;
898
+ }
899
+ return null;
900
+ }
782
901
 
783
- let message = `Cannot import ${normalized} into code that runs in the browser, as this could leak sensitive information.`;
784
- message += `\n\n${pyramid}`;
785
- message += `\n\nIf you're only using the import as a type, change it to \`import type\`.`;
902
+ const chain = find_chain(normalized, [normalized]);
786
903
 
787
- throw stackless(message);
904
+ if (chain) {
905
+ if (chain.some((id) => remote_module_pattern.test(id))) {
906
+ error_for_missing_config('remote functions', 'experimental.remoteFunctions', 'true');
788
907
  }
908
+
909
+ const pyramid = chain
910
+ .reverse()
911
+ .map((id, i) => {
912
+ return `${' '.repeat(i + 1)}${id}`;
913
+ })
914
+ .join(' imports\n');
915
+
916
+ let message = `Cannot import ${normalized} into code that runs in the browser, as this could leak sensitive information.`;
917
+ message += `\n\n${pyramid}`;
918
+ message += `\n\nIf you're only using the import as a type, change it to \`import type\`.`;
919
+
920
+ throw stackless(message);
789
921
  }
790
922
 
791
- throw new Error('An impossible situation occurred');
923
+ // No chain from this server-only module to a client entrypoint was found —
924
+ // the module is only imported from server code, which is valid.
792
925
  }
926
+ },
927
+
928
+ // avoid watch mode rebuilds using stale import map data
929
+ buildEnd() {
930
+ import_map.clear();
793
931
  }
794
932
  };
795
933
 
796
934
  /** @type {ViteDevServer} */
797
935
  let dev_server;
798
936
 
799
- /** @type {Array<{ hash: string, file: string }>} */
800
- const remotes = [];
937
+ /** @type {RemoteChunk[]} */
938
+ let remotes = [];
801
939
 
802
940
  /** @type {Map<string, string>} Maps remote hash -> original module id */
803
941
  const remote_original_by_hash = new Map();
@@ -810,7 +948,7 @@ function kit({ svelte_config, adapter }) {
810
948
  name: 'vite-plugin-sveltekit-remote',
811
949
 
812
950
  applyToEnvironment(environment) {
813
- return environment.name !== 'serviceWorker';
951
+ return svelte_config.experimental.remoteFunctions && environment.name !== 'serviceWorker';
814
952
  },
815
953
 
816
954
  // prevent other plugins from resolving our remote virtual module
@@ -828,10 +966,6 @@ function kit({ svelte_config, adapter }) {
828
966
  id: prefixRegex('\0sveltekit-remote:')
829
967
  },
830
968
  handler(id) {
831
- if (!kit.experimental.remoteFunctions) {
832
- return null;
833
- }
834
-
835
969
  // On-the-fly generated entry point for remote file just forwards the original module
836
970
  // We're not using manualChunks because it can cause problems with circular dependencies
837
971
  // (e.g. https://github.com/sveltejs/kit/issues/14679) and module ordering in general
@@ -844,234 +978,351 @@ function kit({ svelte_config, adapter }) {
844
978
  },
845
979
 
846
980
  configureServer(_dev_server) {
847
- if (!kit.experimental.remoteFunctions) {
848
- return;
849
- }
850
-
851
981
  dev_server = _dev_server;
852
982
  },
853
983
 
854
- async transform(code, id) {
855
- if (!kit.experimental.remoteFunctions) {
856
- return;
857
- }
984
+ transform: {
985
+ filter: {
986
+ id: remote_module_pattern
987
+ },
988
+ async handler(code, id) {
989
+ if (!is_remote_module(id)) return;
858
990
 
859
- const normalized = normalize_id(id, normalized_lib, normalized_cwd);
860
- if (!svelte_config.kit.moduleExtensions.some((ext) => normalized.endsWith(`.remote${ext}`))) {
861
- return;
862
- }
991
+ const file = posixify(path.relative(root, id));
992
+ const remote = {
993
+ hash: hash(file),
994
+ file
995
+ };
863
996
 
864
- const file = posixify(path.relative(root, id));
865
- const remote = {
866
- hash: hash(file),
867
- file
868
- };
997
+ if (this.environment.name === 'ssr') remotes.push(remote);
869
998
 
870
- remotes.push(remote);
999
+ if (this.environment.config.consumer !== 'client') {
1000
+ // we need to add an `await Promise.resolve()` because if the user imports this function
1001
+ // on the client AND in a load function when loading the client module we will trigger
1002
+ // an import during dev. During a link preload, the module can be mistakenly
1003
+ // loaded and transformed twice and the first time all its exports would be undefined
1004
+ // triggering a dev server error. By adding a microtask we ensure that the module is fully loaded
1005
+ const ms = new MagicString(code);
871
1006
 
872
- if (this.environment.config.consumer !== 'client') {
873
- // we need to add an `await Promise.resolve()` because if the user imports this function
874
- // on the client AND in a load function when loading the client module we will trigger
875
- // an ssrLoadModule during dev. During a link preload, the module can be mistakenly
876
- // loaded and transformed twice and the first time all its exports would be undefined
877
- // triggering a dev server error. By adding a microtask we ensure that the module is fully loaded
1007
+ // Extra newlines to prevent syntax errors around missing semicolons or comments
1008
+ ms.append(
1009
+ '\n\n' +
1010
+ dedent`
1011
+ import * as $$_self_$$ from './${path.basename(id)}';
1012
+ import { init_remote_functions as $$_init_$$ } from '@sveltejs/kit/internal/server';
878
1013
 
879
- // Extra newlines to prevent syntax errors around missing semicolons or comments
880
- code +=
881
- '\n\n' +
882
- dedent`
883
- import * as $$_self_$$ from './${path.basename(id)}';
884
- import { init_remote_functions as $$_init_$$ } from '@sveltejs/kit/internal';
1014
+ ${dev_server ? 'await Promise.resolve()' : ''}
885
1015
 
886
- ${dev_server ? 'await Promise.resolve()' : ''}
1016
+ $$_init_$$($$_self_$$, ${s(file)}, ${s(remote.hash)});
887
1017
 
888
- $$_init_$$($$_self_$$, ${s(file)}, ${s(remote.hash)});
1018
+ for (const [name, fn] of Object.entries($$_self_$$)) {
1019
+ fn.__.id = ${s(remote.hash)} + '/' + name;
1020
+ fn.__.name = name;
1021
+ }
1022
+ `
1023
+ );
889
1024
 
890
- for (const [name, fn] of Object.entries($$_self_$$)) {
891
- fn.__.id = ${s(remote.hash)} + '/' + name;
892
- fn.__.name = name;
893
- }
894
- `;
895
-
896
- // Emit a dedicated entry chunk for this remote in SSR builds (prod only)
897
- if (!dev_server) {
898
- remote_original_by_hash.set(remote.hash, id);
899
- if (!emitted_remote_hashes.has(remote.hash)) {
900
- this.emitFile({
901
- type: 'chunk',
902
- id: `\0sveltekit-remote:${remote.hash}`,
903
- name: `remote-${remote.hash}`
904
- });
905
- emitted_remote_hashes.add(remote.hash);
1025
+ // Emit a dedicated entry chunk for this remote in SSR builds (prod only)
1026
+ if (!dev_server) {
1027
+ remote_original_by_hash.set(remote.hash, id);
1028
+ if (!emitted_remote_hashes.has(remote.hash)) {
1029
+ this.emitFile({
1030
+ type: 'chunk',
1031
+ id: `\0sveltekit-remote:${remote.hash}`,
1032
+ name: `remote-${remote.hash}`
1033
+ });
1034
+ emitted_remote_hashes.add(remote.hash);
1035
+ }
906
1036
  }
1037
+
1038
+ return {
1039
+ code: ms.toString(),
1040
+ map: ms.generateMap({ hires: 'boundary' })
1041
+ };
907
1042
  }
908
1043
 
909
- return code;
910
- }
1044
+ // For the client, read the exports and create a new module that only contains fetch functions with the correct metadata
1045
+
1046
+ /** @type {Map<string, RemoteInternals['type']>} */
1047
+ const map = new Map();
911
1048
 
912
- // For the client, read the exports and create a new module that only contains fetch functions with the correct metadata
1049
+ // in dev, load the server module here (which will result in this hook
1050
+ // being called again with `opts.ssr === true` if the module isn't
1051
+ // already loaded) so we can determine what it exports
1052
+ if (dev_server) {
1053
+ const module = await get_runner(vite, dev_server).import(id);
913
1054
 
914
- /** @type {Map<string, import('types').RemoteInternals['type']>} */
915
- const map = new Map();
1055
+ for (const [name, value] of Object.entries(module)) {
1056
+ const type = value?.__?.type;
1057
+ if (type) {
1058
+ map.set(name, type);
1059
+ }
1060
+ }
1061
+ }
916
1062
 
917
- // in dev, load the server module here (which will result in this hook
918
- // being called again with `opts.ssr === true` if the module isn't
919
- // already loaded) so we can determine what it exports
920
- if (dev_server) {
921
- const module = await dev_server.ssrLoadModule(id);
1063
+ // in prod, we already built and analysed the server code before
1064
+ // building the client code, so `remotes` is populated
1065
+ else if (build_metadata?.remotes) {
1066
+ const exports = build_metadata?.remotes.get(remote.hash);
1067
+ if (!exports) throw new Error('Expected to find metadata for remote file ' + id);
922
1068
 
923
- for (const [name, value] of Object.entries(module)) {
924
- const type = value?.__?.type;
925
- if (type) {
926
- map.set(name, type);
1069
+ for (const [name, value] of exports) {
1070
+ map.set(name, value.type);
927
1071
  }
928
1072
  }
929
- }
930
1073
 
931
- // in prod, we already built and analysed the server code before
932
- // building the client code, so `remotes` is populated
933
- else if (build_metadata?.remotes) {
934
- const exports = build_metadata?.remotes.get(remote.hash);
935
- if (!exports) throw new Error('Expected to find metadata for remote file ' + id);
1074
+ const { namespace, declarations, reexports } = create_exported_declarations(
1075
+ map.keys(),
1076
+ (name, ns) => `${ns}.${map.get(name)}('${remote.hash}/${name}')`,
1077
+ '__remote'
1078
+ );
1079
+
1080
+ let result = `import * as ${namespace} from '__sveltekit/remote';\n\n${declarations.join('\n')}`;
1081
+ if (reexports.length > 0) {
1082
+ result += `\nexport { ${reexports.join(', ')} };`;
1083
+ }
1084
+ result += '\n';
936
1085
 
937
- for (const [name, value] of exports) {
938
- map.set(name, value.type);
1086
+ if (dev_server) {
1087
+ result += `\nimport.meta.hot?.accept();\n`;
939
1088
  }
1089
+
1090
+ return {
1091
+ code: result,
1092
+ map: null
1093
+ };
940
1094
  }
1095
+ },
941
1096
 
942
- let namespace = '__remote';
943
- let uid = 1;
944
- while (map.has(namespace)) namespace = `__remote${uid++}`;
1097
+ buildEnd() {
1098
+ if (this.environment.config.consumer === 'server') {
1099
+ emitted_remote_hashes.clear();
1100
+ }
1101
+ }
1102
+ };
945
1103
 
946
- const exports = Array.from(map).map(([name, type]) => {
947
- return `export const ${name} = ${namespace}.${type}('${remote.hash}/${name}');`;
948
- });
1104
+ /** @type {Plugin} */
1105
+ const plugin_remote_guard = {
1106
+ name: 'vite-plugin-sveltekit-remote-guard',
949
1107
 
950
- let result = `import * as ${namespace} from '__sveltekit/remote';\n\n${exports.join('\n')}\n`;
1108
+ applyToEnvironment() {
1109
+ return !svelte_config.experimental.remoteFunctions;
1110
+ },
951
1111
 
952
- if (dev_server) {
953
- result += `\nimport.meta.hot?.accept();\n`;
1112
+ transform: {
1113
+ filter: {
1114
+ id: new RegExp(
1115
+ `.remote(${svelte_config.moduleExtensions.join('|')})$`.replaceAll('.', '\\.')
1116
+ )
1117
+ },
1118
+ handler() {
1119
+ error_for_missing_config('remote functions', 'experimental.remoteFunctions', 'true');
954
1120
  }
955
-
956
- return {
957
- code: result
958
- };
959
1121
  }
960
1122
  };
961
1123
 
962
- /** @type {import('vite').Manifest} */
963
- let client_manifest;
964
- /** @type {import('types').Prerendered} */
1124
+ /** @type {Manifest} */
1125
+ let vite_server_manifest;
1126
+ /** @type {Manifest | null} */
1127
+ let vite_client_manifest = null;
1128
+ /** @type {Prerendered} */
965
1129
  let prerendered;
966
1130
 
967
- /** @type {Set<string>} */
968
- let build;
969
- /** @type {string} */
970
- let service_worker_code;
1131
+ /** @type {Array<{ path: string }> | null} */
1132
+ let immutable = null;
1133
+ /** @type {string | null} */
1134
+ let manifest_data_code = null;
971
1135
 
972
1136
  /**
973
1137
  * Creates the service worker virtual modules
974
- * @type {import('vite').Plugin}
1138
+ * @type {Plugin}
975
1139
  */
976
1140
  const plugin_service_worker = {
977
1141
  name: 'vite-plugin-sveltekit-service-worker',
978
1142
 
1143
+ config(config) {
1144
+ service_worker_entry_file = resolve_entry(kit.files.serviceWorker);
1145
+
1146
+ if (!service_worker_entry_file) return;
1147
+
1148
+ service_worker_entry_file = posixify(service_worker_entry_file);
1149
+
1150
+ if (kit.paths.assets) {
1151
+ throw new Error('Cannot use service worker alongside config.paths.assets');
1152
+ }
1153
+
1154
+ const user_service_worker_output_config =
1155
+ config.environments?.serviceWorker?.build?.rolldownOptions?.output;
1156
+
1157
+ /** @type {import('vite').UserConfig} */
1158
+ const new_config = {
1159
+ environments: {
1160
+ serviceWorker: {
1161
+ build: {
1162
+ modulePreload: false,
1163
+ rolldownOptions: {
1164
+ external: [`${kit.paths.base}/${kit.appDir}/env.js`],
1165
+ input: {
1166
+ 'service-worker': service_worker_entry_file
1167
+ },
1168
+ output: {
1169
+ format: 'es',
1170
+ entryFileNames: 'service-worker.js',
1171
+ assetFileNames: `${kit.appDir}/immutable/assets/[name].[hash][extname]`,
1172
+ codeSplitting:
1173
+ (Array.isArray(user_service_worker_output_config)
1174
+ ? user_service_worker_output_config[0].codeSplitting
1175
+ : user_service_worker_output_config?.codeSplitting) ?? false
1176
+ }
1177
+ },
1178
+ outDir: `${out}/client`,
1179
+ minify: initial_config.build?.minify,
1180
+ // avoid overwriting the client build Vite manifest
1181
+ manifest: '.vite/service-worker-manifest.json'
1182
+ },
1183
+ consumer: 'client'
1184
+ }
1185
+ }
1186
+ };
1187
+
1188
+ warn_overridden_config(config, new_config);
1189
+
1190
+ return new_config;
1191
+ },
1192
+
1193
+ // our serviceWorker environment only exists when building because Vite only
1194
+ // supports the default client environment during development (for now)
979
1195
  applyToEnvironment(environment) {
980
1196
  return environment.name === 'serviceWorker';
981
1197
  },
982
1198
 
983
1199
  resolveId: {
1200
+ filter: {
1201
+ id: prefixRegex('__sveltekit/')
1202
+ },
984
1203
  handler(id) {
985
- if (id.startsWith('$app/') || id === '$service-worker') {
986
- // ids with :$ don't work with reverse proxies like nginx
987
- return `\0virtual:${id.substring(1)}`;
988
- }
989
-
990
- if (id.startsWith('__sveltekit')) {
991
- return `\0virtual:${id}`;
992
- }
1204
+ return `\0virtual:${id}`;
993
1205
  }
994
1206
  },
995
1207
 
996
- load(id) {
997
- if (!build) {
998
- build = new Set();
999
- for (const key in client_manifest) {
1000
- const { file, css = [], assets = [] } = client_manifest[key];
1001
- build.add(file);
1002
- css.forEach((file) => build.add(file));
1003
- assets.forEach((file) => build.add(file));
1004
- }
1005
-
1006
- // in a service worker, `location` is the location of the service worker itself,
1007
- // which is guaranteed to be `<base>/service-worker.js`
1008
- const base = "location.pathname.split('/').slice(0, -1).join('/')";
1009
-
1010
- service_worker_code = dedent`
1011
- export const base = /*@__PURE__*/ ${base};
1012
-
1013
- export const build = [
1014
- ${Array.from(build)
1015
- .map((file) => `base + ${s(`/${file}`)}`)
1016
- .join(',\n')}
1208
+ load: {
1209
+ filter: {
1210
+ id: [
1211
+ exactRegex('\0virtual:app/manifest'),
1212
+ exactRegex(sveltekit_manifest_data),
1213
+ exactRegex(sveltekit_env_service_worker),
1214
+ exactRegex(sveltekit_env_public_client)
1215
+ ]
1216
+ },
1217
+ handler(id) {
1218
+ if (!manifest_data_code) {
1219
+ // the client build computes `immutable`, unless it was skipped
1220
+ // because every route has `csr: false`
1221
+ immutable ??= collect_immutable(vite_server_manifest, kit.appDir, new Set());
1222
+
1223
+ manifest_data_code = dedent`
1224
+ export const immutable = [
1225
+ ${immutable.map((entry) => s(entry)).join(',\n')}
1017
1226
  ];
1018
1227
 
1019
- export const files = [
1020
- ${manifest_data.assets
1021
- .filter((asset) => kit.serviceWorker.files(asset.file))
1022
- .map((asset) => `base + ${s(`/${asset.file}`)}`)
1023
- .join(',\n')}
1228
+ export const assets = [
1229
+ ${stringify_assets(manifest_data.assets)}
1024
1230
  ];
1025
1231
 
1026
1232
  export const prerendered = [
1027
- ${prerendered.paths.map((path) => `base + ${s(path.replace(kit.paths.base, ''))}`).join(',\n')}
1233
+ ${prerendered.paths.map((path) => s({ path: path.replace(kit.paths.base, '').slice(1) })).join(',\n')}
1028
1234
  ];
1029
1235
 
1030
- export const version = ${s(kit.version.name)};
1031
- `;
1032
- }
1033
-
1034
- if (!id.startsWith('\0virtual:')) return;
1035
-
1036
- const global = is_build
1037
- ? `globalThis.__sveltekit_${version_hash}`
1038
- : 'globalThis.__sveltekit_dev';
1236
+ export const routes = [
1237
+ ${stringify_routes(manifest_data.routes)}
1238
+ ];
1239
+ `;
1240
+ }
1039
1241
 
1040
- if (id === service_worker) {
1041
- return service_worker_code;
1042
- }
1242
+ if (id === '\0virtual:app/manifest') {
1243
+ return `export { immutable, assets, prerendered, routes } from '__sveltekit/manifest-data';`;
1244
+ }
1245
+
1246
+ if (id === sveltekit_manifest_data) {
1247
+ return manifest_data_code;
1248
+ }
1249
+
1250
+ if (id === sveltekit_env_service_worker) {
1251
+ return is_build
1252
+ ? create_sveltekit_env_service_worker(
1253
+ explicit_env_config,
1254
+ env,
1255
+ kit.version.name,
1256
+ kit_global,
1257
+ kit.paths.base,
1258
+ kit.appDir
1259
+ )
1260
+ : create_sveltekit_env_service_worker_dev(
1261
+ explicit_env_config,
1262
+ env,
1263
+ kit.version.name,
1264
+ kit_global
1265
+ );
1266
+ }
1043
1267
 
1044
- if (id === sveltekit_env_service_worker) {
1045
- return create_sveltekit_env_service_worker_dev(explicit_env_config, env, global);
1268
+ if (id === sveltekit_env_public_client) {
1269
+ return create_sveltekit_env_public(
1270
+ explicit_env_config,
1271
+ env,
1272
+ `const env = ${kit_global}.env;`
1273
+ );
1274
+ }
1046
1275
  }
1276
+ },
1277
+
1278
+ generateBundle(_, bundle) {
1279
+ const invalid_modules = new Set();
1280
+ const modules = new Map([
1281
+ [`${runtime_directory}/app/forms/index.js`, '$app/forms'],
1282
+ [`${runtime_directory}/app/navigation/index.js`, '$app/navigation'],
1283
+ [`${runtime_directory}/app/state/index.js`, '$app/state']
1284
+ ]);
1285
+
1286
+ for (const output of Object.values(bundle)) {
1287
+ if (output.type !== 'chunk') continue;
1047
1288
 
1048
- if (id === sveltekit_env_public_client) {
1049
- return create_sveltekit_env_public(explicit_env_config, env, `const env = ${global}.env;`);
1289
+ for (const id of output.moduleIds) {
1290
+ const module = modules.get(id);
1291
+ if (module) invalid_modules.add(module);
1292
+ }
1050
1293
  }
1051
1294
 
1052
- const normalized_cwd = vite.normalizePath(vite_config.root);
1053
- const normalized_lib = vite.normalizePath(kit.files.lib);
1054
- const relative = normalize_id(id, normalized_lib, normalized_cwd);
1055
- const stripped = strip_virtual_prefix(relative);
1056
- throw new Error(
1057
- `Cannot import ${stripped} into service-worker code. Only the modules $service-worker and $app/env/public are available in service workers.`
1058
- );
1295
+ if (invalid_modules.size > 0) {
1296
+ throw new Error(
1297
+ `Cannot import ${Array.from(modules.values())
1298
+ .filter((module) => invalid_modules.has(module))
1299
+ .join(', ')} into service-worker code.`
1300
+ );
1301
+ }
1059
1302
  }
1060
1303
  };
1061
1304
 
1062
1305
  /** @type {Plugin} */
1063
1306
  const plugin_service_worker_env = {
1064
1307
  name: 'vite-plugin-sveltekit-service-worker-env',
1065
-
1308
+ configResolved() {
1309
+ if (service_worker_entry_file) {
1310
+ // @ts-expect-error transform is defined in this object
1311
+ plugin_service_worker_env.transform.filter = {
1312
+ id: exactRegex(service_worker_entry_file)
1313
+ };
1314
+ }
1315
+ },
1316
+ applyToEnvironment(environment) {
1317
+ return !!service_worker_entry_file && environment.config.consumer === 'client';
1318
+ },
1066
1319
  transform: {
1067
- filter: {
1068
- id: service_worker_entry_file || '<skip>'
1069
- },
1070
1320
  handler(code) {
1071
- // in dev, we prepend the service worker with an import that
1072
- // configures `env`, in case `$app/env/public` is imported,
1073
- // in prod, where we currently use non-module service
1074
- // workers, we have to use `importScripts` instead
1321
+ // prepend the service worker with an import that configures
1322
+ // `env`, in case `$app/env/public` is imported. In production
1323
+ // this is required: dynamic public env vars aren't known at
1324
+ // build time, so `env.js` is loaded at runtime. In dev, the
1325
+ // imported module just inlines the current values instead.
1075
1326
  return {
1076
1327
  code: `import '__sveltekit/env/service-worker';\n${code}`
1077
1328
  };
@@ -1079,14 +1330,15 @@ function kit({ svelte_config, adapter }) {
1079
1330
  }
1080
1331
  };
1081
1332
 
1333
+ /** @type {Map<string, Rolldown.RolldownOutput['output']>} */
1334
+ const watch_build_output = new Map();
1335
+ /** @type {(() => Promise<void>) | null} */
1336
+ let finalise = null;
1337
+
1082
1338
  /** @type {Plugin} */
1083
1339
  const plugin_compile = {
1084
1340
  name: 'vite-plugin-sveltekit-compile',
1085
1341
 
1086
- applyToEnvironment(environment) {
1087
- return environment.name !== 'serviceWorker';
1088
- },
1089
-
1090
1342
  /**
1091
1343
  * Build the SvelteKit-provided Vite config to be merged with the user's vite.config.js file.
1092
1344
  * @see https://vitejs.dev/guide/api-plugin.html#config
@@ -1099,12 +1351,13 @@ function kit({ svelte_config, adapter }) {
1099
1351
  let new_config;
1100
1352
 
1101
1353
  if (is_build) {
1102
- const prefix = `${kit.appDir}/immutable`;
1354
+ const app_immutable = `${kit.appDir}/immutable`;
1103
1355
 
1104
1356
  /** @type {Record<string, string>} */
1105
1357
  const server_input = {
1106
1358
  index: `${runtime_directory}/server/index.js`,
1107
1359
  internal: `${out_dir}/generated/server/internal.js`,
1360
+ env: '__sveltekit/env',
1108
1361
  ['remote-entry']: `${runtime_directory}/app/server/remote/index.js`
1109
1362
  };
1110
1363
 
@@ -1133,11 +1386,10 @@ function kit({ svelte_config, adapter }) {
1133
1386
  }
1134
1387
  });
1135
1388
 
1136
- // ...and every matcher
1137
- Object.entries(manifest_data.matchers).forEach(([key, file]) => {
1138
- const name = posixify(path.join('entries/matchers', key));
1139
- server_input[name] = path.resolve(root, file);
1140
- });
1389
+ // ...and the params file
1390
+ if (manifest_data.params) {
1391
+ server_input['entries/params'] = path.resolve(root, manifest_data.params);
1392
+ }
1141
1393
 
1142
1394
  // ...and the hooks files
1143
1395
  if (manifest_data.hooks.server) {
@@ -1155,15 +1407,8 @@ function kit({ svelte_config, adapter }) {
1155
1407
  path.join(kit.files.src, 'instrumentation.server')
1156
1408
  );
1157
1409
  if (server_instrumentation) {
1158
- if (adapter && !adapter.supports?.instrumentation?.()) {
1159
- throw new Error(`${server_instrumentation} is unsupported in ${adapter.name}.`);
1160
- }
1161
- if (!kit.experimental.instrumentation.server) {
1162
- error_for_missing_config(
1163
- '`instrumentation.server.js`',
1164
- 'kit.experimental.instrumentation.server',
1165
- 'true'
1166
- );
1410
+ if (kit.adapter && !kit.adapter.supports?.instrumentation?.()) {
1411
+ throw new Error(`${server_instrumentation} is unsupported in ${kit.adapter.name}.`);
1167
1412
  }
1168
1413
  server_input['instrumentation.server'] = server_instrumentation;
1169
1414
  }
@@ -1171,10 +1416,11 @@ function kit({ svelte_config, adapter }) {
1171
1416
  /** @type {Record<string, string>} */
1172
1417
  const client_input = {};
1173
1418
 
1174
- if (svelte_config.kit.output.bundleStrategy !== 'split') {
1419
+ if (svelte_config.output.bundleStrategy !== 'split') {
1175
1420
  client_input['bundle'] = `${runtime_directory}/client/bundle.js`;
1176
1421
  } else {
1177
1422
  client_input['entry/start'] = `${runtime_directory}/client/entry.js`;
1423
+ client_input['entry/payload'] = `${runtime_directory}/client/payload.js`;
1178
1424
  client_input['entry/app'] = `${out_dir}/generated/client-optimized/app.js`;
1179
1425
  manifest_data.nodes.forEach((node, i) => {
1180
1426
  if (node.component || node.universal) {
@@ -1183,22 +1429,27 @@ function kit({ svelte_config, adapter }) {
1183
1429
  });
1184
1430
  }
1185
1431
 
1186
- const inline = svelte_config.kit.output.bundleStrategy === 'inline';
1187
-
1188
- const config_base = assets_base(kit);
1432
+ const inline = svelte_config.output.bundleStrategy === 'inline';
1189
1433
 
1190
1434
  /** @type {string} */
1191
- const base = kit.paths.assets || kit.paths.base || '/';
1192
- const root_to_assets = prefix + '/assets/';
1435
+ const base = (kit.paths.assets || kit.paths.base) + '/';
1436
+ const root_to_assets = app_immutable + '/assets/';
1193
1437
  const assets_to_root =
1194
- prefix
1438
+ app_immutable
1195
1439
  .split('/')
1196
1440
  .map(() => '..')
1197
1441
  .join('/') + '/../';
1198
1442
 
1443
+ const relative = kit.paths.relative !== false || !!kit.paths.assets;
1444
+
1199
1445
  new_config = {
1200
1446
  appType: 'custom',
1201
- base: config_base,
1447
+ // Affects how Vite loads JS assets on the client.
1448
+ // If the initial HTML we render uses an absolute path for assets,
1449
+ // the additional chunks Vite loads must also use an absolute path.
1450
+ // Otherwise, you end up with additional chunks being loaded relative
1451
+ // to the current chunk rather than the root.
1452
+ base: relative ? './' : base,
1202
1453
  build: {
1203
1454
  cssCodeSplit: !inline,
1204
1455
  cssMinify:
@@ -1207,7 +1458,7 @@ function kit({ svelte_config, adapter }) {
1207
1458
  rolldownOptions: {
1208
1459
  output: {
1209
1460
  name: `__sveltekit_${version_hash}.app`,
1210
- assetFileNames: `${prefix}/assets/[name].[hash][extname]`,
1461
+ assetFileNames: `${app_immutable}/assets/[name].[hash][extname]`,
1211
1462
  hoistTransitiveImports: false,
1212
1463
  sourcemapIgnoreList
1213
1464
  },
@@ -1223,6 +1474,12 @@ function kit({ svelte_config, adapter }) {
1223
1474
  }
1224
1475
 
1225
1476
  handler(warning);
1477
+ },
1478
+ watch: {
1479
+ exclude: [
1480
+ // Ignore all siblings of config.outDir/generated
1481
+ `${out_dir}/generated/**`
1482
+ ]
1226
1483
  }
1227
1484
  },
1228
1485
  emptyOutDir: false,
@@ -1246,7 +1503,7 @@ function kit({ svelte_config, adapter }) {
1246
1503
  }
1247
1504
  }
1248
1505
  },
1249
- // during the initial server build we don't know yet
1506
+ // these are stubs that will be replaced after the initial server build
1250
1507
  define: {
1251
1508
  __SVELTEKIT_HAS_SERVER_LOAD__: 'true',
1252
1509
  __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true',
@@ -1260,9 +1517,27 @@ function kit({ svelte_config, adapter }) {
1260
1517
  input: inline ? client_input['bundle'] : client_input,
1261
1518
  output: {
1262
1519
  format: inline ? 'iife' : 'esm',
1263
- entryFileNames: `${prefix}/[name].[hash].js`,
1264
- chunkFileNames: `${prefix}/chunks/[hash].js`,
1265
- codeSplitting: svelte_config.kit.output.bundleStrategy === 'split'
1520
+ entryFileNames: `${app_immutable}/[name].[hash].js`,
1521
+ chunkFileNames: (/** @type {Rolldown.PreRenderedChunk} */ chunk_info) => {
1522
+ // The manifest data chunk gets a fixed (non-hashed) filename so
1523
+ // that importers' content hashes are stable regardless of the
1524
+ // manifest content — this breaks the content-hash feedback loop
1525
+ if (chunk_info.name === 'sveltekit-manifest') {
1526
+ return `${kit.appDir}/manifest.js`;
1527
+ }
1528
+ return `${app_immutable}/chunks/[hash].js`;
1529
+ },
1530
+ codeSplitting:
1531
+ svelte_config.output.bundleStrategy === 'split'
1532
+ ? {
1533
+ groups: [
1534
+ {
1535
+ name: 'sveltekit-manifest',
1536
+ test: sveltekit_manifest_data
1537
+ }
1538
+ ]
1539
+ }
1540
+ : false
1266
1541
  },
1267
1542
  // This silences Rolldown warnings about not supporting `import.meta`
1268
1543
  // for the `iife` output format. We don't care because it's
@@ -1277,68 +1552,42 @@ function kit({ svelte_config, adapter }) {
1277
1552
  }
1278
1553
  },
1279
1554
  define: {
1280
- __SVELTEKIT_PAYLOAD__: `globalThis.__sveltekit_${version_hash}`
1555
+ __SVELTEKIT_PAYLOAD__:
1556
+ svelte_config.output.bundleStrategy !== 'split' ? kit_global : 'undefined'
1281
1557
  }
1282
1558
  }
1283
1559
  },
1284
1560
  experimental: {
1285
- // we can't change the base path per environment so we're setting the
1286
- // base prefix for files here ourselves
1287
- renderBuiltUrl:
1288
- // if the Vite base is relative, we need to ensure paths used during SSR are absolute
1289
- config_base === './'
1290
- ? (filename, { ssr }) => {
1291
- if (ssr) return base + filename;
1292
- }
1293
- : // but if the Vite base is absolute, we just need to ensure
1294
- // client paths are relative rather than absolute
1295
- (filename, { ssr, hostType }) => {
1296
- if (ssr) return;
1297
-
1298
- if (hostType === 'js') {
1299
- // We could always use a relative asset base path here, but it's better for performance not to.
1300
- // E.g. Vite generates `new URL('/asset.png', import.meta).href` for a relative path vs just '/asset.png'.
1301
- // That's larger and takes longer to run and also causes an HTML diff between SSR and client
1302
- // causing us to do a more expensive hydration check.
1303
- return {
1304
- relative: kit.paths.relative !== false || !!kit.paths.assets
1305
- };
1306
- }
1307
-
1308
- // _app/immutable/assets files
1309
- if (filename.startsWith(root_to_assets)) {
1310
- return `./${filename.slice(root_to_assets.length)}`;
1311
- }
1312
-
1313
- // static dir files
1314
- return assets_to_root + filename;
1315
- }
1561
+ // Allows us to use relative paths in as many places as we can
1562
+ renderBuiltUrl(filename, { ssr, hostType }) {
1563
+ if (hostType === 'js') {
1564
+ // SSR builds should use an absolute path in JS modules to
1565
+ // match the default Vite behaviour
1566
+ if (ssr) return base + filename;
1567
+
1568
+ // We could always use a relative asset base path here, but it's better for performance not to.
1569
+ // E.g. Vite generates `new URL('/asset.png', import.meta).href` for a relative path vs just '/asset.png'.
1570
+ // That's larger and takes longer to run and also causes an HTML diff between SSR and client
1571
+ // causing us to do a more expensive hydration check.
1572
+ return { relative };
1573
+ }
1574
+
1575
+ if (!relative) return;
1576
+
1577
+ // ensure assets loaded by CSS files are loaded relative to the
1578
+ // CSS file rather than the default of relative to the root
1579
+
1580
+ // _app/immutable/assets files
1581
+ if (filename.startsWith(root_to_assets)) {
1582
+ return `./${filename.slice(root_to_assets.length)}`;
1583
+ }
1584
+
1585
+ // static dir files
1586
+ return assets_to_root + filename;
1587
+ }
1316
1588
  },
1317
1589
  publicDir: kit.files.assets
1318
1590
  };
1319
-
1320
- if (service_worker_entry_file) {
1321
- /** @type {Record<string, import('vite').EnvironmentOptions>} */ (
1322
- new_config.environments
1323
- ).serviceWorker = {
1324
- build: {
1325
- modulePreload: false,
1326
- rolldownOptions: {
1327
- input: {
1328
- 'service-worker': service_worker_entry_file
1329
- },
1330
- output: {
1331
- entryFileNames: 'service-worker.js',
1332
- assetFileNames: `${kit.appDir}/immutable/assets/[name].[hash][extname]`,
1333
- codeSplitting: false
1334
- }
1335
- },
1336
- outDir: `${out}/client`,
1337
- minify: initial_config.build?.minify
1338
- },
1339
- consumer: 'client'
1340
- };
1341
- }
1342
1591
  } else {
1343
1592
  new_config = {
1344
1593
  appType: 'custom',
@@ -1366,33 +1615,67 @@ function kit({ svelte_config, adapter }) {
1366
1615
  * Adds the SvelteKit middleware to do SSR in dev mode.
1367
1616
  * @see https://vitejs.dev/guide/api-plugin.html#configureserver
1368
1617
  */
1369
- async configureServer(vite) {
1370
- return await dev(vite, vite_config, svelte_config, () => remotes, root, adapter);
1618
+ async configureServer(server) {
1619
+ return await dev(
1620
+ vite,
1621
+ server,
1622
+ vite_config,
1623
+ svelte_config,
1624
+ () => remotes,
1625
+ root,
1626
+ (data) => {
1627
+ manifest_data = data;
1628
+ // Invalidate the manifest data module so it reloads with new routes/files
1629
+ invalidate_module(server, sveltekit_manifest_data);
1630
+ }
1631
+ );
1371
1632
  },
1372
1633
 
1373
1634
  /**
1374
1635
  * Adds the SvelteKit middleware to do SSR in preview mode.
1375
1636
  * @see https://vitejs.dev/guide/api-plugin.html#configurepreviewserver
1376
1637
  */
1377
- configurePreviewServer(vite) {
1378
- return preview(vite, vite_config, svelte_config, adapter);
1638
+ configurePreviewServer(server) {
1639
+ return preview(server, vite_config, svelte_config);
1379
1640
  },
1380
1641
 
1381
- renderChunk(code, chunk) {
1382
- if (code.includes('__SVELTEKIT_TRACK__')) {
1383
- return {
1384
- // Rolldown changes our single quotes to double quotes so we need it in the regex too
1385
- code: code.replace(/__SVELTEKIT_TRACK__\(['"](.+?)['"]\)/g, (_, label) => {
1386
- (tracked_features[chunk.name + '.js'] ??= []).push(label);
1387
- // put extra whitespace at the end of the comment to preserve the source size and avoid interfering with source maps
1388
- return `/* track ${label} */`;
1389
- }),
1390
- map: null // TODO we may need to generate a sourcemap in future
1391
- };
1642
+ applyToEnvironment(environment) {
1643
+ return environment.name !== 'serviceWorker';
1644
+ },
1645
+
1646
+ renderChunk: {
1647
+ // composable filters are not accepted type-wise but still work during build
1648
+ // see https://github.com/vitejs/rolldown-vite/issues/605
1649
+ filter: /** @type {any} */ (
1650
+ /** @satisfies {TopLevelFilterExpression[]} */ ([include(code('__SVELTEKIT_TRACK__'))])
1651
+ ),
1652
+ handler(code, chunk) {
1653
+ // composable filters only work during build so we still need this guard for dev
1654
+ // see https://github.com/vitejs/rolldown-vite/issues/605
1655
+ if (code.includes('__SVELTEKIT_TRACK__')) {
1656
+ return {
1657
+ // Rolldown changes our single quotes to double quotes so we need it in the regex too
1658
+ code: code.replace(/__SVELTEKIT_TRACK__\(['"](.+?)['"]\)/g, (_, label) => {
1659
+ (tracked_features[chunk.name + '.js'] ??= []).push(label);
1660
+ // put extra whitespace at the end of the comment to preserve the source size and avoid interfering with source maps
1661
+ return `/* track ${label} */`;
1662
+ }),
1663
+ map: null // TODO we may need to generate a sourcemap in future
1664
+ };
1665
+ }
1392
1666
  }
1393
1667
  },
1394
1668
 
1395
- generateBundle() {
1669
+ generateBundle(_options, bundle) {
1670
+ // a watched build returns a watcher rather than the build output from
1671
+ // `builder.build` so we need to retrieve it from the generateBundle hook
1672
+ if (this.meta.watchMode) {
1673
+ watch_build_output.set(
1674
+ this.environment.name,
1675
+ /** @type {Rolldown.RolldownOutput['output']} */ (Object.values(bundle))
1676
+ );
1677
+ }
1678
+
1396
1679
  if (this.environment.config.consumer !== 'client') return;
1397
1680
 
1398
1681
  this.emitFile({
@@ -1403,330 +1686,540 @@ function kit({ svelte_config, adapter }) {
1403
1686
  },
1404
1687
 
1405
1688
  async buildApp(builder) {
1406
- // clears the output directories
1407
- if (!builder.config.build.watch) {
1408
- rimraf(out);
1409
- }
1410
- mkdirp(out);
1411
-
1412
- const server_bundle = /** @type {import('vite').Rolldown.RolldownOutput} */ (
1413
- await builder.build(builder.environments.ssr)
1414
- );
1689
+ fs.rmSync(out, { force: true, recursive: true });
1690
+ fs.mkdirSync(out, { recursive: true });
1415
1691
 
1416
- const verbose = vite_config.logLevel === 'info';
1692
+ const verbose = builder.config.logLevel === 'info';
1417
1693
  const log = logger({ verbose });
1418
1694
 
1419
- /** @type {import('vite').Manifest} */
1420
- const server_manifest = JSON.parse(read(`${out}/server/.vite/manifest.json`));
1421
-
1422
- /** @type {import('types').BuildData} */
1423
- const build_data = {
1424
- app_dir: kit.appDir,
1425
- app_path: `${kit.paths.base.slice(1)}${kit.paths.base ? '/' : ''}${kit.appDir}`,
1426
- manifest_data,
1427
- out_dir: out,
1428
- service_worker: service_worker_entry_file ? 'service-worker.js' : null, // TODO make file configurable?
1429
- client: null,
1430
- server_manifest
1431
- };
1695
+ let ssr_build = await builder.build(builder.environments.ssr);
1696
+
1697
+ /** @param {Rolldown.RolldownOutput['output']} server_chunks */
1698
+ const process_ssr_build = async (server_chunks) => {
1699
+ // Replace manifest placeholders in SSR output. `assets` and `routes`
1700
+ // are known from `manifest_data`. `immutable` and `prerendered` are not
1701
+ // known yet they get sentinel strings that are replaced after
1702
+ // the client build and after prerendering respectively.
1703
+ replace_manifest_placeholder_variables(server_chunks, `${out}/server`, {
1704
+ assets: manifest_data.assets.map((asset) => ({ path: asset.file })),
1705
+ routes: get_manifest_routes(manifest_data.routes)
1706
+ });
1707
+
1708
+ vite_server_manifest = /** @type {Manifest} */ (
1709
+ JSON.parse(read(`${out}/server/.vite/manifest.json`))
1710
+ );
1432
1711
 
1433
- const manifest_path = `${out}/server/manifest-full.js`;
1434
- fs.writeFileSync(
1435
- manifest_path,
1436
- `export const manifest = ${generate_manifest({
1437
- build_data,
1438
- prerendered: [],
1439
- relative_path: '.',
1440
- routes: manifest_data.routes,
1441
- remotes,
1712
+ const manifest_path = `${out}/server/manifest-full.js`;
1713
+ const assets_path = `${kit.appDir}/immutable/assets`;
1714
+
1715
+ /** @type {BuildData} */
1716
+ const build_data = {
1717
+ app_dir: kit.appDir,
1718
+ app_path: `${kit.paths.base.slice(1)}${kit.paths.base ? '/' : ''}${kit.appDir}`,
1719
+ manifest_data,
1720
+ out_dir: out,
1721
+ service_worker: service_worker_entry_file ? 'service-worker.js' : null, // TODO make file configurable?
1722
+ client: null,
1723
+ server_manifest: vite_server_manifest
1724
+ };
1725
+
1726
+ fs.writeFileSync(
1727
+ manifest_path,
1728
+ `export const manifest = ${generate_manifest({
1729
+ build_data,
1730
+ prerendered: [],
1731
+ relative_path: '.',
1732
+ routes: manifest_data.routes,
1733
+ remotes,
1734
+ root
1735
+ })};\n`
1736
+ );
1737
+
1738
+ // first, build server nodes without the client manifest so we can analyse it
1739
+ build_server_nodes(
1740
+ out,
1741
+ kit,
1742
+ manifest_data,
1743
+ vite_server_manifest,
1744
+ null,
1745
+ assets_path,
1746
+ server_chunks,
1442
1747
  root
1443
- })};\n`
1444
- );
1748
+ );
1445
1749
 
1446
- log.info('Analysing routes');
1447
-
1448
- const { metadata } = await analyse({
1449
- hash: kit.router.type === 'hash',
1450
- manifest_path,
1451
- manifest_data,
1452
- server_manifest,
1453
- tracked_features,
1454
- env,
1455
- out,
1456
- remotes,
1457
- root,
1458
- vite_config_file: vite_config.configFile
1459
- });
1750
+ log.info('Analysing routes');
1460
1751
 
1461
- build_metadata = metadata;
1752
+ const { metadata } = await analyse({
1753
+ hash: kit.router.type === 'hash',
1754
+ manifest_path,
1755
+ manifest_data,
1756
+ server_manifest: vite_server_manifest,
1757
+ tracked_features,
1758
+ env,
1759
+ remotes,
1760
+ vite_config_file: vite_config.configFile
1761
+ });
1762
+ build_metadata = metadata;
1462
1763
 
1463
- log.info('Building app');
1764
+ log.info('Building app');
1464
1765
 
1465
- // create client build
1466
- write_client_manifest(
1467
- kit,
1468
- manifest_data,
1469
- `${out_dir}/generated/client-optimized`,
1470
- metadata.nodes
1471
- );
1766
+ const server_assets = `${out}/server/${assets_path}`;
1767
+ const client_assets = `${out}/client/${assets_path}`;
1472
1768
 
1473
- const nodes = Object.values(
1474
- /** @type {import('types').ServerMetadata} */ (build_metadata).nodes
1475
- );
1769
+ const skip_client_build = manifest_data.nodes.every(
1770
+ (node) => node.page_options?.csr === false
1771
+ );
1476
1772
 
1477
- // Through the finished analysis we can now check if any node has server or universal load functions
1478
- const has_server_load = nodes.some((node) => node.has_server_load);
1479
- const has_universal_load = nodes.some((node) => node.has_universal_load);
1773
+ if (skip_client_build) {
1774
+ copy(server_assets, client_assets);
1775
+ copy(kit.files.assets, `${out}/client`);
1776
+ } else {
1777
+ // ...and build the client
1778
+ write_client_manifest(
1779
+ kit,
1780
+ manifest_data,
1781
+ `${out_dir}/generated/client-optimized`,
1782
+ build_metadata.nodes
1783
+ );
1480
1784
 
1481
- if (builder.environments.client.config.define) {
1482
- builder.environments.client.config.define.__SVELTEKIT_HAS_SERVER_LOAD__ =
1483
- s(has_server_load);
1484
- builder.environments.client.config.define.__SVELTEKIT_HAS_UNIVERSAL_LOAD__ =
1485
- s(has_universal_load);
1486
- }
1785
+ // Through the finished analysis we can now check if any node has server or universal load functions
1786
+ const nodes = Object.values(build_metadata.nodes);
1787
+ const has_server_load = nodes.some((node) => node.has_server_load);
1788
+ const has_universal_load = nodes.some((node) => node.has_universal_load);
1487
1789
 
1488
- const { output: client_chunks } = /** @type {import('vite').Rolldown.RolldownOutput} */ (
1489
- await builder.build(builder.environments.client)
1490
- );
1790
+ if (builder.environments.client.config.define) {
1791
+ builder.environments.client.config.define.__SVELTEKIT_HAS_SERVER_LOAD__ =
1792
+ s(has_server_load);
1793
+ builder.environments.client.config.define.__SVELTEKIT_HAS_UNIVERSAL_LOAD__ =
1794
+ s(has_universal_load);
1795
+ }
1491
1796
 
1492
- // We use `build.ssrEmitAssets` so that asset URLs created from
1493
- // imports in server-only modules correspond to files in the build,
1494
- // but we don't want to copy over CSS imports as these are already
1495
- // accounted for in the client bundle. In most cases it would be
1496
- // a no-op, but for SSR builds `url(...)` paths are handled
1497
- // differently (relative for client, absolute for server)
1498
- // resulting in different hashes, and thus duplication
1499
- const ssr_stylesheets = new Set(
1500
- Object.values(server_manifest)
1501
- .map((chunk) => chunk.css ?? [])
1502
- .flat()
1503
- );
1797
+ const client_build = await builder.build(builder.environments.client);
1798
+ const client_chunks = await normalise_build(
1799
+ builder.environments.client.name,
1800
+ client_build,
1801
+ watch_build_output
1802
+ );
1803
+
1804
+ // We use `build.ssrEmitAssets` so that asset URLs created from
1805
+ // imports in server-only modules correspond to files in the build,
1806
+ // but we don't want to copy over CSS imports as these are already
1807
+ // accounted for in the client bundle. In most cases it would be
1808
+ // a no-op, but for SSR builds `url(...)` paths are handled
1809
+ // differently (relative for client, absolute for server)
1810
+ // resulting in different hashes, and thus duplication
1811
+ const ssr_stylesheets = new Set(
1812
+ Object.values(vite_server_manifest)
1813
+ .map((chunk) => chunk.css ?? [])
1814
+ .flat()
1815
+ );
1504
1816
 
1505
- const assets_path = `${kit.appDir}/immutable/assets`;
1506
- const server_assets = `${out}/server/${assets_path}`;
1507
- const client_assets = `${out}/client/${assets_path}`;
1817
+ if (fs.existsSync(server_assets)) {
1818
+ for (const file of fs.readdirSync(server_assets)) {
1819
+ const src = `${server_assets}/${file}`;
1820
+ const dest = `${client_assets}/${file}`;
1508
1821
 
1509
- if (fs.existsSync(server_assets)) {
1510
- for (const file of fs.readdirSync(server_assets)) {
1511
- const src = `${server_assets}/${file}`;
1512
- const dest = `${client_assets}/${file}`;
1822
+ if (fs.existsSync(dest) || ssr_stylesheets.has(`${assets_path}/${file}`)) {
1823
+ continue;
1824
+ }
1513
1825
 
1514
- if (fs.existsSync(dest) || ssr_stylesheets.has(`${assets_path}/${file}`)) {
1515
- continue;
1826
+ copy(src, dest);
1827
+ }
1516
1828
  }
1517
1829
 
1518
- if (file.endsWith('.css')) {
1519
- // make absolute paths in CSS relative, for portability
1520
- const content = fs
1521
- .readFileSync(src, 'utf-8')
1522
- .replaceAll(`${kit.paths.base}/${assets_path}`, '.');
1830
+ vite_client_manifest = /** @type {Manifest} */ (
1831
+ JSON.parse(read(`${out}/client/.vite/manifest.json`))
1832
+ );
1523
1833
 
1524
- fs.writeFileSync(src, content);
1525
- }
1834
+ /**
1835
+ * @param {string} entry
1836
+ * @param {boolean} [add_dynamic_css]
1837
+ */
1838
+ const deps_of = (entry, add_dynamic_css = false) =>
1839
+ find_deps(
1840
+ /** @type {Manifest} */ (vite_client_manifest),
1841
+ posixify(path.relative(root, entry)),
1842
+ add_dynamic_css,
1843
+ root
1844
+ );
1526
1845
 
1527
- copy(src, dest);
1528
- }
1529
- }
1846
+ // the inline bundle and stylesheet are deleted further down, after
1847
+ // being inlined into the page, so they must not appear in `immutable`
1848
+ /** @type {Set<string>} */
1849
+ const inlined = new Set();
1850
+ /** @type {Rolldown.OutputAsset | undefined} */
1851
+ let inline_style;
1852
+
1853
+ if (kit.output.bundleStrategy === 'inline') {
1854
+ inline_style = /** @type {Rolldown.OutputAsset | undefined} */ (
1855
+ client_chunks.find(
1856
+ (chunk) =>
1857
+ chunk.type === 'asset' &&
1858
+ chunk.names.length === 1 &&
1859
+ chunk.names[0] === 'style.css'
1860
+ )
1861
+ );
1530
1862
 
1531
- /** @type {import('vite').Manifest} */
1532
- client_manifest = JSON.parse(read(`${out}/client/.vite/manifest.json`));
1533
-
1534
- /**
1535
- * @param {string} entry
1536
- * @param {boolean} [add_dynamic_css]
1537
- */
1538
- const deps_of = (entry, add_dynamic_css = false) =>
1539
- find_deps(client_manifest, posixify(path.relative(root, entry)), add_dynamic_css, root);
1540
-
1541
- if (svelte_config.kit.output.bundleStrategy === 'split') {
1542
- const start = deps_of(`${runtime_directory}/client/entry.js`);
1543
- const app = deps_of(`${out_dir}/generated/client-optimized/app.js`);
1544
-
1545
- build_data.client = {
1546
- start: start.file,
1547
- app: app.file,
1548
- imports: [...start.imports, ...app.imports],
1549
- stylesheets: [...start.stylesheets, ...app.stylesheets],
1550
- fonts: [...start.fonts, ...app.fonts],
1551
- uses_env_dynamic_public: client_chunks.some(
1552
- (chunk) => chunk.type === 'chunk' && chunk.modules[sveltekit_env_public_client]
1553
- )
1554
- };
1863
+ inlined.add(deps_of(`${runtime_directory}/client/bundle.js`).file);
1864
+ if (inline_style) inlined.add(inline_style.fileName);
1865
+ }
1555
1866
 
1556
- // In case of server-side route resolution, we create a purpose-built route manifest that is
1557
- // similar to that on the client, with as much information computed upfront so that we
1558
- // don't need to include any code of the actual routes in the server bundle.
1559
- if (svelte_config.kit.router.resolution === 'server') {
1560
- const nodes = manifest_data.nodes.map((node, i) => {
1561
- if (node.component || node.universal) {
1562
- const entry = `${out_dir}/generated/client-optimized/nodes/${i}.js`;
1563
- const deps = deps_of(entry, true);
1564
- const file = resolve_symlinks(
1565
- client_manifest,
1566
- `${out_dir}/generated/client-optimized/nodes/${i}.js`,
1567
- root
1568
- ).chunk.file;
1569
-
1570
- return { file, css: deps.stylesheets };
1571
- }
1867
+ // Replace manifest placeholders in client output. `immutable` is
1868
+ // computed from the Vite client manifest, `assets` and `routes`
1869
+ // from `manifest_data`. `prerendered` is left as a placeholder
1870
+ // for now — it's replaced after prerendering completes.
1871
+ immutable = collect_immutable(vite_client_manifest, kit.appDir, inlined);
1872
+
1873
+ replace_manifest_placeholder_variables(client_chunks, `${out}/client`, {
1874
+ immutable,
1875
+ assets: manifest_data.assets.map((asset) => ({ path: asset.file })),
1876
+ routes: get_manifest_routes(manifest_data.routes)
1572
1877
  });
1573
- build_data.client.nodes = nodes.map((node) => node?.file);
1574
- build_data.client.css = nodes.map((node) => node?.css);
1575
1878
 
1576
- build_data.client.routes = compact(
1577
- manifest_data.routes.map((route) => {
1578
- if (!route.page) return;
1879
+ // Now that the client build is done, replace the `build` sentinel
1880
+ // in the SSR output with the real build files
1881
+ replace_manifest_placeholder_strings(`${out}/server`, { immutable });
1579
1882
 
1580
- return {
1581
- id: route.id,
1582
- pattern: route.pattern,
1583
- params: route.params,
1584
- layouts: route.page.layouts.map((l) =>
1585
- l !== undefined ? [metadata.nodes[l].has_server_load, l] : undefined
1586
- ),
1587
- errors: route.page.errors,
1588
- leaf: [metadata.nodes[route.page.leaf].has_server_load, route.page.leaf]
1883
+ const has_explicit_dynamic_public_env = Object.values(explicit_env_config ?? {}).some(
1884
+ (variable) => variable.public && !variable.static
1885
+ );
1886
+
1887
+ // the app only depends on runtime public env if it imports `$app/env/public`
1888
+ // *and* at least one public env var is actually dynamic (non-static)
1889
+ const uses_env_dynamic_public =
1890
+ has_explicit_dynamic_public_env &&
1891
+ client_chunks.some(
1892
+ (chunk) => chunk.type === 'chunk' && chunk.modules[sveltekit_env_public_client]
1893
+ );
1894
+
1895
+ if (kit.output.bundleStrategy === 'split') {
1896
+ const start_entry = posixify(
1897
+ path.relative(root, `${runtime_directory}/client/entry.js`)
1898
+ );
1899
+ const start = find_deps(vite_client_manifest, start_entry, false, root);
1900
+ const runtime_entry = resolve_symlinks(vite_client_manifest, start_entry, root).chunk
1901
+ .dynamicImports?.[0]; // client/entry.js dynamically imports client/client-entry.js
1902
+ if (!runtime_entry) throw new Error('Could not find the client runtime chunk');
1903
+ const runtime = find_deps(vite_client_manifest, runtime_entry, false, root);
1904
+ const app = deps_of(`${out_dir}/generated/client-optimized/app.js`);
1905
+
1906
+ build_data.client = {
1907
+ start: start.file,
1908
+ app: app.file,
1909
+ imports: Array.from(
1910
+ new Set([
1911
+ ...start.imports,
1912
+ runtime.file,
1913
+ ...runtime.imports,
1914
+ app.file,
1915
+ ...app.imports
1916
+ ])
1917
+ ),
1918
+ stylesheets: [...start.stylesheets, ...runtime.stylesheets, ...app.stylesheets],
1919
+ fonts: [...start.fonts, ...runtime.fonts, ...app.fonts],
1920
+ uses_env_dynamic_public
1921
+ };
1922
+
1923
+ // In case of server-side route resolution, we create a purpose-built route manifest that is
1924
+ // similar to that on the client, with as much information computed upfront so that we
1925
+ // don't need to include any code of the actual routes in the server bundle.
1926
+ if (kit.router.resolution === 'server') {
1927
+ const nodes = manifest_data.nodes.map((node, i) => {
1928
+ if (node.component || node.universal) {
1929
+ const entry = `${out_dir}/generated/client-optimized/nodes/${i}.js`;
1930
+ const deps = deps_of(entry, true);
1931
+ const file = resolve_symlinks(
1932
+ /** @type {Manifest} */ (vite_client_manifest),
1933
+ `${out_dir}/generated/client-optimized/nodes/${i}.js`,
1934
+ root
1935
+ ).chunk.file;
1936
+
1937
+ return { file, css: deps.stylesheets };
1938
+ }
1939
+ });
1940
+ build_data.client.nodes = nodes.map((node) => node?.file);
1941
+ build_data.client.css = nodes.map((node) => node?.css);
1942
+
1943
+ build_data.client.routes = compact(
1944
+ manifest_data.routes.map((route) => {
1945
+ if (!route.page) return;
1946
+
1947
+ return {
1948
+ id: route.id,
1949
+ pattern: route.pattern,
1950
+ params: route.params,
1951
+ layouts: route.page.layouts.map((l) =>
1952
+ l !== undefined ? [metadata.nodes[l].has_server_load, l] : undefined
1953
+ ),
1954
+ errors: route.page.errors,
1955
+ leaf: [metadata.nodes[route.page.leaf].has_server_load, route.page.leaf]
1956
+ };
1957
+ })
1958
+ );
1959
+ }
1960
+ } else {
1961
+ const start = deps_of(`${runtime_directory}/client/bundle.js`);
1962
+
1963
+ build_data.client = {
1964
+ start: start.file,
1965
+ imports: start.imports,
1966
+ stylesheets: start.stylesheets,
1967
+ fonts: start.fonts,
1968
+ uses_env_dynamic_public
1969
+ };
1970
+
1971
+ if (kit.output.bundleStrategy === 'inline') {
1972
+ build_data.client.inline = {
1973
+ script: read(`${out}/client/${start.file}`),
1974
+ style: /** @type {string | undefined} */ (inline_style?.source)
1589
1975
  };
1590
- })
1976
+
1977
+ // the bundle and stylesheet are inlined into the page, so the
1978
+ // emitted files are never loaded
1979
+ fs.unlinkSync(`${out}/client/${start.file}`);
1980
+ fs.rmSync(`${out}/client/${start.file}.map`, { force: true });
1981
+ if (inline_style) fs.unlinkSync(`${out}/client/${inline_style.fileName}`);
1982
+ }
1983
+ }
1984
+
1985
+ // regenerate manifest now that we have client entry...
1986
+ fs.writeFileSync(
1987
+ manifest_path,
1988
+ `export const manifest = ${generate_manifest({
1989
+ build_data,
1990
+ prerendered: [],
1991
+ relative_path: '.',
1992
+ routes: manifest_data.routes,
1993
+ remotes,
1994
+ root
1995
+ })};\n`
1591
1996
  );
1592
- }
1593
- } else {
1594
- const start = deps_of(`${runtime_directory}/client/bundle.js`);
1595
-
1596
- build_data.client = {
1597
- start: start.file,
1598
- imports: start.imports,
1599
- stylesheets: start.stylesheets,
1600
- fonts: start.fonts,
1601
- uses_env_dynamic_public: client_chunks.some(
1602
- (chunk) => chunk.type === 'chunk' && chunk.modules[sveltekit_env_public_client]
1603
- )
1604
- };
1605
1997
 
1606
- if (svelte_config.kit.output.bundleStrategy === 'inline') {
1607
- const style = /** @type {import('vite').Rolldown.OutputAsset} */ (
1608
- client_chunks.find(
1609
- (chunk) =>
1610
- chunk.type === 'asset' && chunk.names.length === 1 && chunk.names[0] === 'style.css'
1611
- )
1998
+ // regenerate nodes with the client manifest...
1999
+ build_server_nodes(
2000
+ out,
2001
+ kit,
2002
+ manifest_data,
2003
+ vite_server_manifest,
2004
+ vite_client_manifest,
2005
+ assets_path,
2006
+ client_chunks,
2007
+ root
1612
2008
  );
2009
+ }
1613
2010
 
1614
- build_data.client.inline = {
1615
- script: read(`${out}/client/${start.file}`),
1616
- style: /** @type {string | undefined} */ (style?.source)
1617
- };
2011
+ // ...and prerender
2012
+ let prerender_results;
2013
+ try {
2014
+ prerender_results = await prerender({
2015
+ hash: kit.router.type === 'hash',
2016
+ out,
2017
+ manifest_path,
2018
+ metadata,
2019
+ verbose,
2020
+ env,
2021
+ vite_config_file: vite_config.configFile,
2022
+ is_tty: process.stdout.isTTY
2023
+ });
2024
+
2025
+ // this silly hack is necessary to ensure that stderr from prerender is flushed before we continue
2026
+ await new Promise((f) => setTimeout(f, 0));
2027
+ } catch (e) {
2028
+ if (e instanceof Error && e.message === '__handled__') {
2029
+ // error details are already logged inside `prerender`, don't duplicate them
2030
+ throw stackless('Prerendering failed');
2031
+ } else {
2032
+ // Unforeseen error, rethrow as-is
2033
+ throw e;
2034
+ }
1618
2035
  }
1619
- }
1620
2036
 
1621
- // regenerate manifest now that we have client entry...
1622
- fs.writeFileSync(
1623
- manifest_path,
1624
- `export const manifest = ${generate_manifest({
1625
- build_data,
1626
- prerendered: [],
1627
- relative_path: '.',
1628
- routes: manifest_data.routes,
1629
- remotes,
1630
- root
1631
- })};\n`
1632
- );
2037
+ prerendered = prerender_results.prerendered;
1633
2038
 
1634
- // regenerate nodes with the client manifest...
1635
- build_server_nodes(
1636
- out,
1637
- kit,
1638
- manifest_data,
1639
- server_manifest,
1640
- client_manifest,
1641
- assets_path,
1642
- client_chunks,
1643
- root
1644
- );
2039
+ // Replace the `prerendered` sentinel in both SSR and client output
2040
+ // with the real prerendered paths. The other sentinels (`build`)
2041
+ // were already replaced after the client build.
2042
+ const prerendered_paths = prerendered.paths.map((p) => {
2043
+ return { path: p.replace(kit.paths.base, '').slice(1) };
2044
+ });
1645
2045
 
1646
- // ...and prerender
1647
- const prerender_results = await prerender({
1648
- hash: kit.router.type === 'hash',
1649
- out,
1650
- manifest_path,
1651
- metadata,
1652
- verbose,
1653
- env,
1654
- vite_config_file: vite_config.configFile
1655
- });
1656
- prerendered = prerender_results.prerendered;
2046
+ replace_manifest_placeholder_strings(`${out}/server`, { prerendered: prerendered_paths });
2047
+ replace_manifest_placeholder_strings(`${out}/client`, { prerendered: prerendered_paths });
1657
2048
 
1658
- await treeshake_prerendered_remotes(
1659
- vite,
1660
- out,
1661
- remotes,
1662
- metadata,
1663
- process.cwd(),
1664
- server_bundle,
1665
- vite_config.build.sourcemap
1666
- );
2049
+ // For `inline` strategy, the entry file was deleted and read into
2050
+ // `build_data.client.inline.script` — replace the sentinel there too
2051
+ if (build_data.client?.inline?.script) {
2052
+ build_data.client.inline.script = build_data.client.inline.script.replaceAll(
2053
+ '"__sveltekit_manifest_prerendered__"',
2054
+ JSON.stringify(prerendered_paths)
2055
+ );
2056
+ }
1667
2057
 
1668
- // generate a new manifest that doesn't include prerendered pages
1669
- fs.writeFileSync(
1670
- `${out}/server/manifest.js`,
1671
- `export const manifest = ${generate_manifest({
1672
- build_data,
1673
- prerendered: prerendered.paths,
1674
- relative_path: '.',
1675
- routes: manifest_data.routes.filter(
1676
- (route) => prerender_results.prerender_map.get(route.id) !== true
1677
- ),
2058
+ // generate a new manifest that doesn't include prerendered pages
2059
+ fs.writeFileSync(
2060
+ `${out}/server/manifest.js`,
2061
+ `export const manifest = ${generate_manifest({
2062
+ build_data,
2063
+ prerendered: prerendered.paths,
2064
+ relative_path: '.',
2065
+ routes: manifest_data.routes.filter(
2066
+ (route) => prerender_results.prerender_map.get(route.id) !== true
2067
+ ),
2068
+ remotes,
2069
+ root
2070
+ })};\n`
2071
+ );
2072
+
2073
+ await treeshake_prerendered_remotes(
2074
+ vite,
2075
+ out,
1678
2076
  remotes,
1679
- root
1680
- })};\n`
1681
- );
2077
+ remote_original_by_hash,
2078
+ metadata,
2079
+ process.cwd(),
2080
+ server_chunks,
2081
+ vite_config.build.sourcemap
2082
+ );
1682
2083
 
1683
- if (service_worker_entry_file) {
1684
- if (kit.paths.assets) {
1685
- throw new Error('Cannot use service worker alongside config.kit.paths.assets');
1686
- }
2084
+ // defer until after other buildApp hooks have run
2085
+ finalise = async () => {
2086
+ // defer creating the service worker to avoid other plugins from
2087
+ // overwriting it if they run a client environment build
2088
+ if (service_worker_entry_file) {
2089
+ log.info('Building service worker');
1687
2090
 
1688
- log.info('Building service worker');
2091
+ // mirror client settings that we couldn't set per environment in the config hook
2092
+ builder.environments.serviceWorker.config.define =
2093
+ builder.environments.client.config.define;
2094
+ builder.environments.serviceWorker.config.resolve.alias = [
2095
+ ...get_config_aliases(kit, vite_config.root)
2096
+ ];
1689
2097
 
1690
- builder.environments.serviceWorker.config.define =
1691
- builder.environments.client.config.define;
1692
- builder.environments.serviceWorker.config.resolve.alias = [
1693
- ...get_config_aliases(kit, vite_config.root)
1694
- ];
1695
- builder.environments.serviceWorker.config.experimental.renderBuiltUrl = (filename) => {
1696
- return {
1697
- runtime: `new URL(${JSON.stringify(filename)}, location.href).pathname`
1698
- };
2098
+ // we have to overwrite this because it can't be configured per environment in the config hook
2099
+ builder.environments.serviceWorker.config.experimental.renderBuiltUrl = (filename) => {
2100
+ return {
2101
+ runtime: `new URL(${JSON.stringify(filename)}, location.href).pathname`
2102
+ };
2103
+ };
2104
+
2105
+ const service_worker_build = await builder.build(builder.environments.serviceWorker);
2106
+ await normalise_build(
2107
+ builder.environments.serviceWorker.name,
2108
+ service_worker_build,
2109
+ watch_build_output
2110
+ );
2111
+ }
2112
+
2113
+ console.log(
2114
+ `\nRun ${styleText(['bold', 'cyan'], 'npm run preview')} to preview your production build locally.`
2115
+ );
2116
+
2117
+ if (kit.adapter) {
2118
+ await adapt(
2119
+ svelte_config,
2120
+ build_data,
2121
+ metadata,
2122
+ prerendered,
2123
+ prerender_results.prerender_map,
2124
+ log,
2125
+ remotes,
2126
+ vite_config,
2127
+ explicit_env_config
2128
+ );
2129
+ } else {
2130
+ log.warn('\nNo adapter specified');
2131
+
2132
+ const link = styleText(['bold', 'cyan'], 'https://svelte.dev/docs/kit/adapters');
2133
+ console.log(
2134
+ `See ${link} to learn how to configure your app to run on the platform of your choosing`
2135
+ );
2136
+ }
1699
2137
  };
2138
+ };
2139
+
2140
+ // `vite build`
2141
+ ssr_build = Array.isArray(ssr_build) ? ssr_build[0] : ssr_build;
2142
+ if ('output' in ssr_build) {
2143
+ await load_and_validate_params({
2144
+ routes: manifest_data.routes,
2145
+ params_path: manifest_data.params,
2146
+ root
2147
+ });
1700
2148
 
1701
- await builder.build(builder.environments.serviceWorker);
2149
+ return await process_ssr_build(ssr_build.output);
1702
2150
  }
1703
2151
 
1704
- console.log(
1705
- `\nRun ${styleText(['bold', 'cyan'], 'npm run preview')} to preview your production build locally.`
1706
- );
2152
+ // `vite build --watch`
2153
+ let rebuild = false;
1707
2154
 
1708
- if (adapter) {
1709
- const { adapt } = await import('../../core/adapt/index.js');
1710
- await adapt(
1711
- adapter,
1712
- svelte_config,
1713
- build_data,
1714
- metadata,
1715
- prerendered,
1716
- prerender_results.prerender_map,
1717
- log,
1718
- remotes,
1719
- vite_config,
1720
- explicit_env_config
1721
- );
1722
- } else {
1723
- console.log(styleText(['bold', 'yellow'], '\nNo adapter specified'));
2155
+ const before_ssr_build_rerun = async () => {
2156
+ rebuild = true;
2157
+
2158
+ // these are set once per plugin initialisation or when the config hook
2159
+ // runs. However, those don't re-run during watch mode. So, we need to
2160
+ // re-initialise them manually here
2161
+ manifest_data = sync.all(svelte_config, root).manifest_data;
2162
+
2163
+ tracked_features = {};
2164
+
2165
+ remotes = [];
2166
+ remote_original_by_hash.clear();
2167
+ emitted_remote_hashes.clear();
2168
+
2169
+ immutable = null;
2170
+ manifest_data_code = null;
2171
+
2172
+ finalise = null;
1724
2173
 
1725
- const link = styleText(['bold', 'cyan'], 'https://svelte.dev/docs/kit/adapters');
1726
- console.log(
1727
- `See ${link} to learn how to configure your app to run on the platform of your choosing`
2174
+ fs.mkdirSync(out, { recursive: true });
2175
+
2176
+ explicit_env_entry = resolve_explicit_env_entry(kit);
2177
+ explicit_env_config = await sync.env(
2178
+ vite,
2179
+ kit,
2180
+ explicit_env_entry,
2181
+ vite_config.root,
2182
+ vite_config.mode
1728
2183
  );
1729
- }
2184
+
2185
+ await load_and_validate_params({
2186
+ routes: manifest_data.routes,
2187
+ params_path: manifest_data.params,
2188
+ root
2189
+ });
2190
+ };
2191
+
2192
+ ssr_build.on('change', before_ssr_build_rerun);
2193
+ ssr_build.on('restart', before_ssr_build_rerun);
2194
+
2195
+ /** @type {PromiseWithResolvers<void>} */
2196
+ const task = Promise.withResolvers();
2197
+
2198
+ ssr_build.on('event', async (event) => {
2199
+ if (event.code === 'ERROR') {
2200
+ return task.reject();
2201
+ }
2202
+
2203
+ if (event.code === 'BUNDLE_END') {
2204
+ try {
2205
+ await process_ssr_build(
2206
+ /** @type {Rolldown.RolldownOutput['output']} */ (
2207
+ watch_build_output.get(builder.environments.ssr.name)
2208
+ )
2209
+ );
2210
+ // buildApp hooks don't rerun in watch mode so we need to run
2211
+ // the deferred steps here on subsequent builds
2212
+ if (rebuild) await finalise?.();
2213
+ } catch (e) {
2214
+ return task.reject(e);
2215
+ } finally {
2216
+ await event.result.close();
2217
+ }
2218
+ return task.resolve();
2219
+ }
2220
+ });
2221
+
2222
+ await task.promise;
1730
2223
  }
1731
2224
  };
1732
2225
 
@@ -1734,23 +2227,32 @@ function kit({ svelte_config, adapter }) {
1734
2227
  const plugin_adapter = {
1735
2228
  name: 'vite-plugin-sveltekit-adapter',
1736
2229
  apply: 'build',
1737
- // expose the adapter so that forked processes (e.g. prerendering)
1738
- // can retrieve it by resolving the Vite config
1739
- api: {
1740
- adapter
2230
+ buildApp: {
2231
+ // this will run after any buildApp hooks provided by other Vite plugins
2232
+ // see https://vite.dev/guide/api-environment-frameworks#environments-during-build
2233
+ order: 'post',
2234
+ async handler() {
2235
+ await finalise?.();
2236
+ }
1741
2237
  }
1742
2238
  };
1743
2239
 
1744
- return [
1745
- plugin_setup,
1746
- plugin_remote,
1747
- plugin_virtual_modules,
1748
- process.env.TEST !== 'true' ? plugin_guard : undefined,
1749
- service_worker_entry_file && plugin_service_worker_env,
1750
- plugin_service_worker,
1751
- plugin_compile,
1752
- plugin_adapter
1753
- ].filter((p) => !!p);
2240
+ return /** @type {Plugin[]} */ (
2241
+ [
2242
+ svelte_config.adapter?.vite?.plugins?.pre,
2243
+ plugin_resolve_root,
2244
+ plugin_setup,
2245
+ plugin_remote_guard,
2246
+ plugin_remote,
2247
+ plugin_virtual_modules,
2248
+ process.env.TEST !== 'true' ? plugin_guard : undefined,
2249
+ plugin_service_worker,
2250
+ plugin_service_worker_env,
2251
+ plugin_compile,
2252
+ plugin_adapter,
2253
+ svelte_config.adapter?.vite?.plugins?.post
2254
+ ].filter(Boolean)
2255
+ );
1754
2256
  }
1755
2257
 
1756
2258
  /**
@@ -1785,13 +2287,14 @@ function find_overridden_config(config, resolved_config, enforced_config, path,
1785
2287
  for (const key in enforced_config) {
1786
2288
  if (typeof config === 'object' && key in config && key in resolved_config) {
1787
2289
  const enforced = enforced_config[key];
2290
+ const resolved = resolved_config[key];
1788
2291
 
1789
2292
  if (enforced === true) {
1790
- if (config[key] !== resolved_config[key]) {
2293
+ if (comparable(config[key]) !== comparable(resolved)) {
1791
2294
  out.push(path + key);
1792
2295
  }
1793
2296
  } else {
1794
- find_overridden_config(config[key], resolved_config[key], enforced, path + key + '.', out);
2297
+ find_overridden_config(config[key], resolved, enforced, path + key + '.', out);
1795
2298
  }
1796
2299
  }
1797
2300
  }
@@ -1799,20 +2302,249 @@ function find_overridden_config(config, resolved_config, enforced_config, path,
1799
2302
  }
1800
2303
 
1801
2304
  /**
1802
- * @param {import('types').ValidatedConfig} config
2305
+ * Collects the content-hashed files of a Vite manifest, in the shape of
2306
+ * `$app/manifest`'s `immutable` export.
2307
+ *
2308
+ * @param {Manifest} manifest
2309
+ * @param {string} app_dir
2310
+ * @param {Set<string>} inlined files deleted from the output after being inlined into the page
2311
+ * @returns {Array<{ path: string }>}
1803
2312
  */
1804
- const create_service_worker_module = (config) => dedent`
1805
- if (typeof self === 'undefined' || self instanceof ServiceWorkerGlobalScope === false) {
1806
- throw new Error('This module can only be imported inside a service worker');
2313
+ const collect_immutable = (manifest, app_dir, inlined) => {
2314
+ const prefix = `${app_dir}/immutable`;
2315
+
2316
+ /** @type {Set<string>} */
2317
+ const files = new Set();
2318
+
2319
+ /** @param {string} file */
2320
+ const add = (file) => {
2321
+ if (file.startsWith(prefix) && !inlined.has(file)) files.add(file);
2322
+ };
2323
+
2324
+ for (const key in manifest) {
2325
+ const { file, css, assets } = manifest[key];
2326
+ add(file);
2327
+ if (css) for (let i = 0; i < css.length; i++) add(css[i]);
2328
+ if (assets) for (let i = 0; i < assets.length; i++) add(assets[i]);
1807
2329
  }
1808
2330
 
1809
- export const build = [];
1810
- export const files = [
1811
- ${create_assets(config)
1812
- .filter((asset) => config.kit.serviceWorker.files(asset.file))
1813
- .map((asset) => `${s(`${config.kit.paths.base}/${asset.file}`)}`)
1814
- .join(',\n')}
1815
- ];
1816
- export const prerendered = [];
1817
- export const version = ${s(config.kit.version.name)};
1818
- `;
2331
+ return Array.from(files, (path) => ({ path }));
2332
+ };
2333
+
2334
+ /**
2335
+ * Normalizes a config value for comparison, since Windows paths may use backslashes
2336
+ * and differ in casing (e.g. the drive letter) depending on where they came from.
2337
+ * @param {any} value
2338
+ */
2339
+ function comparable(value) {
2340
+ if (typeof value !== 'string') return value;
2341
+ const normalized = posixify(value);
2342
+ return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
2343
+ }
2344
+
2345
+ /**
2346
+ * @param {Asset[] | undefined} assets
2347
+ * @returns {string}
2348
+ */
2349
+ function stringify_assets(assets) {
2350
+ return assets?.map((asset) => s({ path: asset.file })).join(',\n') ?? '';
2351
+ }
2352
+
2353
+ /**
2354
+ * @param {RouteData[] | undefined} routes
2355
+ * @returns {Array<{ id: string; page: boolean; endpoint: boolean }>}
2356
+ */
2357
+ function get_manifest_routes(routes) {
2358
+ return (
2359
+ routes?.filter(is_app_route).map((route) => ({
2360
+ id: route.id,
2361
+ page: is_page_route(route),
2362
+ endpoint: is_endpoint_route(route)
2363
+ })) ?? []
2364
+ );
2365
+ }
2366
+
2367
+ /**
2368
+ * @param {RouteData[] | undefined} routes
2369
+ * @returns {string}
2370
+ */
2371
+ function stringify_routes(routes) {
2372
+ return get_manifest_routes(routes)
2373
+ .map((route) => s(route))
2374
+ .join(',\n');
2375
+ }
2376
+
2377
+ /**
2378
+ * Creates the `$app/manifest` data module. During development, real values
2379
+ * are emitted for `assets` and `routes` (the only data known at that point).
2380
+ *
2381
+ * During build, bare identifier placeholders (fake globals) are emitted.
2382
+ * The bundler leaves these as unresolved global references in the output,
2383
+ * which are then replaced with real values by scanning the output chunks
2384
+ * after each build completes. This avoids the content-hash feedback loop:
2385
+ * the manifest data lives in its own chunk with a fixed filename, so
2386
+ * importers' hashes are stable regardless of the manifest content.
2387
+ *
2388
+ * @param {boolean} is_build
2389
+ * @param {ManifestData | undefined} manifest_data
2390
+ * @returns {string}
2391
+ */
2392
+ const create_manifest_data_module = (is_build, manifest_data) => {
2393
+ if (is_build) {
2394
+ // Bare identifiers (fake globals) — the bundler leaves these as
2395
+ // unresolved global references in the output. They are replaced
2396
+ // with real values by `replace_manifest_placeholder_variables`
2397
+ // after each build completes.
2398
+ return dedent`
2399
+ export const immutable = __SVELTEKIT_MANIFEST_IMMUTABLE__;
2400
+ export const assets = __SVELTEKIT_MANIFEST_ASSETS__;
2401
+ export const prerendered = __SVELTEKIT_MANIFEST_PRERENDERED__;
2402
+ export const routes = __SVELTEKIT_MANIFEST_ROUTES__;
2403
+ `;
2404
+ }
2405
+
2406
+ // In dev, `manifest_data` may not be set yet on the very first load,
2407
+ // but `configureServer` (which calls `sync.create`) runs before any
2408
+ // module is served, so it will be set by the time this is called.
2409
+ return dedent`
2410
+ // empty during dev
2411
+ export const immutable = [];
2412
+ export const prerendered = [];
2413
+
2414
+ export const assets = [
2415
+ ${stringify_assets(manifest_data?.assets)}
2416
+ ];
2417
+
2418
+ export const routes = [
2419
+ ${stringify_routes(manifest_data?.routes)}
2420
+ ];
2421
+ `;
2422
+ };
2423
+
2424
+ /**
2425
+ * Replaces manifest data placeholder identifiers in output chunks with real
2426
+ * values, or strings that are valid JS (so thecode doesn't crash during prerendering)
2427
+ * but findable on disk for later replacement by `replace_manifest_placeholder_strings`.
2428
+ *
2429
+ * @param {Rolldown.RolldownOutput['output']} chunks
2430
+ * @param {string} output_dir
2431
+ * @param {{
2432
+ * immutable?: Array<{ path: string }>;
2433
+ * assets?: Array<{ path: string }>;
2434
+ * prerendered?: Array<{ path: string }>;
2435
+ * routes?: Array<{ id: string; page: boolean; endpoint: boolean }>;
2436
+ * }} values
2437
+ */
2438
+ const replace_manifest_placeholder_variables = (chunks, output_dir, values) => {
2439
+ /** @type {Record<string, string>} */
2440
+ const replacements = {
2441
+ __SVELTEKIT_MANIFEST_IMMUTABLE__: JSON.stringify(
2442
+ values.immutable ?? '__sveltekit_manifest_build__'
2443
+ ),
2444
+ __SVELTEKIT_MANIFEST_ASSETS__: JSON.stringify(values.assets ?? '__sveltekit_manifest_files__'),
2445
+ __SVELTEKIT_MANIFEST_PRERENDERED__: JSON.stringify(
2446
+ values.prerendered ?? '__sveltekit_manifest_prerendered__'
2447
+ ),
2448
+ __SVELTEKIT_MANIFEST_ROUTES__: JSON.stringify(values.routes ?? '__sveltekit_manifest_routes__')
2449
+ };
2450
+
2451
+ for (const chunk of chunks) {
2452
+ if (chunk.type !== 'chunk') continue;
2453
+ if (!chunk.code.includes('__SVELTEKIT_MANIFEST_')) continue;
2454
+
2455
+ let code = chunk.code;
2456
+
2457
+ for (const [identifier, replacement] of Object.entries(replacements)) {
2458
+ code = code.replaceAll(identifier, replacement);
2459
+ }
2460
+
2461
+ const file_path = `${output_dir}/${chunk.fileName}`;
2462
+ fs.writeFileSync(file_path, code);
2463
+ }
2464
+ };
2465
+
2466
+ /**
2467
+ * Replaces manifest sentinel strings in files on disk with real values.
2468
+ * This is used for values that weren't known when the first replacement
2469
+ * pass ran (e.g. `build` wasn't known until after the client build,
2470
+ * `prerendered` wasn't known until after prerendering).
2471
+ *
2472
+ * @param {string} dir Directory to scan for .js files
2473
+ * @param {{
2474
+ * immutable?: Array<{ path: string }>;
2475
+ * prerendered?: Array<{ path: string }>;
2476
+ * }} values
2477
+ */
2478
+ const replace_manifest_placeholder_strings = (dir, values) => {
2479
+ /** @type {Record<string, string>} */
2480
+ const replacements = {};
2481
+
2482
+ if (values.immutable !== undefined) {
2483
+ replacements['__sveltekit_manifest_build__'] = JSON.stringify(values.immutable);
2484
+ }
2485
+ if (values.prerendered !== undefined) {
2486
+ replacements['__sveltekit_manifest_prerendered__'] = JSON.stringify(values.prerendered);
2487
+ }
2488
+
2489
+ for (const file of fs.readdirSync(dir)) {
2490
+ const file_path = `${dir}/${file}`;
2491
+ const stat = fs.statSync(file_path);
2492
+
2493
+ if (stat.isDirectory()) {
2494
+ replace_manifest_placeholder_strings(file_path, values);
2495
+ continue;
2496
+ }
2497
+
2498
+ if (!file.endsWith('.js')) continue;
2499
+
2500
+ let code = read(file_path);
2501
+ let changed = false;
2502
+
2503
+ for (const [sentinel, replacement] of Object.entries(replacements)) {
2504
+ if (code.includes(sentinel)) {
2505
+ code = code.replaceAll(`"${sentinel}"`, replacement);
2506
+ changed = true;
2507
+ }
2508
+ }
2509
+
2510
+ if (changed) {
2511
+ fs.writeFileSync(file_path, code);
2512
+ }
2513
+ }
2514
+ };
2515
+
2516
+ /**
2517
+ * Normalises the build output to a consistent format, handling watch mode and multiple environments
2518
+ * @param {string} name The name of the environment
2519
+ * @param {Rolldown.RolldownOutput | Rolldown.RolldownOutput[] | Rolldown.RolldownWatcher} build The return value of builder.build
2520
+ * @param {Map<string, Rolldown.RolldownOutput['output']>} build_output_map
2521
+ * @returns {Promise<Rolldown.RolldownOutput['output']>}
2522
+ */
2523
+ async function normalise_build(name, build, build_output_map) {
2524
+ if ('output' in build) {
2525
+ return build.output;
2526
+ }
2527
+
2528
+ if (Array.isArray(build)) {
2529
+ return build[0].output;
2530
+ }
2531
+
2532
+ /** @type {PromiseWithResolvers<void>} */
2533
+ const bundling = Promise.withResolvers();
2534
+
2535
+ build.on('event', async (event) => {
2536
+ if (event.code === 'ERROR') {
2537
+ await build.close();
2538
+ return bundling.reject(event.error);
2539
+ }
2540
+
2541
+ if (event.code === 'BUNDLE_END') {
2542
+ await build.close();
2543
+ return bundling.resolve();
2544
+ }
2545
+ });
2546
+
2547
+ await bundling.promise;
2548
+
2549
+ return /** @type {Rolldown.RolldownOutput['output']} */ (build_output_map.get(name));
2550
+ }