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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/package.json +33 -21
  2. package/src/core/adapt/builder.js +29 -43
  3. package/src/core/adapt/index.js +1 -4
  4. package/src/core/config/index.js +132 -71
  5. package/src/core/config/options.js +294 -244
  6. package/src/core/config/types.d.ts +1 -1
  7. package/src/core/env.js +121 -5
  8. package/src/core/generate_manifest/index.js +12 -15
  9. package/src/core/postbuild/analyse.js +7 -19
  10. package/src/core/postbuild/crawl.js +22 -6
  11. package/src/core/postbuild/prerender.js +42 -23
  12. package/src/core/sync/create_manifest_data/index.js +53 -50
  13. package/src/core/sync/sync.js +0 -2
  14. package/src/core/sync/write_client_manifest.js +8 -15
  15. package/src/core/sync/write_env.js +2 -1
  16. package/src/core/sync/write_non_ambient.js +12 -9
  17. package/src/core/sync/write_server.js +13 -14
  18. package/src/core/sync/write_tsconfig.js +16 -8
  19. package/src/core/sync/write_tsconfig_test/package.json +7 -0
  20. package/src/core/sync/write_types/index.js +28 -24
  21. package/src/core/utils.js +2 -2
  22. package/src/exports/env/index.js +12 -0
  23. package/src/exports/hooks/index.js +3 -9
  24. package/src/exports/hooks/sequence.js +3 -2
  25. package/src/exports/index.js +35 -13
  26. package/src/exports/internal/client.js +5 -0
  27. package/src/exports/internal/env.js +2 -2
  28. package/src/exports/internal/index.js +1 -90
  29. package/src/exports/internal/{event.js → server/event.js} +1 -2
  30. package/src/exports/internal/server/index.js +33 -0
  31. package/src/exports/internal/shared.js +89 -0
  32. package/src/exports/node/index.js +62 -15
  33. package/src/exports/params.js +63 -0
  34. package/src/exports/public.d.ts +323 -170
  35. package/src/exports/url.js +86 -0
  36. package/src/exports/vite/build/build_server.js +53 -59
  37. package/src/exports/vite/build/remote.js +18 -11
  38. package/src/exports/vite/build/utils.js +0 -8
  39. package/src/exports/vite/dev/index.js +42 -44
  40. package/src/exports/vite/index.js +810 -593
  41. package/src/exports/vite/preview/index.js +29 -22
  42. package/src/exports/vite/static_analysis/index.js +2 -4
  43. package/src/exports/vite/static_analysis/types.d.ts +14 -0
  44. package/src/exports/vite/utils.js +21 -36
  45. package/src/runtime/app/env/internal.js +4 -4
  46. package/src/runtime/app/env/types.d.ts +1 -1
  47. package/src/runtime/app/environment/index.js +6 -0
  48. package/src/runtime/app/forms.js +24 -7
  49. package/src/runtime/app/paths/client.js +4 -10
  50. package/src/runtime/app/paths/internal/client.js +4 -2
  51. package/src/runtime/app/paths/internal/server.js +2 -23
  52. package/src/runtime/app/paths/public.d.ts +0 -28
  53. package/src/runtime/app/paths/server.js +9 -5
  54. package/src/runtime/app/server/remote/command.js +0 -3
  55. package/src/runtime/app/server/remote/form.js +27 -15
  56. package/src/runtime/app/server/remote/prerender.js +29 -36
  57. package/src/runtime/app/server/remote/query.js +101 -99
  58. package/src/runtime/app/server/remote/requested.js +22 -14
  59. package/src/runtime/app/server/remote/shared.js +30 -26
  60. package/src/runtime/app/state/client.js +1 -2
  61. package/src/runtime/app/stores.js +13 -76
  62. package/src/runtime/client/bundle.js +1 -1
  63. package/src/runtime/client/client-entry.js +3 -0
  64. package/src/runtime/client/client.js +411 -318
  65. package/src/runtime/client/entry.js +24 -3
  66. package/src/runtime/client/fetcher.js +3 -2
  67. package/src/runtime/client/ndjson.js +6 -33
  68. package/src/runtime/client/payload.js +17 -0
  69. package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
  70. package/src/runtime/client/remote-functions/command.svelte.js +7 -32
  71. package/src/runtime/client/remote-functions/form.svelte.js +183 -112
  72. package/src/runtime/client/remote-functions/prerender.svelte.js +29 -8
  73. package/src/runtime/client/remote-functions/query/index.js +7 -14
  74. package/src/runtime/client/remote-functions/query/instance.svelte.js +63 -18
  75. package/src/runtime/client/remote-functions/query/proxy.js +3 -3
  76. package/src/runtime/client/remote-functions/query-batch.svelte.js +60 -68
  77. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +60 -18
  78. package/src/runtime/client/remote-functions/query-live/iterator.js +36 -55
  79. package/src/runtime/client/remote-functions/shared.svelte.js +88 -62
  80. package/src/runtime/client/sse.js +32 -0
  81. package/src/runtime/client/state.svelte.js +65 -49
  82. package/src/runtime/client/stream.js +38 -0
  83. package/src/runtime/client/types.d.ts +11 -7
  84. package/src/runtime/client/utils.js +0 -96
  85. package/src/runtime/components/root.svelte +66 -0
  86. package/src/runtime/env/dynamic/private.js +7 -0
  87. package/src/runtime/env/dynamic/public.js +7 -0
  88. package/src/runtime/env/static/private.js +6 -0
  89. package/src/runtime/env/static/public.js +6 -0
  90. package/src/runtime/form-utils.js +100 -22
  91. package/src/runtime/server/cookie.js +69 -52
  92. package/src/runtime/server/csrf.js +65 -0
  93. package/src/runtime/server/data/index.js +14 -18
  94. package/src/runtime/server/env_module.js +0 -5
  95. package/src/runtime/server/index.js +2 -2
  96. package/src/runtime/server/page/actions.js +41 -26
  97. package/src/runtime/server/page/index.js +9 -15
  98. package/src/runtime/server/page/load_data.js +1 -1
  99. package/src/runtime/server/page/render.js +112 -187
  100. package/src/runtime/server/page/respond_with_error.js +7 -8
  101. package/src/runtime/server/page/server_routing.js +27 -18
  102. package/src/runtime/server/remote.js +355 -216
  103. package/src/runtime/server/respond.js +97 -61
  104. package/src/runtime/server/utils.js +32 -9
  105. package/src/runtime/shared.js +83 -13
  106. package/src/runtime/telemetry/otel.js +1 -1
  107. package/src/runtime/types.d.ts +8 -0
  108. package/src/types/ambient.d.ts +10 -5
  109. package/src/types/global-private.d.ts +15 -22
  110. package/src/types/internal.d.ts +89 -74
  111. package/src/types/private.d.ts +33 -1
  112. package/src/utils/error.js +24 -4
  113. package/src/utils/import.js +6 -1
  114. package/src/utils/imports.js +83 -0
  115. package/src/utils/mime.js +9 -0
  116. package/src/utils/params.js +66 -0
  117. package/src/utils/routing.js +90 -44
  118. package/src/utils/shared-iterator.js +5 -0
  119. package/src/utils/streaming.js +14 -4
  120. package/src/utils/url.js +20 -2
  121. package/src/version.js +1 -1
  122. package/types/index.d.ts +467 -537
  123. package/types/index.d.ts.map +22 -39
  124. package/src/core/sync/write_root.js +0 -148
  125. package/src/exports/internal/server.js +0 -22
  126. package/src/types/synthetic/$lib.md +0 -5
  127. /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
@@ -1,7 +1,8 @@
1
- /** @import { EnvVarConfig } from '@sveltejs/kit' */
1
+ /** @import { EnvVarConfig, KitConfig } from '@sveltejs/kit' */
2
2
  /** @import { Options, SvelteConfig } from '@sveltejs/vite-plugin-svelte' */
3
3
  /** @import { PreprocessorGroup } from 'svelte/compiler' */
4
- /** @import { Plugin, ResolvedConfig, UserConfig, ViteDevServer } from 'vite' */
4
+ /** @import { BuildData, ManifestData, Prerendered, ServerMetadata, RemoteInternals, ValidatedConfig, ValidatedKitConfig } from 'types' */
5
+ /** @import { Manifest, Plugin, ResolvedConfig, Rolldown, UserConfig, ViteDevServer } from 'vite' */
5
6
  import fs from 'node:fs';
6
7
  import path from 'node:path';
7
8
  import process from 'node:process';
@@ -15,30 +16,34 @@ import {
15
16
  create_sveltekit_env,
16
17
  create_sveltekit_env_public,
17
18
  resolve_explicit_env_entry,
19
+ create_sveltekit_env_service_worker,
18
20
  create_sveltekit_env_service_worker_dev,
19
- create_sveltekit_env_private
21
+ create_sveltekit_env_private,
22
+ create_exported_declarations
20
23
  } from '../../core/env.js';
21
24
  import * as sync from '../../core/sync/sync.js';
22
25
  import { create_assets } from '../../core/sync/create_manifest_data/index.js';
26
+ import { load_and_validate_params } from '../../utils/params.js';
23
27
  import { runtime_directory, logger } from '../../core/utils.js';
24
28
  import { generate_manifest } from '../../core/generate_manifest/index.js';
25
29
  import { build_server_nodes } from './build/build_server.js';
26
- import { assets_base, find_deps, resolve_symlinks } from './build/utils.js';
30
+ import { find_deps, resolve_symlinks } from './build/utils.js';
27
31
  import { dev } from './dev/index.js';
28
32
  import { preview } from './preview/index.js';
29
33
  import {
30
34
  error_for_missing_config,
31
35
  get_config_aliases,
32
36
  normalize_id,
33
- stackless,
34
37
  strip_virtual_prefix
35
38
  } from './utils.js';
39
+ import { stackless } from '../../utils/error.js';
36
40
  import { write_client_manifest } from '../../core/sync/write_client_manifest.js';
37
41
  import prerender from '../../core/postbuild/prerender.js';
38
42
  import analyse from '../../core/postbuild/analyse.js';
39
43
  import { s } from '../../utils/misc.js';
40
44
  import { hash } from '../../utils/hash.js';
41
45
  import { dedent } from '../../core/sync/utils.js';
