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

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