@sveltejs/kit 3.0.0-next.11 → 3.0.0-next.12

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 (61) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +8 -8
  3. package/src/constants.js +1 -1
  4. package/src/core/config/options.js +43 -35
  5. package/src/core/env.js +25 -11
  6. package/src/core/sync/sync.js +11 -14
  7. package/src/core/sync/utils.js +21 -1
  8. package/src/core/sync/{write_non_ambient.js → write_app_types.js} +23 -21
  9. package/src/core/sync/write_client_manifest.js +4 -12
  10. package/src/core/sync/write_env.js +6 -4
  11. package/src/core/sync/write_server.js +10 -15
  12. package/src/core/sync/write_tsconfig/index.js +245 -0
  13. package/src/core/sync/write_tsconfig/utils.js +162 -0
  14. package/src/core/sync/write_types/index.js +80 -88
  15. package/src/exports/internal/server/index.js +3 -1
  16. package/src/exports/public.d.ts +28 -51
  17. package/src/exports/vite/dev/index.js +88 -76
  18. package/src/exports/vite/index.js +364 -144
  19. package/src/exports/vite/module_ids.js +3 -1
  20. package/src/exports/vite/utils.js +1 -10
  21. package/src/runner.js +13 -0
  22. package/src/runtime/app/forms.js +4 -0
  23. package/src/runtime/app/manifest/index.js +1 -0
  24. package/src/runtime/app/paths/client.js +30 -30
  25. package/src/runtime/app/paths/internal/client.js +26 -0
  26. package/src/runtime/app/paths/server.js +22 -9
  27. package/src/runtime/app/paths/types.d.ts +11 -19
  28. package/src/runtime/app/server/remote/command.js +12 -7
  29. package/src/runtime/app/server/remote/form.js +29 -26
  30. package/src/runtime/app/server/remote/prerender.js +9 -2
  31. package/src/runtime/app/server/remote/query.js +5 -4
  32. package/src/runtime/app/server/remote/shared.js +48 -30
  33. package/src/runtime/app/service-worker/index.js +24 -0
  34. package/src/runtime/app/state/index.js +1 -1
  35. package/src/runtime/client/client.js +46 -9
  36. package/src/runtime/client/remote-functions/form.svelte.js +43 -51
  37. package/src/runtime/client/remote-functions/query/index.js +2 -2
  38. package/src/runtime/client/remote-functions/query-batch.svelte.js +2 -3
  39. package/src/runtime/client/remote-functions/query-live/iterator.js +5 -2
  40. package/src/runtime/client/remote-functions/shared.svelte.js +4 -1
  41. package/src/runtime/client/state.svelte.js +54 -21
  42. package/src/runtime/form-utils.js +89 -54
  43. package/src/runtime/server/cookie.js +1 -1
  44. package/src/runtime/server/data/index.js +31 -28
  45. package/src/runtime/server/errors.js +1 -1
  46. package/src/runtime/server/page/actions.js +3 -3
  47. package/src/runtime/server/page/render.js +19 -28
  48. package/src/runtime/server/remote-functions.js +66 -35
  49. package/src/runtime/server/respond.js +56 -14
  50. package/src/runtime/server/utils.js +10 -0
  51. package/src/types/ambient-private.d.ts +8 -0
  52. package/src/types/ambient.d.ts +22 -27
  53. package/src/types/global-private.d.ts +12 -0
  54. package/src/types/internal.d.ts +13 -3
  55. package/src/utils/url.js +12 -0
  56. package/src/version.js +1 -1
  57. package/types/index.d.ts +80 -98
  58. package/types/index.d.ts.map +4 -1
  59. package/src/core/sync/write_ambient.js +0 -18
  60. package/src/core/sync/write_tsconfig.js +0 -258
  61. /package/src/core/sync/{write_tsconfig_test → write_tsconfig/test-app}/package.json +0 -0
@@ -7,11 +7,13 @@ import fs from 'node:fs';
7
7
  import path from 'node:path';
8
8
  import process from 'node:process';
9
9
  import { styleText } from 'node:util';
10
+ import MagicString from 'magic-string';
10
11
  import { loadEnv } from 'vite';
11
12
  import { exactRegex, prefixRegex } from 'rolldown/filter';
12
13
 
13
14
  import { copy, mkdirp, read, resolve_entry, rimraf } from '../../utils/filesystem.js';
14
15
  import { posixify } from '../../utils/os.js';