46
+ import { get_import_aliases, get_hash_import_keys } from '../../utils/imports.js';
42
47
  import {
43
48
  app_env_private,
44
49
  app_server,
@@ -53,10 +58,14 @@ import {
53
58
  import { import_peer } from '../../utils/import.js';
54
59
  import { compact } from '../../utils/array.js';
55
60
  import { should_ignore, has_children } from './static_analysis/utils.js';
56
- import { process_config } from '../../core/config/index.js';
61
+ import { process_config, split_config, validate_config } from '../../core/config/index.js';
57
62
  import { treeshake_prerendered_remotes } from './build/remote.js';
58
63
 
59
- /** @type {string} */
64
+ /**
65
+ * The posix-ified root of the project based on the Vite configuration.
66
+ * Populated after Vite plugins' `config` hooks run
67
+ * @type {string}
68
+ */
60
69
  let root;
61
70
 
62
71
  /** @type {import('./types.js').EnforcedConfig} */
@@ -89,7 +98,7 @@ const enforced_config = {
89
98
  resolve: {
90
99
  alias: {
91
100
  $app: true,
92
- $lib: true,
101
+ $env: true,
93
102
  '$service-worker': true
94
103
  }
95
104
  }
@@ -146,65 +155,73 @@ let vite_plugin_svelte;
146
155
 
147
156
  /**
148
157
  * 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]
158
+ * Any options that don't belong to SvelteKit are passed through to `vite-plugin-svelte`.
159
+ *
160
+ * Since version 3.0.0 you must pass [configuration](configuration) directly.
161
+ *
162
+ * Since version 2.62.0 you can pass configuration directly, in which case `svelte.config.js` is ignored.
163
+ *
164
+ * @param {KitConfig & Omit<Options, 'onwarn'> & Pick<SvelteConfig, 'vitePlugin'>} [config]
151
165
  * @returns {Promise<Plugin[]>}
152
166
  */
153
167
  export async function sveltekit(config) {
154
168
  const cwd = process.cwd();
155
169
 
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);
170
+ // any options passed to the plugin that SvelteKit doesn't use itself are
171
+ // forwarded to vite-plugin-svelte, which does its own validation
172
+ const split = split_config(config ?? {});
173
+ const svelte_config = validate_config(split.svelte_config);
174
+
175
+ if (Array.isArray(svelte_config.preprocess)) {
176
+ svelte_config.preprocess.push(warning_preprocessor);
177
+ } else if (svelte_config.preprocess) {
178
+ svelte_config.preprocess = [svelte_config.preprocess, warning_preprocessor];
179
+ } else {
180
+ svelte_config.preprocess = warning_preprocessor;
172
181
  }
173
182
 
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
183
+ vite_plugin_svelte = await import_peer('@sveltejs/vite-plugin-svelte', cwd);
184
+
185
+ /** @type {Partial<Options>} */
186
+ const inline_vps_config = {
187
+ preprocess: svelte_config.preprocess,
188
+ ...(svelte_config.vitePlugin ?? {}),
189
+ // pass through any options that SvelteKit doesn't use itself, so that
190
+ // the options SvelteKit manages always take precedence
191
+ ...split.vite_plugin_svelte_config,
192
+ // we don't want vite-plugin-svelte to load the svelte.config.js file because
193
+ // we expect options to be passed through the SvelteKit Vite plugin
178
194
  configFile: false
179
195
  };
180
196
 
181
- vite_plugin_svelte = await import_peer('@sveltejs/vite-plugin-svelte', cwd);
197
+ // vite-plugin-svelte inline config options need to be added conditionally
198
+ // because passing undefined causes it to crash
199
+ if (svelte_config.extensions) {
200
+ inline_vps_config.extensions = svelte_config.extensions;
201
+ }
202
+
203
+ if (svelte_config.compilerOptions) {
204
+ inline_vps_config.compilerOptions = svelte_config.compilerOptions;
205
+ }
182
206
 
183
207
  return [
184
- plugin_svelte_config({ vite_plugin_svelte_options, svelte_config }),
185
- ...vite_plugin_svelte.svelte(vite_plugin_svelte_options),
208
+ plugin_root(),
209
+ ...vite_plugin_svelte.svelte(inline_vps_config),
186
210
  ...kit({
187
- svelte_config,
188
- adapter: svelte_config.kit.adapter
211
+ svelte_config
189
212
  })
190
213
  ];
191
214
  }
192
215
 
193
- /** @param {import('vite').UserConfig | import('vite').ResolvedConfig} vite_config */
216
+ /** @param {UserConfig | ResolvedConfig} vite_config */
194
217
  function resolve_root(vite_config) {
195
218
  return posixify(vite_config.root ? path.resolve(vite_config.root) : process.cwd());
196
219
  }
197
220
 
198
221
  /**
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}
222
+ * @return {Plugin}
206
223
  */
207
- function plugin_svelte_config({ vite_plugin_svelte_options, svelte_config }) {
224
+ function plugin_root() {
208
225
  return {
209
226
  name: 'vite-plugin-sveltekit-resolve-svelte-config',
210
227
  // make sure it runs first
@@ -214,21 +231,14 @@ function plugin_svelte_config({ vite_plugin_svelte_options, svelte_config }) {
214
231
  handler(config) {
215
232
  root = resolve_root(config);
216
233
 
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;
234
+ const config_file = ['svelte.config.js', 'svelte.config.ts'].find((file) =>
235
+ fs.existsSync(path.join(root, file))
236
+ );
237
+ if (config_file) {
238
+ throw new Error(
239
+ `${config_file} is no longer used. Please pass configuration via the \`sveltekit(...)\` plugin in your Vite config.`
240
+ );
225
241
  }
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
242
  }
233
243
  },
234
244
  // TODO: do we even need to set `root` based on the final Vite config?
@@ -252,15 +262,14 @@ function plugin_svelte_config({ vite_plugin_svelte_options, svelte_config }) {
252
262
  * - https://rolldown.rs/apis/plugin-api#output-generation-hooks
253
263
  *
254
264
  * @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[]}
265
+ * @param {ValidatedConfig} opts.svelte_config
266
+ * @return {Plugin[]}
258
267
  */
259
- function kit({ svelte_config, adapter }) {
268
+ function kit({ svelte_config }) {
260
269
  /** @type {typeof import('vite')} */
261
270
  let vite;
262
271
 
263
- /** @type {import('types').ValidatedKitConfig} */
272
+ /** @type {ValidatedKitConfig} */
264
273
  let kit;
265
274
  /** @type {string} `kit.outDir` but posix-ified */
266
275
  let out_dir;
@@ -279,26 +288,30 @@ function kit({ svelte_config, adapter }) {
279
288
  /** @type {Record<string, string>} */
280
289
  let env;
281
290
 
282
- /** @type {import('types').ManifestData} */
291
+ /** @type {ManifestData} */
283
292
  let manifest_data;
284
293
 
285
- /** @type {import('types').ServerMetadata | undefined} only set at build time once analysis is finished */
294
+ /** @type {ServerMetadata | undefined} only set at build time once analysis is finished */
286
295
  let build_metadata = undefined;
287
296
 
288
297
  /** @type {UserConfig} */
289
298
  let initial_config;
290
299
 
291
300
  /** @type {string | null} */
292
- let service_worker_entry_file = resolve_entry(svelte_config.kit.files.serviceWorker);
301
+ let service_worker_entry_file;
293
302
  /** @type {import('node:path').ParsedPath} */
294
303
  let parsed_service_worker;
295
304
 
296
305
  /** @type {string} */
297
306
  let normalized_cwd;
298
- /** @type {string} */
299
- let normalized_lib;
307
+ /** @type {Array<{ alias: string, path: string }>} */
308
+ let normalized_aliases;
300
309
  /** @type {string} */
301
310
  let normalized_node_modules;
311
+ /** @type {string} */
312
+ let normalized_routes;
313
+ /** @type {string} */
314
+ let normalized_assets;
302
315
  /**
303
316
  * A map showing which features (such as `$app/server:read`) are defined
304
317
  * in which chunks, so that we can later determine which routes use which features
@@ -309,6 +322,9 @@ function kit({ svelte_config, adapter }) {
309
322
  const sourcemapIgnoreList = /** @param {string} relative_path */ (relative_path) =>
310
323
  relative_path.includes('node_modules') || relative_path.includes(kit.outDir);
311
324
 
325
+ /** @type {string} name for `globalThis.__sveltekit_xxx` */
326
+ let kit_global;
327
+
312
328
  /** @type {Plugin} */
313
329
  const plugin_setup = {
314
330
  name: 'vite-plugin-sveltekit-setup',
@@ -316,10 +332,6 @@ function kit({ svelte_config, adapter }) {
316
332
  options: svelte_config
317
333
  },
318
334
 
319
- applyToEnvironment(environment) {
320
- return environment.name !== 'serviceWorker';
321
- },
322
-
323
335
  /**
324
336
  * Build the SvelteKit-provided Vite config to be merged with the user's vite.config.js file.
325
337
  * @see https://vitejs.dev/guide/api-plugin.html#config
@@ -330,32 +342,56 @@ function kit({ svelte_config, adapter }) {
330
342
  initial_config = config;
331
343
  is_build = config_env.command === 'build';
332
344
 
333
- ({ kit } = svelte_config);
345
+ ({ kit } = process_config(svelte_config, root));
334
346
  out_dir = posixify(kit.outDir);
335
347
  out = `${out_dir}/output`;
336
348
 
337
349
  version_hash = hash(kit.version.name);
338
350
 
339
- env = loadEnv(config_env.mode, kit.env.dir, '');
351
+ kit_global = is_build
352
+ ? `globalThis.__sveltekit_${version_hash}`
353
+ : 'globalThis.__sveltekit_dev';
340
354
 
341
- service_worker_entry_file = resolve_entry(kit.files.serviceWorker);
342
- parsed_service_worker = path.parse(kit.files.serviceWorker);
355
+ env = loadEnv(config_env.mode, kit.env.dir, '');
343
356
 
344
357
  vite = await import_peer('vite', root);
345
358
 
346
359
  normalized_cwd = vite.normalizePath(root);
347
- normalized_lib = vite.normalizePath(kit.files.lib);
360
+ normalized_aliases = get_import_aliases(root, vite.normalizePath.bind(vite));
348
361
  normalized_node_modules = vite.normalizePath(path.resolve(root, 'node_modules'));
362
+ normalized_routes = vite.normalizePath(path.resolve(root, kit.files.routes));
363
+ normalized_assets = vite.normalizePath(path.resolve(root, kit.files.assets));
364
+
365
+ // Add `#`-prefixed import keys to the enforced config so users are warned
366
+ // if they try to set them in their Vite config's resolve.alias
367
+ const enforced_alias = /** @type {Record<string, true>} */ (
368
+ /** @type {any} */ (enforced_config.resolve).alias
369
+ );
370
+ for (const key of get_hash_import_keys(root)) {
371
+ enforced_alias[key] = true;
372
+ }
349
373
 
350
374
  const allow = new Set([
351
- kit.files.lib,
352
375
  kit.files.routes,
376
+ kit.files.src,
353
377
  kit.outDir,
354
378
  path.resolve(root, kit.files.src),
355
379
  path.resolve(root, 'node_modules'),
356
- path.resolve(process.cwd(), 'node_modules')
380
+ // ensures that the client entry is served even if it's located outside
381
+ // the local node_modules, such as the pnpm global virtual store
382
+ runtime_directory,
383
+ path.resolve('node_modules'),
384
+ // include the directory that contains the workspaces declaration
385
+ // which usually also contains hoisted packages
386
+ // see https://vite.dev/guide/api-javascript#searchforworkspaceroot
387
+ path.resolve(vite.searchForWorkspaceRoot(process.cwd()), 'node_modules')
357
388
  ]);
358
389
 
390
+ // Add directories from `#`-prefixed package.json imports to the allow list
391
+ for (const { path: alias_path } of normalized_aliases) {
392
+ allow.add(alias_path);
393
+ }
394
+
359
395
  // We can only add directories to the allow list, so we find out
360
396
  // if there's a client hooks file and pass its directory
361
397
  const client_hooks = resolve_entry(kit.files.hooks.client);
@@ -370,6 +406,7 @@ function kit({ svelte_config, adapter }) {
370
406
  alias: [
371
407
  { find: '__SERVER__', replacement: `${generated}/server` },
372
408
  { find: '$app', replacement: `${runtime_directory}/app` },
409
+ { find: '$env', replacement: `${runtime_directory}/env` },
373
410
  ...get_config_aliases(kit, root)
374
411
  ]
375
412
  },
@@ -381,7 +418,7 @@ function kit({ svelte_config, adapter }) {
381
418
  sourcemapIgnoreList,
382
419
  watch: {
383
420
  ignored: [
384
- // Ignore all siblings of config.kit.outDir/generated
421
+ // Ignore all siblings of config.outDir/generated
385
422
  `${out_dir}/!(generated)`
386
423
  ]
387
424
  }
@@ -443,7 +480,7 @@ function kit({ svelte_config, adapter }) {
443
480
  }
444
481
 
445
482
  const define = {
446
- __SVELTEKIT_APP_DIR__: s(kit.appDir),
483
+ __SVELTEKIT_APP_DIR__: s(posixify(kit.appDir)),
447
484
  __SVELTEKIT_APP_VERSION__: s(kit.version.name),
448
485
  __SVELTEKIT_EMBEDDED__: s(kit.embedded),
449
486
  __SVELTEKIT_FORK_PRELOADS__: s(kit.experimental.forkPreloads),
@@ -452,8 +489,10 @@ function kit({ svelte_config, adapter }) {
452
489
  __SVELTEKIT_PATHS_RELATIVE__: s(kit.paths.relative),
453
490
  __SVELTEKIT_CLIENT_ROUTING__: s(kit.router.resolution === 'client'),
454
491
  __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),
492
+ __SVELTEKIT_SERVER_TRACING_ENABLED__: s(kit.tracing.server),
493
+ __SVELTEKIT_SUPPORTS_ASYNC__: s(
494
+ svelte_config.compilerOptions?.experimental?.async ?? false
495
+ ),
457
496
  __SVELTEKIT_ROOT__: s(root),
458
497
  __SVELTEKIT_DEV__: s(!is_build)
459
498
  };
@@ -461,7 +500,7 @@ function kit({ svelte_config, adapter }) {
461
500
  if (is_build) {
462
501
  new_config.define = {
463
502
  ...define,
464
- __SVELTEKIT_ADAPTER_NAME__: s(adapter?.name),
503
+ __SVELTEKIT_ADAPTER_NAME__: s(kit.adapter?.name),
465
504
  __SVELTEKIT_APP_VERSION_FILE__: s(`${kit.appDir}/version.json`),
466
505
  __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: s(kit.version.pollInterval)
467
506
  };
@@ -471,16 +510,31 @@ function kit({ svelte_config, adapter }) {
471
510
  new_config.define = {
472
511
  ...define,
473
512
  __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0',
474
- __SVELTEKIT_PAYLOAD__: 'globalThis.__sveltekit_dev',
513
+ __SVELTEKIT_PAYLOAD__: kit_global, // only relevant when bundleStrategy !== 'split'
475
514
  __SVELTEKIT_HAS_SERVER_LOAD__: 'true',
476
515
  __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true'
477
516
  };
478
517
 
479
518
  // These Kit dependencies are packaged as CommonJS, which means they must always be externalized.
480
519
  // 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'
483
- ];
520
+ /** @type {NonNullable<UserConfig['ssr']>} */ (new_config.ssr).external = ['cookie'];
521
+ }
522
+
523
+ // Vite's `define` is a compile-time text replacement, but Vitest strips
524
+ // user `define` from the server config and reinstalls the values only as
525
+ // `globalThis` properties inside test workers, so anything
526
+ // that runs outside of a test will freak out over
527
+ // them not being defined
528
+ if (process.env.VITEST === 'true') {
529
+ for (const key in new_config.define) {
530
+ const value = new_config.define[key];
531
+ try {
532
+ /** @type {Record<string, any>} */ (globalThis)[key] = JSON.parse(value);
533
+ } catch {
534
+ // `kit_global` isn't JSON, so don't try to parse it. We may one day
535
+ // need to define it in Vitest somehow but for now, ignore it
536
+ }
537
+ }
484
538
  }
485
539
 
486
540
  warn_overridden_config(config, new_config);
@@ -494,6 +548,24 @@ function kit({ svelte_config, adapter }) {
494
548
  */
495
549
  configResolved(config) {
496
550
  vite_config = config;
551
+
552
+ const unsupported_plugins = vite_config.plugins.filter((plugin) => plugin.transformIndexHtml);
553
+ if (unsupported_plugins.length) {
554
+ const verbose = vite_config.logLevel === 'info';
555
+ const log = logger({ verbose });
556
+
557
+ const list = unsupported_plugins
558
+ .map((plugin) => ` - ${plugin.name || '(missing plugin name)'}`)
559
+ .join('\n');
560
+
561
+ log.warn(
562
+ dedent`
563
+ The following plugins may not work correctly because they use the \`transformIndexHtml\` hook which is not supported:
564
+
565
+ ${list}
566
+ `
567
+ );
568
+ }
497
569
  }
498
570
  };
499
571
 
@@ -507,10 +579,6 @@ function kit({ svelte_config, adapter }) {
507
579
  const plugin_virtual_modules = {
508
580
  name: 'vite-plugin-sveltekit-virtual-modules',
509
581
 
510
- applyToEnvironment(environment) {
511
- return environment.name !== 'serviceWorker';
512
- },
513
-
514
582
  async configResolved(config) {
515
583
  explicit_env_entry = resolve_explicit_env_entry(kit);
516
584
  explicit_env_config = await sync.env(kit, explicit_env_entry, config.root, config.mode);
@@ -546,52 +614,32 @@ function kit({ svelte_config, adapter }) {
546
614
  });
547
615
  },
548
616
 
549
- resolveId(id, importer) {
550
- if (id === '__sveltekit/manifest') {
551
- return `${out_dir}/generated/client-optimized/app.js`;
552
- }
553
-
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;
617
+ applyToEnvironment(environment) {
618
+ return environment.name !== 'serviceWorker';
619
+ },
565
620
 
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
- );
621
+ resolveId: {
622
+ filter: {
623
+ id: [exactRegex('$service-worker'), prefixRegex('__sveltekit/')]
624
+ },
625
+ handler(id) {
626
+ if (id === '__sveltekit/manifest') {
627
+ return `${out_dir}/generated/client-optimized/app.js`;
579
628
  }
580
- }
581
629
 
582
- if (id === '$service-worker') {
583
- // ids with :$ don't work with reverse proxies like nginx
584
- return `\0virtual:${id.substring(1)}`;
585
- }
630
+ if (id === '$service-worker') {
631
+ // ids with :$ don't work with reverse proxies like nginx
632
+ return `\0virtual:${id.substring(1)}`;
633
+ }
586
634
 
587
- if (id === '__sveltekit/remote') {
588
- return `${runtime_directory}/client/remote-functions/index.js`;
589
- }
635
+ if (id === '__sveltekit/remote') {
636
+ return `${runtime_directory}/client/remote-functions/index.js`;
637
+ }
590
638
 
591
- if (id.startsWith('__sveltekit/')) {
592
639
  return `\0virtual:${id}`;
593
640
  }
594
641
  },
642
+
595
643
  load: {
596
644
  filter: {
597
645
  id: [
@@ -605,22 +653,18 @@ function kit({ svelte_config, adapter }) {
605
653
  ]
606
654
  },
607
655
  handler(id) {
608
- const global = is_build
609
- ? `globalThis.__sveltekit_${version_hash}`
610
- : 'globalThis.__sveltekit_dev';
611
-
612
656
  switch (id) {
613
657
  case service_worker:
614
658
  return create_service_worker_module(svelte_config);
615
659
 
616
660
  case sveltekit_env:
617
- return create_sveltekit_env(explicit_env_config, env, explicit_env_entry);
661
+ return create_sveltekit_env(explicit_env_config, env, explicit_env_entry, !is_build);
618
662
 
619
663
  case sveltekit_env_public_client:
620
664
  return create_sveltekit_env_public(
621
665
  explicit_env_config,
622
666
  env,
623
- `const env = ${global}.env;`
667
+ `import { payload } from ${s(`${runtime_directory}/client/payload.js`)};\nconst env = payload.env;`
624
668
  );
625
669
 
626
670
  case sveltekit_env_public_server:
@@ -634,22 +678,30 @@ function kit({ svelte_config, adapter }) {
634
678
  return create_sveltekit_env_private(explicit_env_config, env);
635
679
 
636
680
  case sveltekit_env_service_worker:
637
- return create_sveltekit_env_service_worker_dev(explicit_env_config, env, global);
681
+ return is_build
682
+ ? create_sveltekit_env_service_worker(
683
+ explicit_env_config,
684
+ env,
685
+ kit_global,
686
+ kit.paths.base,
687
+ kit.appDir
688
+ )
689
+ : create_sveltekit_env_service_worker_dev(explicit_env_config, env, kit_global);
638
690
 
639
691
  case sveltekit_server: {
640
692
  return dedent`
641
- export let read_implementation = null;
693
+ export let read_implementation = null;
642
694
 
643
- export let manifest = null;
695
+ export let manifest = null;
644
696
 
645
- export function set_read_implementation(fn) {
646
- read_implementation = fn;
647
- }
697
+ export function set_read_implementation(fn) {
698
+ read_implementation = fn;
699
+ }
648
700
 
649
- export function set_manifest(_) {
650
- manifest = _;
651
- }
652
- `;
701
+ export function set_manifest(_) {
702
+ manifest = _;
703
+ }
704
+ `;
653
705
  }
654
706
  }
655
707
  }
@@ -658,11 +710,14 @@ function kit({ svelte_config, adapter }) {
658
710
 
659
711
  /** @type {Map<string, Set<string>>} */
660
712
  const import_map = new Map();
661
- const server_only_pattern = /.*\.server\..+/;
713
+ // Matches any ID that has .server. in its filename
714
+ const server_only_module_pattern = /\.server\.[^/]+$/;
715
+ // Matches any ID that has /server/ in its path
716
+ const server_only_directory_pattern = /\/server\//;
662
717
 
663
718
  /**
664
719
  * 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`
720
+ * whether in `*.server.js` files, `$app/server`, any `/server/` directory, or `$app/env/private`
666
721
  * @type {Plugin}
667
722
  */
668
723
  const plugin_guard = {
@@ -673,7 +728,8 @@ function kit({ svelte_config, adapter }) {
673
728
  enforce: 'pre',
674
729
 
675
730
  applyToEnvironment(environment) {
676
- return environment.name !== 'serviceWorker';
731
+ // the import map is only read for client-side violations in `load`, so skip other environments
732
+ return environment.config.consumer === 'client' && environment.name !== 'serviceWorker';
677
733
  },
678
734
 
679
735
  resolveId: {
@@ -688,7 +744,7 @@ function kit({ svelte_config, adapter }) {
688
744
  const resolved = await this.resolve(id, importer, { ...options, skipSelf: true });
689
745
 
690
746
  if (resolved) {
691
- const normalized = normalize_id(resolved.id, normalized_lib, normalized_cwd);
747
+ const normalized = normalize_id(resolved.id, normalized_aliases, normalized_cwd);
692
748
 
693
749
  let importers = import_map.get(normalized);
694
750
 
@@ -697,7 +753,7 @@ function kit({ svelte_config, adapter }) {
697
753
  import_map.set(normalized, importers);
698
754
  }
699
755
 
700
- importers.add(normalize_id(importer, normalized_lib, normalized_cwd));
756
+ importers.add(normalize_id(importer, normalized_aliases, normalized_cwd));
701
757
  }
702
758
  }
703
759
  }
@@ -708,30 +764,31 @@ function kit({ svelte_config, adapter }) {
708
764
  id: [
709
765
  exactRegex(app_server),
710
766
  exactRegex(app_env_private),
711
- /\/server\//,
712
- new RegExp(`${server_only_pattern.source}$`)
767
+ server_only_module_pattern,
768
+ server_only_directory_pattern
713
769
  ]
714
770
  },
715
771
  handler(id) {
716
- if (this.environment.config.consumer !== 'client') return;
717
-
718
772
  // skip .server.js files outside the cwd or in node_modules, as the filename might not mean 'server-only module' in this context
719
773
  const is_internal =
720
774
  id.startsWith(normalized_cwd) && !id.startsWith(normalized_node_modules);
721
775
 
722
- const normalized = normalize_id(id, normalized_lib, normalized_cwd);
776
+ const normalized = normalize_id(id, normalized_aliases, normalized_cwd);
777
+
778
+ // server-only directories: any file in a `/server/` folder inside the cwd,
779
+ // except those inside the routes or assets directories
780
+ const is_in_routes = id.startsWith(normalized_routes + '/');
781
+ const is_in_assets = id.startsWith(normalized_assets + '/');
782
+ const is_server_only_directory =
783
+ is_internal && !is_in_routes && !is_in_assets && server_only_directory_pattern.test(id);
723
784
 
724
785
  const is_server_only =
725
786
  normalized === '$app/env/private' ||
726
787
  normalized === '$app/server' ||
727
- normalized.startsWith('$lib/server/') ||
728
- (is_internal && server_only_pattern.test(path.basename(id)));
788
+ is_server_only_directory ||
789
+ (is_internal && server_only_module_pattern.test(id));
729
790
 
730
- // 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;
734
- }
791
+ if (!is_server_only) return;
735
792
 
736
793
  // in dev, this doesn't exist, so we need to create it
737
794
  manifest_data ??= sync.all(svelte_config, root).manifest_data;
@@ -746,49 +803,56 @@ function kit({ svelte_config, adapter }) {
746
803
  if (manifest_data.hooks.client) entrypoints.add(manifest_data.hooks.client);
747
804
  if (manifest_data.hooks.universal) entrypoints.add(manifest_data.hooks.universal);
748
805
 
749
- const chain = [normalized];
750
-
751
- let current = normalized;
752
- let includes_remote_file = false;
753
-
754
- while (true) {
806
+ // Walk up the import graph from the server-only module, looking for a chain
807
+ // that leads back to a client entrypoint. We search all candidates (not just
808
+ // the first) because a module can be imported by both server and client code,
809
+ // and a greedy first-match could follow a server-only branch that never
810
+ // reaches an entrypoint — see https://github.com/sveltejs/kit/issues/16232
811
+ /** @type {Set<string>} */
812
+ const visited = new Set([normalized]);
813
+
814
+ /**
815
+ * @param {string} current
816
+ * @param {string[]} chain
817
+ * @returns {string[] | null}
818
+ */
819
+ function find_chain(current, chain) {
755
820
  const importers = import_map.get(current);
756
- if (!importers) break;
821
+ if (!importers) return null;
757
822
 
758
- const candidates = Array.from(importers).filter((importer) => !chain.includes(importer));
759
- if (candidates.length === 0) break;
823
+ for (const importer of importers) {
824
+ if (visited.has(importer)) continue;
825
+ visited.add(importer);
760
826
 
761
- chain.push((current = candidates[0]));
827
+ const next_chain = [...chain, importer];
828
+ if (entrypoints.has(importer)) {
829
+ return next_chain;
830
+ }
831
+ const result = find_chain(importer, next_chain);
832
+ if (result) return result;
833
+ }
834
+ return null;
835
+ }
762
836
 
763
- includes_remote_file ||= svelte_config.kit.moduleExtensions.some((ext) => {
764
- return current.endsWith(`.remote${ext}`);
765
- });
837
+ const chain = find_chain(normalized, [normalized]);
766
838
 
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
- );
781
- }
839
+ if (chain) {
840
+ const pyramid = chain
841
+ .reverse()
842
+ .map((id, i) => {
843
+ return `${' '.repeat(i + 1)}${id}`;
844
+ })
845
+ .join(' imports\n');
782
846
 
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\`.`;
847
+ let message = `Cannot import ${normalized} into code that runs in the browser, as this could leak sensitive information.`;
848
+ message += `\n\n${pyramid}`;
849
+ message += `\n\nIf you're only using the import as a type, change it to \`import type\`.`;
786
850
 
787
- throw stackless(message);
788
- }
851
+ throw stackless(message);
789
852
  }
790
853
 
791
- throw new Error('An impossible situation occurred');
854
+ // No chain from this server-only module to a client entrypoint was found —
855
+ // the module is only imported from server code, which is valid.
792
856
  }
793
857
  }
794
858
  };
@@ -810,7 +874,7 @@ function kit({ svelte_config, adapter }) {
810
874
  name: 'vite-plugin-sveltekit-remote',
811
875
 
812
876
  applyToEnvironment(environment) {
813
- return environment.name !== 'serviceWorker';
877
+ return svelte_config.kit.experimental.remoteFunctions && environment.name !== 'serviceWorker';
814
878
  },
815
879
 
816
880
  // prevent other plugins from resolving our remote virtual module
@@ -828,10 +892,6 @@ function kit({ svelte_config, adapter }) {
828
892
  id: prefixRegex('\0sveltekit-remote:')
829
893
  },
830
894
  handler(id) {
831
- if (!kit.experimental.remoteFunctions) {
832
- return null;
833
- }
834
-
835
895
  // On-the-fly generated entry point for remote file just forwards the original module
836
896
  // We're not using manualChunks because it can cause problems with circular dependencies
837
897
  // (e.g. https://github.com/sveltejs/kit/issues/14679) and module ordering in general
@@ -844,19 +904,11 @@ function kit({ svelte_config, adapter }) {
844
904
  },
845
905
 
846
906
  configureServer(_dev_server) {
847
- if (!kit.experimental.remoteFunctions) {
848
- return;
849
- }
850
-
851
907
  dev_server = _dev_server;
852
908
  },
853
909
 
854
910
  async transform(code, id) {
855
- if (!kit.experimental.remoteFunctions) {
856
- return;
857
- }
858
-
859
- const normalized = normalize_id(id, normalized_lib, normalized_cwd);
911
+ const normalized = normalize_id(id, normalized_aliases, normalized_cwd);
860
912
  if (!svelte_config.kit.moduleExtensions.some((ext) => normalized.endsWith(`.remote${ext}`))) {
861
913
  return;
862
914
  }
@@ -881,7 +933,7 @@ function kit({ svelte_config, adapter }) {
881
933
  '\n\n' +
882
934
  dedent`
883
935
  import * as $$_self_$$ from './${path.basename(id)}';
884
- import { init_remote_functions as $$_init_$$ } from '@sveltejs/kit/internal';
936
+ import { init_remote_functions as $$_init_$$ } from '@sveltejs/kit/internal/server';
885
937
 
886
938
  ${dev_server ? 'await Promise.resolve()' : ''}
887
939
 
@@ -911,7 +963,7 @@ function kit({ svelte_config, adapter }) {
911
963
 
912
964
  // For the client, read the exports and create a new module that only contains fetch functions with the correct metadata
913
965
 
914
- /** @type {Map<string, import('types').RemoteInternals['type']>} */
966
+ /** @type {Map<string, RemoteInternals['type']>} */
915
967
  const map = new Map();
916
968
 
917
969
  // in dev, load the server module here (which will result in this hook
@@ -939,15 +991,17 @@ function kit({ svelte_config, adapter }) {
939
991
  }
940
992
  }
941
993
 
942
- let namespace = '__remote';
943
- let uid = 1;
944
- while (map.has(namespace)) namespace = `__remote${uid++}`;
945
-
946
- const exports = Array.from(map).map(([name, type]) => {
947
- return `export const ${name} = ${namespace}.${type}('${remote.hash}/${name}');`;
948
- });
994
+ const { namespace, declarations, reexports } = create_exported_declarations(
995
+ map.keys(),
996
+ (name, ns) => `${ns}.${map.get(name)}('${remote.hash}/${name}')`,
997
+ '__remote'
998
+ );
949
999
 
950
- let result = `import * as ${namespace} from '__sveltekit/remote';\n\n${exports.join('\n')}\n`;
1000
+ let result = `import * as ${namespace} from '__sveltekit/remote';\n\n${declarations.join('\n')}`;
1001
+ if (reexports.length > 0) {
1002
+ result += `\nexport { ${reexports.join(', ')} };`;
1003
+ }
1004
+ result += '\n';
951
1005
 
952
1006
  if (dev_server) {
953
1007
  result += `\nimport.meta.hot?.accept();\n`;
@@ -959,59 +1013,173 @@ function kit({ svelte_config, adapter }) {
959
1013
  }
960
1014
  };
961
1015
 
962
- /** @type {import('vite').Manifest} */
963
- let client_manifest;
964
- /** @type {import('types').Prerendered} */
1016
+ /** @type {Plugin} */
1017
+ const plugin_remote_guard = {
1018
+ name: 'vite-plugin-sveltekit-remote-guard',
1019
+
1020
+ applyToEnvironment() {
1021
+ return !svelte_config.kit.experimental.remoteFunctions;
1022
+ },
1023
+
1024
+ transform: {
1025
+ filter: {
1026
+ id: new RegExp(
1027
+ `.remote(${svelte_config.kit.moduleExtensions.join('|')})$`.replaceAll('.', '\\.')
1028
+ )
1029
+ },
1030
+ handler() {
1031
+ error_for_missing_config('remote functions', 'experimental.remoteFunctions', 'true');
1032
+ }
1033
+ }
1034
+ };
1035
+
1036
+ /** @type {Manifest} */
1037
+ let vite_server_manifest;
1038
+ /** @type {Manifest | null} */
1039
+ let vite_client_manifest = null;
1040
+ /** @type {Prerendered} */
965
1041
  let prerendered;
966
1042
 
967
1043
  /** @type {Set<string>} */
968
- let build;
1044
+ let build_files;
969
1045
  /** @type {string} */
970
1046
  let service_worker_code;
971
1047
 
972
1048
  /**
973
1049
  * Creates the service worker virtual modules
974
- * @type {import('vite').Plugin}
1050
+ * @type {Plugin}
975
1051
  */
976
1052
  const plugin_service_worker = {
977
1053
  name: 'vite-plugin-sveltekit-service-worker',
978
1054
 
1055
+ config(config) {
1056
+ service_worker_entry_file = resolve_entry(kit.files.serviceWorker);
1057
+ parsed_service_worker = path.parse(kit.files.serviceWorker);
1058
+
1059
+ if (!service_worker_entry_file) return;
1060
+
1061
+ service_worker_entry_file = posixify(service_worker_entry_file);
1062
+
1063
+ if (kit.paths.assets) {
1064
+ throw new Error('Cannot use service worker alongside config.paths.assets');
1065
+ }
1066
+
1067
+ const user_service_worker_output_config =
1068
+ config.environments?.serviceWorker?.build?.rolldownOptions?.output;
1069
+
1070
+ /** @type {import('vite').UserConfig} */
1071
+ const new_config = {
1072
+ environments: {
1073
+ serviceWorker: {
1074
+ build: {
1075
+ modulePreload: false,
1076
+ rolldownOptions: {
1077
+ external: [`${kit.paths.base}/${kit.appDir}/env.js`],
1078
+ input: {
1079
+ 'service-worker': service_worker_entry_file
1080
+ },
1081
+ output: {
1082
+ format: 'es',
1083
+ entryFileNames: 'service-worker.js',
1084
+ assetFileNames: `${kit.appDir}/immutable/assets/[name].[hash][extname]`,
1085
+ codeSplitting:
1086
+ (Array.isArray(user_service_worker_output_config)
1087
+ ? user_service_worker_output_config[0].codeSplitting
1088
+ : user_service_worker_output_config?.codeSplitting) ?? false
1089
+ }
1090
+ },
1091
+ outDir: `${out}/client`,
1092
+ minify: initial_config.build?.minify,
1093
+ // avoid overwriting the client build Vite manifest
1094
+ manifest: '.vite/service-worker-manifest.json'
1095
+ },
1096
+ consumer: 'client'
1097
+ }
1098
+ }
1099
+ };
1100
+
1101
+ warn_overridden_config(config, new_config);
1102
+
1103
+ return new_config;
1104
+ },
1105
+
1106
+ // our serviceWorker environment only exists when building because Vite only
1107
+ // supports the default client environment during development (for now)
979
1108
  applyToEnvironment(environment) {
980
1109
  return environment.name === 'serviceWorker';
981
1110
  },
982
1111
 
983
- resolveId: {
984
- 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
- }
1112
+ resolveId(id, importer) {
1113
+ // If importing from a service-worker, only allow $service-worker & $app/env/public, but none of the other virtual modules.
1114
+ // This check won't catch transitive imports, but it will warn when the import comes from a service-worker directly.
1115
+ // Transitive imports will be caught during the build.
1116
+ if (importer) {
1117
+ const parsed_importer = path.parse(importer);
989
1118
 
990
- if (id.startsWith('__sveltekit')) {
991
- return `\0virtual:${id}`;
1119
+ const importer_is_service_worker =
1120
+ parsed_importer.dir === parsed_service_worker.dir &&
1121
+ parsed_importer.name === parsed_service_worker.name;
1122
+
1123
+ if (
1124
+ importer_is_service_worker &&
1125
+ id !== '$service-worker' &&
1126
+ id !== 'virtual:$app/env/public' &&
1127
+ id !== '__sveltekit/env/service-worker'
1128
+ ) {
1129
+ throw new Error(
1130
+ `Cannot import ${normalize_id(
1131
+ id,
1132
+ normalized_aliases,
1133
+ normalized_cwd
1134
+ )} into service-worker code. Only the modules $service-worker and $app/env/public are available in service workers.`
1135
+ );
992
1136
  }
993
1137
  }
1138
+
1139
+ if (id.startsWith('$app/') || id === '$service-worker') {
1140
+ // ids with :$ don't work with reverse proxies like nginx
1141
+ return `\0virtual:${id.substring(1)}`;
1142
+ }
1143
+
1144
+ if (id.startsWith('__sveltekit')) {
1145
+ return `\0virtual:${id}`;
1146
+ }
994
1147
  },
995
1148
 
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
- }
1149
+ load: {
1150
+ filter: {
1151
+ id: [prefixRegex('\0virtual:')]
1152
+ },
1153
+ handler(id) {
1154
+ if (!build_files) {
1155
+ build_files = new Set();
1156
+ const manifest = vite_client_manifest ?? vite_server_manifest;
1157
+ for (const key in manifest) {
1158
+ const { file, css = [], assets = [] } = manifest[key];
1159
+ build_files.add(file);
1160
+ css.forEach((file) => build_files.add(file));
1161
+ assets.forEach((file) => build_files.add(file));
1162
+ }
1005
1163
 
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('/')";
1164
+ if (kit.output.bundleStrategy === 'inline') {
1165
+ // the bundle and stylesheet are inlined into the page and their files
1166
+ // deleted, so they must not appear in the list of cacheable assets
1167
+ for (const file of build_files) {
1168
+ if (!fs.existsSync(`${out}/client/${file}`)) {
1169
+ build_files.delete(file);
1170
+ }
1171
+ }
1172
+ }
1009
1173
 
1010
- service_worker_code = dedent`
1174
+ // in a service worker, `location` is the location of the service worker itself,
1175
+ // which is guaranteed to be `<base>/service-worker.js`
1176
+ const base = "location.pathname.split('/').slice(0, -1).join('/')";
1177
+
1178
+ service_worker_code = dedent`
1011
1179
  export const base = /*@__PURE__*/ ${base};
1012
1180
 
1013
1181
  export const build = [
1014
- ${Array.from(build)
1182
+ ${Array.from(build_files)
1015
1183
  .map((file) => `base + ${s(`/${file}`)}`)
1016
1184
  .join(',\n')}
1017
1185
  ];
@@ -1028,65 +1196,74 @@ function kit({ svelte_config, adapter }) {
1028
1196
  ];
1029
1197
 
1030
1198
  export const version = ${s(kit.version.name)};
1031
- `;
1032
- }
1033
-
1034
- if (!id.startsWith('\0virtual:')) return;
1199
+ `;
1200
+ }
1035
1201
 
1036
- const global = is_build
1037
- ? `globalThis.__sveltekit_${version_hash}`
1038
- : 'globalThis.__sveltekit_dev';
1202
+ if (id === service_worker) {
1203
+ return service_worker_code;
1204
+ }
1039
1205
 
1040
- if (id === service_worker) {
1041
- return service_worker_code;
1042
- }
1206
+ if (id === sveltekit_env_service_worker) {
1207
+ return is_build
1208
+ ? create_sveltekit_env_service_worker(
1209
+ explicit_env_config,
1210
+ env,
1211
+ kit_global,
1212
+ kit.paths.base,
1213
+ kit.appDir
1214
+ )
1215
+ : create_sveltekit_env_service_worker_dev(explicit_env_config, env, kit_global);
1216
+ }
1043
1217
 
1044
- if (id === sveltekit_env_service_worker) {
1045
- return create_sveltekit_env_service_worker_dev(explicit_env_config, env, global);
1046
- }
1218
+ if (id === sveltekit_env_public_client) {
1219
+ return create_sveltekit_env_public(
1220
+ explicit_env_config,
1221
+ env,
1222
+ `const env = ${kit_global}.env;`
1223
+ );
1224
+ }
1047
1225
 
1048
- if (id === sveltekit_env_public_client) {
1049
- return create_sveltekit_env_public(explicit_env_config, env, `const env = ${global}.env;`);
1226
+ const sw_normalized_cwd = vite.normalizePath(vite_config.root);
1227
+ const sw_normalized_aliases = get_import_aliases(
1228
+ vite_config.root,
1229
+ vite.normalizePath.bind(vite)
1230
+ );
1231
+ const relative = normalize_id(id, sw_normalized_aliases, sw_normalized_cwd);
1232
+ const stripped = strip_virtual_prefix(relative);
1233
+ throw new Error(
1234
+ `Cannot import ${stripped} into service-worker code. Only the modules $service-worker and $app/env/public are available in service workers.`
1235
+ );
1050
1236
  }
1051
-
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
- );
1059
1237
  }
1060
1238
  };
1061
1239
 
1062
1240
  /** @type {Plugin} */
1063
1241
  const plugin_service_worker_env = {
1064
1242
  name: 'vite-plugin-sveltekit-service-worker-env',
1065
-
1066
- 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
1075
- return {
1076
- code: `import '__sveltekit/env/service-worker';\n${code}`
1077
- };
1078
- }
1243
+ applyToEnvironment(environment) {
1244
+ return !!service_worker_entry_file && environment.config.consumer === 'client';
1245
+ },
1246
+ transform(code, id) {
1247
+ if (id !== service_worker_entry_file) return;
1248
+
1249
+ // prepend the service worker with an import that configures
1250
+ // `env`, in case `$app/env/public` is imported. In production
1251
+ // this is required: dynamic public env vars aren't known at
1252
+ // build time, so `env.js` is loaded at runtime. In dev, the
1253
+ // imported module just inlines the current values instead.
1254
+ return {
1255
+ code: `import '__sveltekit/env/service-worker';\n${code}`
1256
+ };
1079
1257
  }
1080
1258
  };
1081
1259
 
1260
+ /** @type {() => Promise<void> | undefined} */
1261
+ let finalise;
1262
+
1082
1263
  /** @type {Plugin} */
1083
1264
  const plugin_compile = {
1084
1265
  name: 'vite-plugin-sveltekit-compile',
1085
1266
 
1086
- applyToEnvironment(environment) {
1087
- return environment.name !== 'serviceWorker';
1088
- },
1089
-
1090
1267
  /**
1091
1268
  * Build the SvelteKit-provided Vite config to be merged with the user's vite.config.js file.
1092
1269
  * @see https://vitejs.dev/guide/api-plugin.html#config
@@ -1099,12 +1276,13 @@ function kit({ svelte_config, adapter }) {
1099
1276
  let new_config;
1100
1277
 
1101
1278
  if (is_build) {
1102
- const prefix = `${kit.appDir}/immutable`;
1279
+ const app_immutable = `${kit.appDir}/immutable`;
1103
1280
 
1104
1281
  /** @type {Record<string, string>} */
1105
1282
  const server_input = {
1106
1283
  index: `${runtime_directory}/server/index.js`,
1107
1284
  internal: `${out_dir}/generated/server/internal.js`,
1285
+ env: '__sveltekit/env',
1108
1286
  ['remote-entry']: `${runtime_directory}/app/server/remote/index.js`
1109
1287
  };
1110
1288
 
@@ -1133,11 +1311,10 @@ function kit({ svelte_config, adapter }) {
1133
1311
  }
1134
1312
  });
1135
1313
 
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
- });
1314
+ // ...and the params file
1315
+ if (manifest_data.params) {
1316
+ server_input['entries/params'] = path.resolve(root, manifest_data.params);
1317
+ }
1141
1318
 
1142
1319
  // ...and the hooks files
1143
1320
  if (manifest_data.hooks.server) {
@@ -1155,15 +1332,8 @@ function kit({ svelte_config, adapter }) {
1155
1332
  path.join(kit.files.src, 'instrumentation.server')
1156
1333
  );
1157
1334
  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
- );
1335
+ if (kit.adapter && !kit.adapter.supports?.instrumentation?.()) {
1336
+ throw new Error(`${server_instrumentation} is unsupported in ${kit.adapter.name}.`);
1167
1337
  }
1168
1338
  server_input['instrumentation.server'] = server_instrumentation;
1169
1339
  }
@@ -1175,6 +1345,7 @@ function kit({ svelte_config, adapter }) {
1175
1345
  client_input['bundle'] = `${runtime_directory}/client/bundle.js`;
1176
1346
  } else {
1177
1347
  client_input['entry/start'] = `${runtime_directory}/client/entry.js`;
1348
+ client_input['entry/payload'] = `${runtime_directory}/client/payload.js`;
1178
1349
  client_input['entry/app'] = `${out_dir}/generated/client-optimized/app.js`;
1179
1350
  manifest_data.nodes.forEach((node, i) => {
1180
1351
  if (node.component || node.universal) {
@@ -1185,20 +1356,25 @@ function kit({ svelte_config, adapter }) {
1185
1356
 
1186
1357
  const inline = svelte_config.kit.output.bundleStrategy === 'inline';
1187
1358
 
1188
- const config_base = assets_base(kit);
1189
-
1190
1359
  /** @type {string} */
1191
- const base = kit.paths.assets || kit.paths.base || '/';
1192
- const root_to_assets = prefix + '/assets/';
1360
+ const base = (kit.paths.assets || kit.paths.base) + '/';
1361
+ const root_to_assets = app_immutable + '/assets/';
1193
1362
  const assets_to_root =
1194
- prefix
1363
+ app_immutable
1195
1364
  .split('/')
1196
1365
  .map(() => '..')
1197
1366
  .join('/') + '/../';
1198
1367
 
1368
+ const relative = kit.paths.relative !== false || !!kit.paths.assets;
1369
+
1199
1370
  new_config = {
1200
1371
  appType: 'custom',
1201
- base: config_base,
1372
+ // Affects how Vite loads JS assets on the client.
1373
+ // If the initial HTML we render uses an absolute path for assets,
1374
+ // the additional chunks Vite loads must also use an absolute path.
1375
+ // Otherwise, you end up with additional chunks being loaded relative
1376
+ // to the current chunk rather than the root.
1377
+ base: relative ? './' : base,
1202
1378
  build: {
1203
1379
  cssCodeSplit: !inline,
1204
1380
  cssMinify:
@@ -1207,7 +1383,7 @@ function kit({ svelte_config, adapter }) {
1207
1383
  rolldownOptions: {
1208
1384
  output: {
1209
1385
  name: `__sveltekit_${version_hash}.app`,
1210
- assetFileNames: `${prefix}/assets/[name].[hash][extname]`,
1386
+ assetFileNames: `${app_immutable}/assets/[name].[hash][extname]`,
1211
1387
  hoistTransitiveImports: false,
1212
1388
  sourcemapIgnoreList
1213
1389
  },
@@ -1246,7 +1422,7 @@ function kit({ svelte_config, adapter }) {
1246
1422
  }
1247
1423
  }
1248
1424
  },
1249
- // during the initial server build we don't know yet
1425
+ // these are stubs that will be replaced after the initial server build
1250
1426
  define: {
1251
1427
  __SVELTEKIT_HAS_SERVER_LOAD__: 'true',
1252
1428
  __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true',
@@ -1260,9 +1436,10 @@ function kit({ svelte_config, adapter }) {
1260
1436
  input: inline ? client_input['bundle'] : client_input,
1261
1437
  output: {
1262
1438
  format: inline ? 'iife' : 'esm',
1263
- entryFileNames: `${prefix}/[name].[hash].js`,
1264
- chunkFileNames: `${prefix}/chunks/[hash].js`,
1265
- codeSplitting: svelte_config.kit.output.bundleStrategy === 'split'
1439
+ entryFileNames: `${app_immutable}/[name].[hash].js`,
1440
+ chunkFileNames: `${app_immutable}/chunks/[hash].js`,
1441
+ codeSplitting:
1442
+ svelte_config.kit.output.bundleStrategy === 'split' ? undefined : false
1266
1443
  },
1267
1444
  // This silences Rolldown warnings about not supporting `import.meta`
1268
1445
  // for the `iife` output format. We don't care because it's
@@ -1277,68 +1454,42 @@ function kit({ svelte_config, adapter }) {
1277
1454
  }
1278
1455
  },
1279
1456
  define: {
1280
- __SVELTEKIT_PAYLOAD__: `globalThis.__sveltekit_${version_hash}`
1457
+ __SVELTEKIT_PAYLOAD__:
1458
+ svelte_config.kit.output.bundleStrategy !== 'split' ? kit_global : 'undefined'
1281
1459
  }
1282
1460
  }
1283
1461
  },
1284
1462
  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
- }
1463
+ // Allows us to use relative paths in as many places as we can
1464
+ renderBuiltUrl(filename, { ssr, hostType }) {
1465
+ if (hostType === 'js') {
1466
+ // SSR builds should use an absolute path in JS modules to
1467
+ // match the default Vite behaviour
1468
+ if (ssr) return base + filename;
1469
+
1470
+ // We could always use a relative asset base path here, but it's better for performance not to.
1471
+ // E.g. Vite generates `new URL('/asset.png', import.meta).href` for a relative path vs just '/asset.png'.
1472
+ // That's larger and takes longer to run and also causes an HTML diff between SSR and client
1473
+ // causing us to do a more expensive hydration check.
1474
+ return { relative };
1475
+ }
1476
+
1477
+ if (!relative) return;
1478
+
1479
+ // ensure assets loaded by CSS files are loaded relative to the
1480
+ // CSS file rather than the default of relative to the root
1481
+
1482
+ // _app/immutable/assets files
1483
+ if (filename.startsWith(root_to_assets)) {
1484
+ return `./${filename.slice(root_to_assets.length)}`;
1485
+ }
1486
+
1487
+ // static dir files
1488
+ return assets_to_root + filename;
1489
+ }
1316
1490
  },
1317
1491
  publicDir: kit.files.assets
1318
1492
  };
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
1493
  } else {
1343
1494
  new_config = {
1344
1495
  appType: 'custom',
@@ -1366,16 +1517,20 @@ function kit({ svelte_config, adapter }) {
1366
1517
  * Adds the SvelteKit middleware to do SSR in dev mode.
1367
1518
  * @see https://vitejs.dev/guide/api-plugin.html#configureserver
1368
1519
  */
1369
- async configureServer(vite) {
1370
- return await dev(vite, vite_config, svelte_config, () => remotes, root, adapter);
1520
+ async configureServer(server) {
1521
+ return await dev(server, vite_config, svelte_config, () => remotes, root);
1371
1522
  },
1372
1523
 
1373
1524
  /**
1374
1525
  * Adds the SvelteKit middleware to do SSR in preview mode.
1375
1526
  * @see https://vitejs.dev/guide/api-plugin.html#configurepreviewserver
1376
1527
  */
1377
- configurePreviewServer(vite) {
1378
- return preview(vite, vite_config, svelte_config, adapter);
1528
+ configurePreviewServer(server) {
1529
+ return preview(server, vite_config, svelte_config);
1530
+ },
1531
+
1532
+ applyToEnvironment(environment) {
1533
+ return environment.name !== 'serviceWorker';
1379
1534
  },
1380
1535
 
1381
1536
  renderChunk(code, chunk) {
@@ -1409,17 +1564,23 @@ function kit({ svelte_config, adapter }) {
1409
1564
  }
1410
1565
  mkdirp(out);
1411
1566
 
1412
- const server_bundle = /** @type {import('vite').Rolldown.RolldownOutput} */ (
1567
+ await load_and_validate_params({
1568
+ routes: manifest_data.routes,
1569
+ params_path: manifest_data.params,
1570
+ root
1571
+ });
1572
+
1573
+ const { output: server_chunks } = /** @type {Rolldown.RolldownOutput} */ (
1413
1574
  await builder.build(builder.environments.ssr)
1414
1575
  );
1415
1576
 
1416
- const verbose = vite_config.logLevel === 'info';
1577
+ const verbose = builder.config.logLevel === 'info';
1417
1578
  const log = logger({ verbose });
1418
1579
 
1419
- /** @type {import('vite').Manifest} */
1420
- const server_manifest = JSON.parse(read(`${out}/server/.vite/manifest.json`));
1580
+ /** @type {Manifest} */
1581
+ vite_server_manifest = JSON.parse(read(`${out}/server/.vite/manifest.json`));
1421
1582
 
1422
- /** @type {import('types').BuildData} */
1583
+ /** @type {BuildData} */
1423
1584
  const build_data = {
1424
1585
  app_dir: kit.appDir,
1425
1586
  app_path: `${kit.paths.base.slice(1)}${kit.paths.base ? '/' : ''}${kit.appDir}`,
@@ -1427,7 +1588,7 @@ function kit({ svelte_config, adapter }) {
1427
1588
  out_dir: out,
1428
1589
  service_worker: service_worker_entry_file ? 'service-worker.js' : null, // TODO make file configurable?
1429
1590
  client: null,
1430
- server_manifest
1591
+ server_manifest: vite_server_manifest
1431
1592
  };
1432
1593
 
1433
1594
  const manifest_path = `${out}/server/manifest-full.js`;
@@ -1443,18 +1604,30 @@ function kit({ svelte_config, adapter }) {
1443
1604
  })};\n`
1444
1605
  );
1445
1606
 
1607
+ const assets_path = `${kit.appDir}/immutable/assets`;
1608
+
1609
+ // first, build server nodes without the client manifest so we can analyse it
1610
+ build_server_nodes(
1611
+ out,
1612
+ kit,
1613
+ manifest_data,
1614
+ vite_server_manifest,
1615
+ null,
1616
+ assets_path,
1617
+ server_chunks,
1618
+ root
1619
+ );
1620
+
1446
1621
  log.info('Analysing routes');
1447
1622
 
1448
1623
  const { metadata } = await analyse({
1449
1624
  hash: kit.router.type === 'hash',
1450
1625
  manifest_path,
1451
1626
  manifest_data,
1452
- server_manifest,
1627
+ server_manifest: vite_server_manifest,
1453
1628
  tracked_features,
1454
1629
  env,
1455
- out,
1456
1630
  remotes,
1457
- root,
1458
1631
  vite_config_file: vite_config.configFile
1459
1632
  });
1460
1633
 
@@ -1470,178 +1643,205 @@ function kit({ svelte_config, adapter }) {
1470
1643
  metadata.nodes
1471
1644
  );
1472
1645
 
1473
- const nodes = Object.values(
1474
- /** @type {import('types').ServerMetadata} */ (build_metadata).nodes
1646
+ const server_assets = `${out}/server/${assets_path}`;
1647
+ const client_assets = `${out}/client/${assets_path}`;
1648
+
1649
+ const skip_client_build = manifest_data.nodes.every(
1650
+ (node) => node.page_options?.csr === false
1475
1651
  );
1476
1652
 
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);
1653
+ if (!skip_client_build) {
1654
+ const nodes = Object.values(build_metadata.nodes);
1480
1655
 
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
- }
1656
+ // Through the finished analysis we can now check if any node has server or universal load functions
1657
+ const has_server_load = nodes.some((node) => node.has_server_load);
1658
+ const has_universal_load = nodes.some((node) => node.has_universal_load);
1487
1659
 
1488
- const { output: client_chunks } = /** @type {import('vite').Rolldown.RolldownOutput} */ (
1489
- await builder.build(builder.environments.client)
1490
- );
1660
+ if (builder.environments.client.config.define) {
1661
+ builder.environments.client.config.define.__SVELTEKIT_HAS_SERVER_LOAD__ =
1662
+ s(has_server_load);
1663
+ builder.environments.client.config.define.__SVELTEKIT_HAS_UNIVERSAL_LOAD__ =
1664
+ s(has_universal_load);
1665
+ }
1491
1666
 
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
- );
1667
+ const { output: client_chunks } = /** @type {Rolldown.RolldownOutput} */ (
1668
+ await builder.build(builder.environments.client)
1669
+ );
1504
1670
 
1505
- const assets_path = `${kit.appDir}/immutable/assets`;
1506
- const server_assets = `${out}/server/${assets_path}`;
1507
- const client_assets = `${out}/client/${assets_path}`;
1671
+ // We use `build.ssrEmitAssets` so that asset URLs created from
1672
+ // imports in server-only modules correspond to files in the build,
1673
+ // but we don't want to copy over CSS imports as these are already
1674
+ // accounted for in the client bundle. In most cases it would be
1675
+ // a no-op, but for SSR builds `url(...)` paths are handled
1676
+ // differently (relative for client, absolute for server)
1677
+ // resulting in different hashes, and thus duplication
1678
+ const ssr_stylesheets = new Set(
1679
+ Object.values(vite_server_manifest)
1680
+ .map((chunk) => chunk.css ?? [])
1681
+ .flat()
1682
+ );
1508
1683
 
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}`;
1684
+ if (fs.existsSync(server_assets)) {
1685
+ for (const file of fs.readdirSync(server_assets)) {
1686
+ const src = `${server_assets}/${file}`;
1687
+ const dest = `${client_assets}/${file}`;
1513
1688
 
1514
- if (fs.existsSync(dest) || ssr_stylesheets.has(`${assets_path}/${file}`)) {
1515
- continue;
1689
+ if (fs.existsSync(dest) || ssr_stylesheets.has(`${assets_path}/${file}`)) {
1690
+ continue;
1691
+ }
1692
+
1693
+ copy(src, dest);
1516
1694
  }
1695
+ }
1517
1696
 
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}`, '.');
1697
+ const vite_manifest = (vite_client_manifest = JSON.parse(
1698
+ read(`${out}/client/.vite/manifest.json`)
1699
+ ));
1523
1700
 
1524
- fs.writeFileSync(src, content);
1525
- }
1701
+ /**
1702
+ * @param {string} entry
1703
+ * @param {boolean} [add_dynamic_css]
1704
+ */
1705
+ const deps_of = (entry, add_dynamic_css = false) =>
1706
+ find_deps(vite_manifest, posixify(path.relative(root, entry)), add_dynamic_css, root);
1526
1707
 
1527
- copy(src, dest);
1528
- }
1529
- }
1708
+ const has_explicit_dynamic_public_env = Object.values(explicit_env_config ?? {}).some(
1709
+ (variable) => variable.public && !variable.static
1710
+ );
1530
1711
 
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(
1712
+ // the app only depends on runtime public env if it imports `$app/env/public`
1713
+ // *and* at least one public env var is actually dynamic (non-static)
1714
+ const uses_env_dynamic_public =
1715
+ has_explicit_dynamic_public_env &&
1716
+ client_chunks.some(
1552
1717
  (chunk) => chunk.type === 'chunk' && chunk.modules[sveltekit_env_public_client]
1553
- )
1554
- };
1555
-
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
- }
1572
- });
1573
- build_data.client.nodes = nodes.map((node) => node?.file);
1574
- build_data.client.css = nodes.map((node) => node?.css);
1575
-
1576
- build_data.client.routes = compact(
1577
- manifest_data.routes.map((route) => {
1578
- if (!route.page) return;
1579
-
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]
1589
- };
1590
- })
1591
1718
  );
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
1719
 
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
- )
1612
- );
1720
+ if (svelte_config.kit.output.bundleStrategy === 'split') {
1721
+ const start_entry = posixify(path.relative(root, `${runtime_directory}/client/entry.js`));
1722
+ const start = find_deps(vite_manifest, start_entry, false, root);
1723
+ const runtime_entry = resolve_symlinks(vite_manifest, start_entry, root).chunk
1724
+ .dynamicImports?.[0]; // client/entry.js dynamically imports client/client-entry.js
1725
+ if (!runtime_entry) throw new Error('Could not find the client runtime chunk');
1726
+ const runtime = find_deps(vite_manifest, runtime_entry, false, root);
1727
+ const app = deps_of(`${out_dir}/generated/client-optimized/app.js`);
1728
+
1729
+ build_data.client = {
1730
+ start: start.file,
1731
+ app: app.file,
1732
+ imports: Array.from(
1733
+ new Set([
1734
+ ...start.imports,
1735
+ runtime.file,
1736
+ ...runtime.imports,
1737
+ app.file,
1738
+ ...app.imports
1739
+ ])
1740
+ ),
1741
+ stylesheets: [...start.stylesheets, ...runtime.stylesheets, ...app.stylesheets],
1742
+ fonts: [...start.fonts, ...runtime.fonts, ...app.fonts],
1743
+ uses_env_dynamic_public
1744
+ };
1613
1745
 
1614
- build_data.client.inline = {
1615
- script: read(`${out}/client/${start.file}`),
1616
- style: /** @type {string | undefined} */ (style?.source)
1746
+ // In case of server-side route resolution, we create a purpose-built route manifest that is
1747
+ // similar to that on the client, with as much information computed upfront so that we
1748
+ // don't need to include any code of the actual routes in the server bundle.
1749
+ if (svelte_config.kit.router.resolution === 'server') {
1750
+ const nodes = manifest_data.nodes.map((node, i) => {
1751
+ if (node.component || node.universal) {
1752
+ const entry = `${out_dir}/generated/client-optimized/nodes/${i}.js`;
1753
+ const deps = deps_of(entry, true);
1754
+ const file = resolve_symlinks(
1755
+ vite_manifest,
1756
+ `${out_dir}/generated/client-optimized/nodes/${i}.js`,
1757
+ root
1758
+ ).chunk.file;
1759
+
1760
+ return { file, css: deps.stylesheets };
1761
+ }
1762
+ });
1763
+ build_data.client.nodes = nodes.map((node) => node?.file);
1764
+ build_data.client.css = nodes.map((node) => node?.css);
1765
+
1766
+ build_data.client.routes = compact(
1767
+ manifest_data.routes.map((route) => {
1768
+ if (!route.page) return;
1769
+
1770
+ return {
1771
+ id: route.id,
1772
+ pattern: route.pattern,
1773
+ params: route.params,
1774
+ layouts: route.page.layouts.map((l) =>
1775
+ l !== undefined ? [metadata.nodes[l].has_server_load, l] : undefined
1776
+ ),
1777
+ errors: route.page.errors,
1778
+ leaf: [metadata.nodes[route.page.leaf].has_server_load, route.page.leaf]
1779
+ };
1780
+ })
1781
+ );
1782
+ }
1783
+ } else {
1784
+ const start = deps_of(`${runtime_directory}/client/bundle.js`);
1785
+
1786
+ build_data.client = {
1787
+ start: start.file,
1788
+ imports: start.imports,
1789
+ stylesheets: start.stylesheets,
1790
+ fonts: start.fonts,
1791
+ uses_env_dynamic_public
1617
1792
  };
1793
+
1794
+ if (svelte_config.kit.output.bundleStrategy === 'inline') {
1795
+ const style = /** @type {Rolldown.OutputAsset} */ (
1796
+ client_chunks.find(
1797
+ (chunk) =>
1798
+ chunk.type === 'asset' &&
1799
+ chunk.names.length === 1 &&
1800
+ chunk.names[0] === 'style.css'
1801
+ )
1802
+ );
1803
+
1804
+ build_data.client.inline = {
1805
+ script: read(`${out}/client/${start.file}`),
1806
+ style: /** @type {string | undefined} */ (style?.source)
1807
+ };
1808
+
1809
+ // the bundle and stylesheet are inlined into the page, so the
1810
+ // emitted files are never loaded
1811
+ fs.unlinkSync(`${out}/client/${start.file}`);
1812
+ fs.rmSync(`${out}/client/${start.file}.map`, { force: true });
1813
+ if (style) fs.unlinkSync(`${out}/client/${style.fileName}`);
1814
+ }
1618
1815
  }
1619
- }
1620
1816
 
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
- );
1817
+ // regenerate manifest now that we have client entry...
1818
+ fs.writeFileSync(
1819
+ manifest_path,
1820
+ `export const manifest = ${generate_manifest({
1821
+ build_data,
1822
+ prerendered: [],
1823
+ relative_path: '.',
1824
+ routes: manifest_data.routes,
1825
+ remotes,
1826
+ root
1827
+ })};\n`
1828
+ );
1633
1829
 
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
- );
1830
+ // regenerate nodes with the client manifest...
1831
+ build_server_nodes(
1832
+ out,
1833
+ kit,
1834
+ manifest_data,
1835
+ vite_server_manifest,
1836
+ vite_client_manifest,
1837
+ assets_path,
1838
+ client_chunks,
1839
+ root
1840
+ );
1841
+ } else {
1842
+ copy(server_assets, client_assets);
1843
+ copy(kit.files.assets, `${out}/client`);
1844
+ }
1645
1845
 
1646
1846
  // ...and prerender
1647
1847
  const prerender_results = await prerender({
@@ -1661,7 +1861,7 @@ function kit({ svelte_config, adapter }) {
1661
1861
  remotes,
1662
1862
  metadata,
1663
1863
  process.cwd(),
1664
- server_bundle,
1864
+ server_chunks,
1665
1865
  vite_config.build.sourcemap
1666
1866
  );
1667
1867
 
@@ -1680,53 +1880,57 @@ function kit({ svelte_config, adapter }) {
1680
1880
  })};\n`
1681
1881
  );
1682
1882
 
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
- }
1687
-
1688
- log.info('Building service worker');
1883
+ // defer the adapt step to run after any buildApp hooks the adapter might have
1884
+ finalise = async () => {
1885
+ // defer creating the service worker too because other plugins might build
1886
+ // the client environment again and overwrite our service worker which
1887
+ // outputs to the same directory
1888
+ if (service_worker_entry_file) {
1889
+ log.info('Building service worker');
1890
+
1891
+ // mirror client settings that we couldn't set per environment in the config hook
1892
+ builder.environments.serviceWorker.config.define =
1893
+ builder.environments.client.config.define;
1894
+ builder.environments.serviceWorker.config.resolve.alias = [
1895
+ ...get_config_aliases(kit, vite_config.root)
1896
+ ];
1689
1897
 
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`
1898
+ // we have to overwrite this because it can't be configured per environment in the config hook
1899
+ builder.environments.serviceWorker.config.experimental.renderBuiltUrl = (filename) => {
1900
+ return {
1901
+ runtime: `new URL(${JSON.stringify(filename)}, location.href).pathname`
1902
+ };
1698
1903
  };
1699
- };
1700
-
1701
- await builder.build(builder.environments.serviceWorker);
1702
- }
1703
-
1704
- console.log(
1705
- `\nRun ${styleText(['bold', 'cyan'], 'npm run preview')} to preview your production build locally.`
1706
- );
1707
1904
 
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'));
1905
+ await builder.build(builder.environments.serviceWorker);
1906
+ }
1724
1907
 