16
+ import { to_fs } from '../../utils/vite.js';
15
17
  import {
16
18
  create_sveltekit_env,
17
19
  create_sveltekit_env_public,
@@ -22,13 +24,12 @@ import {
22
24
  create_exported_declarations
23
25
  } from '../../core/env.js';
24
26
  import * as sync from '../../core/sync/sync.js';
25
- import { create_assets } from '../../core/sync/create_manifest_data/index.js';
26
27
  import { load_and_validate_params } from '../../utils/params.js';
27
28
  import { runtime_directory, logger } from '../../core/utils.js';
28
29
  import { generate_manifest } from '../../core/generate_manifest/index.js';
29
30
  import { build_server_nodes } from './build/build_server.js';
30
31
  import { find_deps, resolve_symlinks } from './build/utils.js';
31
- import { dev } from './dev/index.js';
32
+ import { dev, invalidate_module } from './dev/index.js';
32
33
  import { preview } from './preview/index.js';
33
34
  import {
34
35
  error_for_missing_config,
@@ -36,8 +37,7 @@ import {
36
37
  normalize_id,
37
38
  remote_module_pattern,
38
39
  server_only_directory_pattern,
39
- server_only_module_pattern,
40
- strip_virtual_prefix
40
+ server_only_module_pattern
41
41
  } from './utils.js';
42
42
  import { stackless } from '../../utils/error.js';
43
43
  import { write_client_manifest } from '../../core/sync/write_client_manifest.js';
@@ -50,10 +50,10 @@ import { get_import_aliases, get_hash_import_keys } from '../../utils/imports.js
50
50
  import {
51
51
  app_env_private,
52
52
  app_server,
53
- service_worker,
54
53
  sveltekit_env,
55
54
  sveltekit_env_private,
56
55
  sveltekit_env_service_worker,
56
+ sveltekit_manifest_data,
57
57
  sveltekit_env_public_client,
58
58
  sveltekit_env_public_server
59
59
  } from './module_ids.js';
@@ -62,6 +62,7 @@ import { compact } from '../../utils/array.js';
62
62
  import { should_ignore, has_children } from './static_analysis/utils.js';
63
63
  import { process_config, split_config, validate_config } from '../../core/config/index.js';
64
64
  import { treeshake_prerendered_remotes } from './build/remote.js';
65
+ import { get_runner } from '../../runner.js';
65
66
 
66
67
  /**
67
68
  * The posix-ified root of the project based on the Vite configuration.
@@ -100,8 +101,7 @@ const enforced_config = {
100
101
  resolve: {
101
102
  alias: {
102
103
  $app: true,
103
- $env: true,
104
- '$service-worker': true
104
+ $env: true
105
105
  }
106
106
  }
107
107
  };
@@ -301,9 +301,6 @@ function kit({ svelte_config }) {
301
301
 
302
302
  /** @type {string | null} */
303
303
  let service_worker_entry_file;
304
- /** @type {import('node:path').ParsedPath} */
305
- let parsed_service_worker;
306
-
307
304
  /** @type {string} */
308
305
  let normalized_cwd;
309
306
  /** @type {Array<{ alias: string, path: string }>} */
@@ -472,27 +469,33 @@ function kit({ svelte_config }) {
472
469
  }
473
470
  };
474
471
 
475
- // treat .remote.js files as empty for the purposes of prebundling
472
+ // externalize .remote.js files to stop dependency tracing during prebundling
476
473
  if (kit.experimental.remoteFunctions) {
477
474
  // @ts-expect-error optimizeDeps is already set above
478
475
  new_config.optimizeDeps.rolldownOptions ??= {};
479
476
  // @ts-expect-error
480
477
  new_config.optimizeDeps.rolldownOptions.plugins ??= [];
481
478
  // @ts-expect-error
482
- new_config.optimizeDeps.rolldownOptions.plugins.push({
483
- name: 'vite-plugin-sveltekit-setup:optimize-remote-functions',
484
- load: {
485
- filter: { id: remote_module_pattern },
486
- handler() {
487
- return '';
479
+ new_config.optimizeDeps.rolldownOptions.plugins.push(
480
+ /** @type {Rolldown.Plugin} */ ({
481
+ name: 'vite-plugin-sveltekit-setup:optimize-remote-functions',
482
+ resolveId: {
483
+ filter: { id: remote_module_pattern },
484
+ async handler(id, importer) {
485
+ const resolved = await this.resolve(id, importer, { skipSelf: true });
486
+ if (!resolved) return { id, external: true };
487
+ // a servable /@fs url; 'absolute' stops rolldown relativizing it in the deps bundle
488
+ return { id: to_fs(resolved.id), external: 'absolute' };
489
+ }
488
490
  }
489
- }
490
- });
491
+ })
492
+ );
491
493
  }
492
494
 
493
495
  const define = {
494
496
  __SVELTEKIT_APP_DIR__: s(posixify(kit.appDir)),
495
497
  __SVELTEKIT_APP_VERSION__: s(kit.version.name),
498
+ __SVELTEKIT_APP_VERSION_CHECKS_ENABLED__: s(kit.output.bundleStrategy !== 'inline'),
496
499
  __SVELTEKIT_EMBEDDED__: s(kit.embedded),
497
500
  __SVELTEKIT_FORK_PRELOADS__: s(kit.experimental.forkPreloads),
498
501
  __SVELTEKIT_PATHS_ASSETS__: s(kit.paths.assets),
@@ -612,14 +615,10 @@ function kit({ svelte_config }) {
612
615
  );
613
616
 
614
617
  for (const id of [sveltekit_env, sveltekit_env_public_client]) {
615
- const module = server.moduleGraph.getModuleById(id);
616
-
617
- if (module) {
618
- server.moduleGraph.invalidateModule(module);
619
- }
618
+ invalidate_module(server, id);
620
619
  }
621
620
 
622
- server.ws.send({ type: 'full-reload' });
621
+ server.hot.send({ type: 'full-reload' });
623
622
  }
624
623
  });