1725
- const link = styleText(['bold', 'cyan'], 'https://svelte.dev/docs/kit/adapters');
1726
1908
  console.log(
1727
- `See ${link} to learn how to configure your app to run on the platform of your choosing`
1909
+ `\nRun ${styleText(['bold', 'cyan'], 'npm run preview')} to preview your production build locally.`
1728
1910
  );
1729
- }
1911
+
1912
+ if (kit.adapter) {
1913
+ const { adapt } = await import('../../core/adapt/index.js');
1914
+ await adapt(
1915
+ svelte_config,
1916
+ build_data,
1917
+ metadata,
1918
+ prerendered,
1919
+ prerender_results.prerender_map,
1920
+ log,
1921
+ remotes,
1922
+ vite_config,
1923
+ explicit_env_config
1924
+ );
1925
+ } else {
1926
+ log.warn('\nNo adapter specified');
1927
+
1928
+ const link = styleText(['bold', 'cyan'], 'https://svelte.dev/docs/kit/adapters');
1929
+ console.log(
1930
+ `See ${link} to learn how to configure your app to run on the platform of your choosing`
1931
+ );
1932
+ }
1933
+ };
1730
1934
  }
1731
1935
  };
1732
1936
 
@@ -1734,23 +1938,30 @@ function kit({ svelte_config, adapter }) {
1734
1938
  const plugin_adapter = {
1735
1939
  name: 'vite-plugin-sveltekit-adapter',
1736
1940
  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
1941
+ buildApp: {
1942
+ // this will run after any buildApp hooks provided by other Vite plugins
1943
+ // see https://vite.dev/guide/api-environment-frameworks#environments-during-build
1944
+ order: 'post',
1945
+ async handler() {
1946
+ await finalise?.();
1947
+ }
1741
1948
  }
1742
1949
  };
1743
1950
 
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);
1951
+ return /** @type {Plugin[]} */ (
1952
+ [
1953
+ svelte_config.kit.adapter?.vite?.plugins,
1954
+ plugin_setup,
1955
+ plugin_remote_guard,
1956
+ plugin_remote,
1957
+ plugin_virtual_modules,
1958
+ process.env.TEST !== 'true' ? plugin_guard : undefined,
1959
+ plugin_service_worker,
1960
+ plugin_service_worker_env,
1961
+ plugin_compile,
1962
+ plugin_adapter
1963
+ ].filter(Boolean)
1964
+ );
1754
1965
  }