625
624
  },
@@ -630,18 +629,13 @@ function kit({ svelte_config }) {
630
629
 
631
630
  resolveId: {
632
631
  filter: {
633
- id: [exactRegex('$service-worker'), prefixRegex('__sveltekit/')]
632
+ id: [prefixRegex('__sveltekit/')]
634
633
  },
635
634
  handler(id) {
636
635
  if (id === '__sveltekit/manifest') {
637
636
  return `${out_dir}/generated/client-optimized/app.js`;
638
637
  }
639
638
 
640
- if (id === '$service-worker') {
641
- // ids with :$ don't work with reverse proxies like nginx
642
- return `\0virtual:${id.substring(1)}`;
643
- }
644
-
645
639
  if (id === '__sveltekit/remote') {
646
640
  return `${runtime_directory}/client/remote-functions/index.js`;
647
641
  }
@@ -653,18 +647,18 @@ function kit({ svelte_config }) {
653
647
  load: {
654
648
  filter: {
655
649
  id: [
656
- exactRegex(service_worker),
657
650
  exactRegex(sveltekit_env),
658
651
  exactRegex(sveltekit_env_private),
659
652
  exactRegex(sveltekit_env_public_client),
660
653
  exactRegex(sveltekit_env_public_server),
661
- exactRegex(sveltekit_env_service_worker)
654
+ exactRegex(sveltekit_env_service_worker),
655
+ exactRegex(sveltekit_manifest_data)
662
656
  ]
663
657
  },
664
658
  handler(id) {
665
659
  switch (id) {
666
- case service_worker:
667
- return create_service_worker_module(svelte_config);
660
+ case sveltekit_manifest_data:
661
+ return create_manifest_data_module(is_build, manifest_data);
668
662
 
669
663
  case sveltekit_env:
670
664
  return create_sveltekit_env(explicit_env_config, env, explicit_env_entry, !is_build);
@@ -691,11 +685,17 @@ function kit({ svelte_config }) {
691
685
  ? create_sveltekit_env_service_worker(
692
686
  explicit_env_config,
693
687
  env,
688
+ kit.version.name,
694
689
  kit_global,
695
690
  kit.paths.base,
696
691
  kit.appDir
697
692
  )
698
- : create_sveltekit_env_service_worker_dev(explicit_env_config, env, kit_global);
693
+ : create_sveltekit_env_service_worker_dev(
694
+ explicit_env_config,
695
+ env,
696
+ kit.version.name,
697
+ kit_global
698
+ );
699
699
  }
700
700
  }
701
701
  }
@@ -718,7 +718,7 @@ function kit({ svelte_config }) {
718
718
 
719
719
  applyToEnvironment(environment) {
720
720
  // the import map is only read for client-side violations in `load`, so skip other environments
721
- return environment.config.consumer === 'client' && environment.name !== 'serviceWorker';
721
+ return environment.config.consumer === 'client';
722
722
  },
723
723
 
724
724
  resolveId: {
@@ -789,6 +789,10 @@ function kit({ svelte_config }) {
789
789
  if (manifest_data.hooks.client) entrypoints.add(manifest_data.hooks.client);
790
790
  if (manifest_data.hooks.universal) entrypoints.add(manifest_data.hooks.universal);
791
791
 
792
+ if (service_worker_entry_file) {
793
+ entrypoints.add(posixify(path.relative(root, service_worker_entry_file)));
794
+ }
795
+
792
796
  // Walk up the import graph from the server-only module, looking for a chain
793
797
  // that leads back to a client entrypoint. We search all candidates (not just
794
798
  // the first) because a module can be imported by both server and client code,
@@ -913,26 +917,28 @@ function kit({ svelte_config }) {
913
917
  if (this.environment.config.consumer !== 'client') {
914
918
  // we need to add an `await Promise.resolve()` because if the user imports this function
915
919
  // on the client AND in a load function when loading the client module we will trigger
916
- // an ssrLoadModule during dev. During a link preload, the module can be mistakenly
920
+ // an import during dev. During a link preload, the module can be mistakenly
917
921
  // loaded and transformed twice and the first time all its exports would be undefined
918
922
  // triggering a dev server error. By adding a microtask we ensure that the module is fully loaded
923
+ const ms = new MagicString(code);
919
924
 
920
925
  // Extra newlines to prevent syntax errors around missing semicolons or comments
921
- code +=
926
+ ms.append(
922
927
  '\n\n' +
923
- dedent`
924
- import * as $$_self_$$ from './${path.basename(id)}';
925
- import { init_remote_functions as $$_init_$$ } from '@sveltejs/kit/internal/server';
928
+ dedent`
929
+ import * as $$_self_$$ from './${path.basename(id)}';
930
+ import { init_remote_functions as $$_init_$$ } from '@sveltejs/kit/internal/server';
926
931
 
927
- ${dev_server ? 'await Promise.resolve()' : ''}
932
+ ${dev_server ? 'await Promise.resolve()' : ''}
928
933
 
929
- $$_init_$$($$_self_$$, ${s(file)}, ${s(remote.hash)});
934
+ $$_init_$$($$_self_$$, ${s(file)}, ${s(remote.hash)});
930
935
 
931
- for (const [name, fn] of Object.entries($$_self_$$)) {
932
- fn.__.id = ${s(remote.hash)} + '/' + name;
933
- fn.__.name = name;
934
- }
935
- `;
936
+ for (const [name, fn] of Object.entries($$_self_$$)) {
937
+ fn.__.id = ${s(remote.hash)} + '/' + name;
938
+ fn.__.name = name;
939
+ }
940
+ `
941
+ );
936
942
 
937
943
  // Emit a dedicated entry chunk for this remote in SSR builds (prod only)
938
944
  if (!dev_server) {
@@ -947,7 +953,10 @@ function kit({ svelte_config }) {
947
953
  }
948
954
  }
949
955
 
950
- return code;
956
+ return {
957
+ code: ms.toString(),
958
+ map: ms.generateMap({ hires: 'boundary' })
959
+ };
951
960
  }
952
961
 
953
962
  // For the client, read the exports and create a new module that only contains fetch functions with the correct metadata
@@ -959,7 +968,7 @@ function kit({ svelte_config }) {
959
968
  // being called again with `opts.ssr === true` if the module isn't
960
969
  // already loaded) so we can determine what it exports
961
970
  if (dev_server) {
962
- const module = await dev_server.ssrLoadModule(id);
971
+ const module = await get_runner(dev_server).import(id);
963
972
 
964
973
  for (const [name, value] of Object.entries(module)) {
965
974
  const type = value?.__?.type;
@@ -997,7 +1006,8 @@ function kit({ svelte_config }) {
997
1006
  }
998
1007
 
999
1008
  return {
1000
- code: result
1009
+ code: result,
1010
+ map: null
1001
1011
  };
1002
1012
  }
1003
1013
  }
@@ -1033,7 +1043,7 @@ function kit({ svelte_config }) {
1033
1043
  /** @type {Set<string>} */
1034
1044
  let build_files;
1035
1045
  /** @type {string} */
1036
- let service_worker_code;
1046
+ let manifest_data_code;
1037
1047
 
1038
1048
  /**
1039
1049
  * Creates the service worker virtual modules
@@ -1044,7 +1054,6 @@ function kit({ svelte_config }) {
1044
1054
 
1045
1055
  config(config) {
1046
1056
  service_worker_entry_file = resolve_entry(kit.files.serviceWorker);
1047
- parsed_service_worker = path.parse(kit.files.serviceWorker);
1048
1057
 
1049
1058
  if (!service_worker_entry_file) return;
1050
1059
 
@@ -1099,46 +1108,23 @@ function kit({ svelte_config }) {
1099
1108
  return environment.name === 'serviceWorker';
1100
1109
  },
1101
1110
 
1102
- resolveId(id, importer) {
1103
- // If importing from a service-worker, only allow $service-worker & $app/env/public, but none of the other virtual modules.
1104
- // This check won't catch transitive imports, but it will warn when the import comes from a service-worker directly.
1105
- // Transitive imports will be caught during the build.
1106
- if (importer) {
1107
- const parsed_importer = path.parse(importer);
1108
-
1109
- const importer_is_service_worker =
1110
- parsed_importer.dir === parsed_service_worker.dir &&
1111
- parsed_importer.name === parsed_service_worker.name;
1112
-
1113
- if (
1114
- importer_is_service_worker &&
1115
- id !== '$service-worker' &&
1116
- id !== 'virtual:$app/env/public' &&
1117
- id !== '__sveltekit/env/service-worker'
1118
- ) {
1119
- throw new Error(
1120
- `Cannot import ${normalize_id(
1121
- id,
1122
- normalized_aliases,
1123
- normalized_cwd
1124
- )} into service-worker code. Only the modules $service-worker and $app/env/public are available in service workers.`
1125
- );
1126
- }
1127
- }
1128
-
1129
- if (id.startsWith('$app/') || id === '$service-worker') {
1130
- // ids with :$ don't work with reverse proxies like nginx
1131
- return `\0virtual:${id.substring(1)}`;
1132
- }
1133
-
1134
- if (id.startsWith('__sveltekit')) {
1111
+ resolveId: {
1112
+ filter: {
1113
+ id: [prefixRegex('__sveltekit/')]
1114
+ },
1115
+ handler(id) {
1135
1116
  return `\0virtual:${id}`;
1136
1117
  }
1137
1118
  },
1138
1119
 
1139
1120
  load: {
1140
1121
  filter: {
1141
- id: [prefixRegex('\0virtual:')]
1122
+ id: [
1123
+ exactRegex('\0virtual:app/manifest'),
1124
+ exactRegex(sveltekit_manifest_data),
1125
+ exactRegex(sveltekit_env_service_worker),
1126
+ exactRegex(sveltekit_env_public_client)
1127
+ ]
1142
1128
  },
1143
1129
  handler(id) {
1144
1130
  if (!build_files) {
@@ -1161,36 +1147,33 @@ function kit({ svelte_config }) {
1161
1147
  }
1162
1148
  }
1163
1149
 
1164
- // in a service worker, `location` is the location of the service worker itself,
1165
- // which is guaranteed to be `<base>/service-worker.js`
1166
- const base = "location.pathname.split('/').slice(0, -1).join('/')";
1167
-
1168
- service_worker_code = dedent`
1169
- export const base = /*@__PURE__*/ ${base};
1170
-
1171
- export const build = [
1150
+ manifest_data_code = dedent`
1151
+ export const immutable = [
1172
1152
  ${Array.from(build_files)
1173
- .map((file) => `base + ${s(`/${file}`)}`)
1153
+ .map((file) => s({ path: file }))
1174
1154
  .join(',\n')}
1175
1155
  ];
1176
1156
 
1177
- export const files = [
1178
- ${manifest_data.assets
1179
- .filter((asset) => kit.serviceWorker.files(asset.file))
1180
- .map((asset) => `base + ${s(`/${asset.file}`)}`)
1181
- .join(',\n')}
1157
+ export const assets = [
1158
+ ${manifest_data.assets.map((asset) => s({ path: asset.file })).join(',\n')}
1182
1159
  ];
1183
1160
 
1184
1161
  export const prerendered = [
1185
- ${prerendered.paths.map((path) => `base + ${s(path.replace(kit.paths.base, ''))}`).join(',\n')}
1162
+ ${prerendered.paths.map((path) => s({ path: path.replace(kit.paths.base, '').slice(1) })).join(',\n')}
1186
1163
  ];
1187
1164
 
1188
- export const version = ${s(kit.version.name)};
1165
+ export const routes = [
1166
+ ${manifest_data.routes.map((route) => s({ id: route.id })).join(',\n')}
1167
+ ];
1189
1168
  `;
1190
1169
  }
1191
1170
 
1192
- if (id === service_worker) {
1193
- return service_worker_code;
1171
+ if (id === '\0virtual:app/manifest') {
1172
+ return `export { immutable, assets, prerendered, routes } from '__sveltekit/manifest-data';`;
1173
+ }
1174
+
1175
+ if (id === sveltekit_manifest_data) {
1176
+ return manifest_data_code;
1194
1177
  }
1195
1178
 
1196
1179
  if (id === sveltekit_env_service_worker) {
@@ -1198,11 +1181,17 @@ function kit({ svelte_config }) {
1198
1181
  ? create_sveltekit_env_service_worker(
1199
1182
  explicit_env_config,
1200
1183
  env,
1184
+ kit.version.name,
1201
1185
  kit_global,
1202
1186
  kit.paths.base,
1203
1187
  kit.appDir
1204
1188
  )
1205
- : create_sveltekit_env_service_worker_dev(explicit_env_config, env, kit_global);
1189
+ : create_sveltekit_env_service_worker_dev(
1190
+ explicit_env_config,
1191
+ env,
1192
+ kit.version.name,
1193
+ kit_global
1194
+ );
1206
1195
  }
1207
1196
 
1208
1197
  if (id === sveltekit_env_public_client) {
@@ -1212,16 +1201,31 @@ function kit({ svelte_config }) {
1212
1201
  `const env = ${kit_global}.env;`
1213
1202
  );
1214
1203
  }
1204
+ }
1205
+ },
1206
+
1207
+ generateBundle(_, bundle) {
1208
+ const invalid_modules = new Set();
1209
+ const modules = new Map([
1210
+ [`${runtime_directory}/app/forms.js`, '$app/forms'],
1211
+ [`${runtime_directory}/app/navigation.js`, '$app/navigation'],
1212
+ [`${runtime_directory}/app/state/index.js`, '$app/state']
1213
+ ]);
1214
+
1215
+ for (const output of Object.values(bundle)) {
1216
+ if (output.type !== 'chunk') continue;
1217
+
1218
+ for (const id of output.moduleIds) {
1219
+ const module = modules.get(id);
1220
+ if (module) invalid_modules.add(module);
1221
+ }
1222
+ }
1215
1223
 
1216
- const sw_normalized_cwd = vite.normalizePath(vite_config.root);
1217
- const sw_normalized_aliases = get_import_aliases(
1218
- vite_config.root,
1219
- vite.normalizePath.bind(vite)
1220
- );
1221
- const relative = normalize_id(id, sw_normalized_aliases, sw_normalized_cwd);
1222
- const stripped = strip_virtual_prefix(relative);
1224
+ if (invalid_modules.size > 0) {
1223
1225
  throw new Error(
1224
- `Cannot import ${stripped} into service-worker code. Only the modules $service-worker and $app/env/public are available in service workers.`
1226
+ `Cannot import ${Array.from(modules.values())
1227
+ .filter((module) => invalid_modules.has(module))
1228
+ .join(', ')} into service-worker code.`
1225
1229
  );
1226
1230
  }
1227
1231
  }
@@ -1233,17 +1237,19 @@ function kit({ svelte_config }) {
1233
1237
  applyToEnvironment(environment) {
1234
1238
  return !!service_worker_entry_file && environment.config.consumer === 'client';
1235
1239
  },
1236
- transform(code, id) {
1237
- if (id !== service_worker_entry_file) return;
1238
-
1239
- // prepend the service worker with an import that configures
1240
- // `env`, in case `$app/env/public` is imported. In production
1241
- // this is required: dynamic public env vars aren't known at
1242
- // build time, so `env.js` is loaded at runtime. In dev, the
1243
- // imported module just inlines the current values instead.
1244
- return {
1245
- code: `import '__sveltekit/env/service-worker';\n${code}`
1246
- };
1240
+ transform: {
1241
+ handler(code, id) {
1242
+ if (id !== service_worker_entry_file) return;
1243
+
1244
+ // prepend the service worker with an import that configures
1245
+ // `env`, in case `$app/env/public` is imported. In production
1246
+ // this is required: dynamic public env vars aren't known at
1247
+ // build time, so `env.js` is loaded at runtime. In dev, the
1248
+ // imported module just inlines the current values instead.
1249
+ return {
1250
+ code: `import '__sveltekit/env/service-worker';\n${code}`
1251
+ };
1252
+ }
1247
1253
  }
1248
1254
  };
1249
1255
 
@@ -1427,9 +1433,26 @@ function kit({ svelte_config }) {
1427
1433
  output: {
1428
1434
  format: inline ? 'iife' : 'esm',
1429
1435
  entryFileNames: `${app_immutable}/[name].[hash].js`,
1430
- chunkFileNames: `${app_immutable}/chunks/[hash].js`,
1436
+ chunkFileNames: (/** @type {Rolldown.PreRenderedChunk} */ chunk_info) => {
1437
+ // The manifest data chunk gets a fixed (non-hashed) filename so
1438
+ // that importers' content hashes are stable regardless of the
1439
+ // manifest content — this breaks the content-hash feedback loop
1440
+ if (chunk_info.name === 'sveltekit-manifest') {
1441
+ return `${kit.appDir}/manifest.js`;
1442
+ }
1443
+ return `${app_immutable}/chunks/[hash].js`;
1444
+ },
1431
1445
  codeSplitting:
1432
- svelte_config.kit.output.bundleStrategy === 'split' ? undefined : false
1446
+ svelte_config.kit.output.bundleStrategy === 'split'
1447
+ ? {
1448
+ groups: [
1449
+ {
1450
+ name: 'sveltekit-manifest',
1451
+ test: sveltekit_manifest_data
1452
+ }
1453
+ ]
1454
+ }
1455
+ : false
1433
1456
  },
1434
1457
  // This silences Rolldown warnings about not supporting `import.meta`
1435
1458
  // for the `iife` output format. We don't care because it's
@@ -1508,7 +1531,18 @@ function kit({ svelte_config }) {
1508
1531
  * @see https://vitejs.dev/guide/api-plugin.html#configureserver
1509
1532
  */
1510
1533
  async configureServer(server) {
1511
- return await dev(server, vite_config, svelte_config, () => remotes, root);
1534
+ return await dev(
1535
+ server,
1536
+ vite_config,
1537
+ svelte_config,
1538
+ () => remotes,
1539
+ root,
1540
+ (data) => {
1541
+ manifest_data = data;
1542
+ // Invalidate the manifest data module so it reloads with new routes/files
1543
+ invalidate_module(server, sveltekit_manifest_data);
1544
+ }
1545
+ );
1512
1546
  },
1513
1547
 
1514
1548
  /**
@@ -1564,6 +1598,15 @@ function kit({ svelte_config }) {
1564
1598
  await builder.build(builder.environments.ssr)
1565
1599
  );
1566
1600
 
1601
+ // Replace manifest placeholders in SSR output. `assets` and `routes`
1602
+ // are known from `manifest_data`. `immutable` and `prerendered` are not
1603
+ // known yet — they get sentinel strings that are replaced after
1604
+ // the client build and after prerendering respectively.
1605
+ replace_manifest_placeholder_variables(server_chunks, `${out}/server`, {
1606
+ assets: manifest_data.assets.map((asset) => ({ path: asset.file })),
1607
+ routes: manifest_data.routes.map((route) => ({ id: route.id }))
1608
+ });
1609
+
1567
1610
  const verbose = builder.config.logLevel === 'info';
1568
1611
  const log = logger({ verbose });
1569
1612
 
@@ -1688,6 +1731,42 @@ function kit({ svelte_config }) {
1688
1731
  read(`${out}/client/.vite/manifest.json`)
1689
1732
  ));
1690
1733
 
1734
+ // Replace manifest placeholders in client output. `immutable` is
1735
+ // computed from the Vite client manifest, `assets` and `routes`
1736
+ // from `manifest_data`. `prerendered` is left as a placeholder
1737
+ // for now — it's replaced after prerendering completes.
1738
+ /** @type {Set<string>} */
1739
+ const immutable = new Set();
1740
+
1741
+ /** @param {string} file */
1742
+ const add_immutable = (file) => {
1743
+ if (
1744
+ file.startsWith(`${kit.appDir}/immutable`) &&
1745
+ fs.existsSync(`${out}/client/${file}`)
1746
+ ) {
1747
+ immutable.add(file);
1748
+ }
1749
+ };
1750
+
1751
+ for (const key in vite_manifest) {
1752
+ const { file, css = [], assets = [] } = vite_manifest[key];
1753
+ add_immutable(file);
1754
+ css.forEach(add_immutable);
1755
+ assets.forEach(add_immutable);
1756
+ }
1757
+
1758
+ replace_manifest_placeholder_variables(client_chunks, `${out}/client`, {
1759
+ immutable: Array.from(immutable).map((file) => ({ path: file })),
1760
+ assets: manifest_data.assets.map((asset) => ({ path: asset.file })),
1761
+ routes: manifest_data.routes.map((route) => ({ id: route.id }))
1762
+ });
1763
+
1764
+ // Now that the client build is done, replace the `build` sentinel
1765
+ // in the SSR output with the real build files
1766
+ replace_manifest_placeholder_strings(`${out}/server`, {
1767
+ immutable: Array.from(immutable).map((file) => ({ path: file }))
1768
+ });
1769
+
1691
1770
  /**
1692
1771
  * @param {string} entry
1693
1772
  * @param {boolean} [add_dynamic_css]
@@ -1860,6 +1939,25 @@ function kit({ svelte_config }) {
1860
1939
 
1861
1940
  prerendered = prerender_results.prerendered;
1862
1941
 
1942
+ // Replace the `prerendered` sentinel in both SSR and client output
1943
+ // with the real prerendered paths. The other sentinels (`build`)
1944
+ // were already replaced after the client build.
1945
+ const prerendered_paths = prerendered.paths.map((p) => {
1946
+ return { path: p.replace(kit.paths.base, '').slice(1) };
1947
+ });
1948
+
1949
+ replace_manifest_placeholder_strings(`${out}/server`, { prerendered: prerendered_paths });
1950
+ replace_manifest_placeholder_strings(`${out}/client`, { prerendered: prerendered_paths });
1951
+
1952
+ // For `inline` strategy, the entry file was deleted and read into
1953
+ // `build_data.client.inline.script` — replace the sentinel there too
1954
+ if (build_data.client?.inline?.script) {
1955
+ build_data.client.inline.script = build_data.client.inline.script.replaceAll(
1956
+ '"__sveltekit_manifest_prerendered__"',
1957
+ JSON.stringify(prerendered_paths)
1958
+ );
1959
+ }
1960
+
1863
1961
  await treeshake_prerendered_remotes(
1864
1962
  vite,
1865
1963
  out,
@@ -2020,21 +2118,143 @@ function find_overridden_config(config, resolved_config, enforced_config, path,
2020
2118
  }
2021
2119
 
2022
2120
  /**
2023
- * @param {ValidatedConfig} config
2121
+ * Creates the `$app/manifest` data module. During development, real values
2122
+ * are emitted for `assets` and `routes` (the only data known at that point).
2123
+ *
2124
+ * During build, bare identifier placeholders (fake globals) are emitted.
2125
+ * The bundler leaves these as unresolved global references in the output,
2126
+ * which are then replaced with real values by scanning the output chunks
2127
+ * after each build completes. This avoids the content-hash feedback loop:
2128
+ * the manifest data lives in its own chunk with a fixed filename, so
2129
+ * importers' hashes are stable regardless of the manifest content.
2130
+ *
2131
+ * @param {boolean} is_build
2132
+ * @param {ManifestData | undefined} manifest_data
2133
+ * @returns {string}
2024
2134
  */
2025
- const create_service_worker_module = (config) => dedent`
2026
- if (typeof self === 'undefined' || self instanceof ServiceWorkerGlobalScope === false) {
2027
- throw new Error('This module can only be imported inside a service worker');
2135
+ const create_manifest_data_module = (is_build, manifest_data) => {
2136
+ if (is_build) {
2137
+ // Bare identifiers (fake globals) the bundler leaves these as
2138
+ // unresolved global references in the output. They are replaced
2139
+ // with real values by `replace_manifest_placeholder_variables`
2140
+ // after each build completes.
2141
+ return dedent`
2142
+ export const immutable = __SVELTEKIT_MANIFEST_IMMUTABLE__;
2143
+ export const assets = __SVELTEKIT_MANIFEST_ASSETS__;
2144
+ export const prerendered = __SVELTEKIT_MANIFEST_PRERENDERED__;
2145
+ export const routes = __SVELTEKIT_MANIFEST_ROUTES__;
2146
+ `;
2028
2147
  }
2029
2148
 
2030
- export const base = location.pathname.split('/').slice(0, -1).join('/');
2031
- export const build = [];
2032
- export const files = [
2033
- ${create_assets(config)
2034
- .filter((asset) => config.kit.serviceWorker.files(asset.file))
2035
- .map((asset) => `${s(`${config.kit.paths.base}/${asset.file}`)}`)
2036
- .join(',\n')}
2037
- ];
2038
- export const prerendered = [];
2039
- export const version = ${s(config.kit.version.name)};
2040
- `;
2149
+ // In dev, `manifest_data` may not be set yet on the very first load,
2150
+ // but `configureServer` (which calls `sync.create`) runs before any
2151
+ // module is served, so it will be set by the time this is called.
2152
+ const routes = manifest_data?.routes.map((route) => s({ id: route.id })).join(',\n') ?? '';
2153
+ const assets = manifest_data?.assets.map((asset) => s({ path: asset.file })).join(',\n') ?? '';
2154
+
2155
+ return dedent`
2156
+ // empty during dev
2157
+ export const immutable = [];
2158
+ export const prerendered = [];
2159
+
2160
+ export const assets = [
2161
+ ${assets}
2162
+ ];
2163
+
2164
+ export const routes = [
2165
+ ${routes}
2166
+ ];
2167
+ `;
2168
+ };
2169
+
2170
+ /**
2171
+ * Replaces manifest data placeholder identifiers in output chunks with real
2172
+ * values, or strings that are valid JS (so thecode doesn't crash during prerendering)
2173
+ * but findable on disk for later replacement by `replace_manifest_placeholder_strings`.
2174
+ *
2175
+ * @param {Rolldown.RolldownOutput['output']} chunks
2176
+ * @param {string} output_dir
2177
+ * @param {{
2178
+ * immutable?: Array<{ path: string }>;
2179
+ * assets?: Array<{ path: string }>;
2180
+ * prerendered?: Array<{ path: string }>;
2181
+ * routes?: Array<{ id: string }>;
2182
+ * }} values
2183
+ */
2184
+ const replace_manifest_placeholder_variables = (chunks, output_dir, values) => {
2185
+ /** @type {Record<string, string>} */
2186
+ const replacements = {
2187
+ __SVELTEKIT_MANIFEST_IMMUTABLE__: JSON.stringify(
2188
+ values.immutable ?? '__sveltekit_manifest_build__'
2189
+ ),
2190
+ __SVELTEKIT_MANIFEST_ASSETS__: JSON.stringify(values.assets ?? '__sveltekit_manifest_files__'),
2191
+ __SVELTEKIT_MANIFEST_PRERENDERED__: JSON.stringify(
2192
+ values.prerendered ?? '__sveltekit_manifest_prerendered__'
2193
+ ),
2194
+ __SVELTEKIT_MANIFEST_ROUTES__: JSON.stringify(values.routes ?? '__sveltekit_manifest_routes__')
2195
+ };
2196
+
2197
+ for (const chunk of chunks) {
2198
+ if (chunk.type !== 'chunk') continue;
2199
+ if (!chunk.code.includes('__SVELTEKIT_MANIFEST_')) continue;
2200
+
2201
+ let code = chunk.code;
2202
+
2203
+ for (const [identifier, replacement] of Object.entries(replacements)) {
2204
+ code = code.replaceAll(identifier, replacement);
2205
+ }
2206
+
2207
+ const file_path = `${output_dir}/${chunk.fileName}`;
2208
+ fs.writeFileSync(file_path, code);
2209
+ }
2210
+ };
2211
+
2212
+ /**
2213
+ * Replaces manifest sentinel strings in files on disk with real values.
2214
+ * This is used for values that weren't known when the first replacement
2215
+ * pass ran (e.g. `build` wasn't known until after the client build,
2216
+ * `prerendered` wasn't known until after prerendering).
2217
+ *
2218
+ * @param {string} dir Directory to scan for .js files
2219
+ * @param {{
2220
+ * immutable?: Array<{ path: string }>;
2221
+ * prerendered?: Array<{ path: string }>;
2222
+ * }} values
2223
+ */
2224
+ const replace_manifest_placeholder_strings = (dir, values) => {
2225
+ /** @type {Record<string, string>} */
2226
+ const replacements = {};
2227
+
2228
+ if (values.immutable !== undefined) {
2229
+ replacements['__sveltekit_manifest_build__'] = JSON.stringify(values.immutable);
2230
+ }
2231
+ if (values.prerendered !== undefined) {
2232
+ replacements['__sveltekit_manifest_prerendered__'] = JSON.stringify(values.prerendered);
2233
+ }
2234
+
2235
+ for (const file of fs.readdirSync(dir)) {
2236
+ const file_path = `${dir}/${file}`;
2237
+ const stat = fs.statSync(file_path);
2238
+
2239
+ if (stat.isDirectory()) {
2240
+ replace_manifest_placeholder_strings(file_path, values);
2241
+ continue;
2242
+ }
2243
+
2244
+ if (!file.endsWith('.js')) continue;
2245
+
2246
+ let code = read(file_path);
2247
+ let changed = false;
2248
+
2249
+ for (const [sentinel, replacement] of Object.entries(replacements)) {
2250
+ if (code.includes(sentinel)) {
2251
+ code = code.replaceAll(`"${sentinel}"`, replacement);
2252
+ changed = true;
2253
+ }
2254
+ }
2255
+
2256
+ if (changed) {
2257
+ fs.writeFileSync(file_path, code);
2258
+ }
2259
+ }
2260
+ };