1755
1966
 
1756
1967
  /**
@@ -1785,13 +1996,18 @@ function find_overridden_config(config, resolved_config, enforced_config, path,
1785
1996
  for (const key in enforced_config) {
1786
1997
  if (typeof config === 'object' && key in config && key in resolved_config) {
1787
1998
  const enforced = enforced_config[key];
1999
+ const resolved = resolved_config[key];
1788
2000
 
1789
2001
  if (enforced === true) {
1790
- if (config[key] !== resolved_config[key]) {
2002
+ // Normalize path separators before comparing to avoid false positives on Windows,
2003
+ // where config values like `root` may use backslashes while SvelteKit uses forward slashes.
2004
+ const a = typeof config[key] === 'string' ? posixify(config[key]) : config[key];
2005
+ const b = typeof resolved === 'string' ? posixify(resolved) : resolved;
2006
+ if (a !== b) {
1791
2007
  out.push(path + key);
1792
2008
  }
1793
2009
  } else {
1794
- find_overridden_config(config[key], resolved_config[key], enforced, path + key + '.', out);
2010
+ find_overridden_config(config[key], resolved, enforced, path + key + '.', out);
1795
2011
  }
1796
2012
  }
1797
2013
  }
@@ -1799,13 +2015,14 @@ function find_overridden_config(config, resolved_config, enforced_config, path,
1799
2015
  }
1800
2016
 
1801
2017
  /**
1802
- * @param {import('types').ValidatedConfig} config
2018
+ * @param {ValidatedConfig} config
1803
2019
  */
1804
2020
  const create_service_worker_module = (config) => dedent`
1805
2021
  if (typeof self === 'undefined' || self instanceof ServiceWorkerGlobalScope === false) {
1806
2022
  throw new Error('This module can only be imported inside a service worker');
1807
2023
  }
1808
2024
 
2025
+ export const base = location.pathname.split('/').slice(0, -1).join('/');
1809
2026
  export const build = [];
1810
2027
  export const files = [
1811
2028
  ${create_assets(config)