@sveltejs/kit 3.0.0-next.10 → 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 (88) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +8 -8
  3. package/src/constants.js +3 -1
  4. package/src/core/adapt/builder.js +3 -6
  5. package/src/core/config/index.js +6 -1
  6. package/src/core/config/options.js +54 -104
  7. package/src/core/env.js +25 -11
  8. package/src/core/postbuild/fallback.js +2 -1
  9. package/src/core/postbuild/prerender.js +75 -28
  10. package/src/core/postbuild/queue.js +2 -1
  11. package/src/core/sync/sync.js +11 -14
  12. package/src/core/sync/utils.js +21 -1
  13. package/src/core/sync/{write_non_ambient.js → write_app_types.js} +23 -21
  14. package/src/core/sync/write_client_manifest.js +4 -12
  15. package/src/core/sync/write_env.js +6 -4
  16. package/src/core/sync/write_server.js +12 -17
  17. package/src/core/sync/write_tsconfig/index.js +245 -0
  18. package/src/core/sync/write_tsconfig/utils.js +162 -0
  19. package/src/core/sync/write_types/index.js +80 -88
  20. package/src/core/utils.js +28 -3
  21. package/src/exports/env/index.js +68 -3
  22. package/src/exports/internal/env.js +6 -3
  23. package/src/exports/internal/server/index.js +3 -1
  24. package/src/exports/params.js +9 -4
  25. package/src/exports/public.d.ts +69 -71
  26. package/src/exports/vite/dev/index.js +112 -56
  27. package/src/exports/vite/index.js +483 -258
  28. package/src/exports/vite/module_ids.js +2 -2
  29. package/src/exports/vite/preview/index.js +3 -2
  30. package/src/exports/vite/utils.js +21 -10
  31. package/src/runner.js +13 -0
  32. package/src/runtime/app/forms.js +4 -0
  33. package/src/runtime/app/manifest/index.js +1 -0
  34. package/src/runtime/app/paths/client.js +30 -30
  35. package/src/runtime/app/paths/internal/client.js +26 -0
  36. package/src/runtime/app/paths/server.js +23 -10
  37. package/src/runtime/app/paths/types.d.ts +11 -19
  38. package/src/runtime/app/server/index.js +1 -1
  39. package/src/runtime/app/server/remote/command.js +12 -7
  40. package/src/runtime/app/server/remote/form.js +29 -26
  41. package/src/runtime/app/server/remote/prerender.js +9 -2
  42. package/src/runtime/app/server/remote/query.js +6 -5
  43. package/src/runtime/app/server/remote/shared.js +48 -30
  44. package/src/runtime/app/service-worker/index.js +24 -0
  45. package/src/runtime/app/state/index.js +1 -1
  46. package/src/runtime/client/client.js +46 -9
  47. package/src/runtime/client/ndjson.js +1 -1
  48. package/src/runtime/client/remote-functions/form.svelte.js +43 -51
  49. package/src/runtime/client/remote-functions/query/index.js +2 -2
  50. package/src/runtime/client/remote-functions/query-batch.svelte.js +2 -3
  51. package/src/runtime/client/remote-functions/query-live/iterator.js +5 -2
  52. package/src/runtime/client/remote-functions/shared.svelte.js +4 -1
  53. package/src/runtime/client/state.svelte.js +54 -21
  54. package/src/runtime/client/stream.js +3 -2
  55. package/src/runtime/form-utils.js +89 -54
  56. package/src/runtime/server/cookie.js +1 -1
  57. package/src/runtime/server/data/index.js +32 -29
  58. package/src/runtime/server/errors.js +135 -0
  59. package/src/runtime/server/fetch.js +1 -1
  60. package/src/runtime/server/index.js +20 -10
  61. package/src/runtime/server/internal.js +25 -0
  62. package/src/runtime/server/page/actions.js +4 -3
  63. package/src/runtime/server/page/data_serializer.js +10 -10
  64. package/src/runtime/server/page/index.js +3 -2
  65. package/src/runtime/server/page/load_data.js +1 -1
  66. package/src/runtime/server/page/render.js +22 -35
  67. package/src/runtime/server/page/respond_with_error.js +6 -5
  68. package/src/runtime/server/{remote.js → remote-functions.js} +67 -36
  69. package/src/runtime/server/respond.js +58 -20
  70. package/src/runtime/server/sourcemaps.js +183 -0
  71. package/src/runtime/server/utils.js +12 -157
  72. package/src/types/ambient-private.d.ts +10 -0
  73. package/src/types/ambient.d.ts +22 -27
  74. package/src/types/global-private.d.ts +12 -5
  75. package/src/types/internal.d.ts +19 -9
  76. package/src/types/private.d.ts +8 -0
  77. package/src/utils/escape.js +9 -25
  78. package/src/utils/features.js +1 -1
  79. package/src/utils/fork.js +7 -2
  80. package/src/utils/page_nodes.js +3 -5
  81. package/src/utils/url.js +12 -0
  82. package/src/version.js +1 -1
  83. package/types/index.d.ts +152 -120
  84. package/types/index.d.ts.map +6 -1
  85. package/src/core/sync/write_ambient.js +0 -18
  86. package/src/core/sync/write_tsconfig.js +0 -258
  87. package/src/runtime/shared-server.js +0 -7
  88. /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,19 +24,20 @@ 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,
35
36
  get_config_aliases,
36
37
  normalize_id,
37
- strip_virtual_prefix
38
+ remote_module_pattern,
39
+ server_only_directory_pattern,
40
+ server_only_module_pattern
38
41
  } from './utils.js';
39
42
  import { stackless } from '../../utils/error.js';
40
43
  import { write_client_manifest } from '../../core/sync/write_client_manifest.js';
@@ -47,11 +50,10 @@ import { get_import_aliases, get_hash_import_keys } from '../../utils/imports.js
47
50
  import {
48
51
  app_env_private,
49
52
  app_server,
50
- service_worker,
51
53
  sveltekit_env,
52
54
  sveltekit_env_private,
53
55
  sveltekit_env_service_worker,
54
- sveltekit_server,
56
+ sveltekit_manifest_data,
55
57
  sveltekit_env_public_client,
56
58
  sveltekit_env_public_server
57
59
  } from './module_ids.js';
@@ -60,6 +62,7 @@ import { compact } from '../../utils/array.js';
60
62
  import { should_ignore, has_children } from './static_analysis/utils.js';
61
63
  import { process_config, split_config, validate_config } from '../../core/config/index.js';
62
64
  import { treeshake_prerendered_remotes } from './build/remote.js';
65
+ import { get_runner } from '../../runner.js';
63
66
 
64
67
  /**
65
68
  * The posix-ified root of the project based on the Vite configuration.
@@ -98,8 +101,7 @@ const enforced_config = {
98
101
  resolve: {
99
102
  alias: {
100
103
  $app: true,
101
- $env: true,
102
- '$service-worker': true
104
+ $env: true
103
105
  }
104
106
  }
105
107
  };
@@ -299,9 +301,6 @@ function kit({ svelte_config }) {
299
301
 
300
302
  /** @type {string | null} */
301
303
  let service_worker_entry_file;
302
- /** @type {import('node:path').ParsedPath} */
303
- let parsed_service_worker;
304
-
305
304
  /** @type {string} */
306
305
  let normalized_cwd;
307
306
  /** @type {Array<{ alias: string, path: string }>} */
@@ -402,11 +401,23 @@ function kit({ svelte_config }) {
402
401
  // dev and preview config can be shared
403
402
  /** @type {UserConfig} */
404
403
  const new_config = {
404
+ environments: {
405
+ ssr: {
406
+ build: {
407
+ sourcemap:
408
+ config.environments?.ssr?.build?.sourcemap ?? config.build?.sourcemap ?? true
409
+ }
410
+ }
411
+ },
405
412
  resolve: {
406
413
  alias: [
407
414
  { find: '__SERVER__', replacement: `${generated}/server` },
408
415
  { find: '$app', replacement: `${runtime_directory}/app` },
409
416
  { find: '$env', replacement: `${runtime_directory}/env` },
417
+ {
418
+ find: '__sveltekit/server',
419
+ replacement: `${runtime_directory}/server/internal.js`
420
+ },
410
421
  ...get_config_aliases(kit, root)
411
422
  ]
412
423
  },
@@ -458,30 +469,33 @@ function kit({ svelte_config }) {
458
469
  }
459
470
  };
460
471
 
472
+ // externalize .remote.js files to stop dependency tracing during prebundling
461
473
  if (kit.experimental.remoteFunctions) {
462
- // treat .remote.js files as empty for the purposes of prebundling
463
- const remote_id_filter = new RegExp(
464
- `.remote(${kit.moduleExtensions.join('|')})$`.replaceAll('.', '\\.')
465
- );
466
474
  // @ts-expect-error optimizeDeps is already set above
467
475
  new_config.optimizeDeps.rolldownOptions ??= {};
468
476
  // @ts-expect-error
469
477
  new_config.optimizeDeps.rolldownOptions.plugins ??= [];
470
478
  // @ts-expect-error
471
- new_config.optimizeDeps.rolldownOptions.plugins.push({
472
- name: 'vite-plugin-sveltekit-setup:optimize-remote-functions',
473
- load: {
474
- filter: { id: remote_id_filter },
475
- handler() {
476
- 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
+ }
477
490
  }
478
- }
479
- });
491
+ })
492
+ );
480
493
  }
481
494
 
482
495
  const define = {
483
496
  __SVELTEKIT_APP_DIR__: s(posixify(kit.appDir)),
484
497
  __SVELTEKIT_APP_VERSION__: s(kit.version.name),
498
+ __SVELTEKIT_APP_VERSION_CHECKS_ENABLED__: s(kit.output.bundleStrategy !== 'inline'),
485
499
  __SVELTEKIT_EMBEDDED__: s(kit.embedded),
486
500
  __SVELTEKIT_FORK_PRELOADS__: s(kit.experimental.forkPreloads),
487
501
  __SVELTEKIT_PATHS_ASSETS__: s(kit.paths.assets),
@@ -493,7 +507,6 @@ function kit({ svelte_config }) {
493
507
  __SVELTEKIT_SUPPORTS_ASYNC__: s(
494
508
  svelte_config.compilerOptions?.experimental?.async ?? false
495
509
  ),
496
- __SVELTEKIT_ROOT__: s(root),
497
510
  __SVELTEKIT_DEV__: s(!is_build)
498
511
  };
499
512
 
@@ -602,14 +615,10 @@ function kit({ svelte_config }) {
602
615
  );
603
616
 
604
617
  for (const id of [sveltekit_env, sveltekit_env_public_client]) {
605
- const module = server.moduleGraph.getModuleById(id);
606
-
607
- if (module) {
608
- server.moduleGraph.invalidateModule(module);
609
- }
618
+ invalidate_module(server, id);
610
619
  }
611
620
 
612
- server.ws.send({ type: 'full-reload' });
621
+ server.hot.send({ type: 'full-reload' });
613
622
  }
614
623
  });
615
624
  },
@@ -620,18 +629,13 @@ function kit({ svelte_config }) {
620
629
 
621
630
  resolveId: {
622
631
  filter: {
623
- id: [exactRegex('$service-worker'), prefixRegex('__sveltekit/')]
632
+ id: [prefixRegex('__sveltekit/')]
624
633
  },
625
634
  handler(id) {
626
635
  if (id === '__sveltekit/manifest') {
627
636
  return `${out_dir}/generated/client-optimized/app.js`;
628
637
  }
629
638
 
630
- if (id === '$service-worker') {
631
- // ids with :$ don't work with reverse proxies like nginx
632
- return `\0virtual:${id.substring(1)}`;
633
- }
634
-
635
639
  if (id === '__sveltekit/remote') {
636
640
  return `${runtime_directory}/client/remote-functions/index.js`;
637
641
  }
@@ -643,19 +647,18 @@ function kit({ svelte_config }) {
643
647
  load: {
644
648
  filter: {
645
649
  id: [
646
- exactRegex(service_worker),
647
650
  exactRegex(sveltekit_env),
648
651
  exactRegex(sveltekit_env_private),
649
652
  exactRegex(sveltekit_env_public_client),
650
653
  exactRegex(sveltekit_env_public_server),
651
654
  exactRegex(sveltekit_env_service_worker),
652
- exactRegex(sveltekit_server)
655
+ exactRegex(sveltekit_manifest_data)
653
656
  ]
654
657
  },
655
658
  handler(id) {
656
659
  switch (id) {
657
- case service_worker:
658
- return create_service_worker_module(svelte_config);
660
+ case sveltekit_manifest_data:
661
+ return create_manifest_data_module(is_build, manifest_data);
659
662
 
660
663
  case sveltekit_env:
661
664
  return create_sveltekit_env(explicit_env_config, env, explicit_env_entry, !is_build);
@@ -682,27 +685,17 @@ function kit({ svelte_config }) {
682
685
  ? create_sveltekit_env_service_worker(
683
686
  explicit_env_config,
684
687
  env,
688
+ kit.version.name,
685
689
  kit_global,
686
690
  kit.paths.base,
687
691
  kit.appDir
688
692
  )
689
- : create_sveltekit_env_service_worker_dev(explicit_env_config, env, kit_global);
690
-
691
- case sveltekit_server: {
692
- return dedent`
693
- export let read_implementation = null;
694
-
695
- export let manifest = null;
696
-
697
- export function set_read_implementation(fn) {
698
- read_implementation = fn;
699
- }
700
-
701
- export function set_manifest(_) {
702
- manifest = _;
703
- }
704
- `;
705
- }
693
+ : create_sveltekit_env_service_worker_dev(
694
+ explicit_env_config,
695
+ env,
696
+ kit.version.name,
697
+ kit_global
698
+ );
706
699
  }
707
700
  }
708
701
  }
@@ -710,10 +703,6 @@ function kit({ svelte_config }) {
710
703
 
711
704
  /** @type {Map<string, Set<string>>} */
712
705
  const import_map = new Map();
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\//;
717
706
 
718
707
  /**
719
708
  * Ensures that client-side code can't accidentally import server-side code,
@@ -729,7 +718,7 @@ function kit({ svelte_config }) {
729
718
 
730
719
  applyToEnvironment(environment) {
731
720
  // 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';
721
+ return environment.config.consumer === 'client';
733
722
  },
734
723
 
735
724
  resolveId: {
@@ -769,24 +758,21 @@ function kit({ svelte_config }) {
769
758
  ]
770
759
  },
771
760
  handler(id) {
772
- // skip .server.js files outside the cwd or in node_modules, as the filename might not mean 'server-only module' in this context
773
- const is_internal =
774
- id.startsWith(normalized_cwd) && !id.startsWith(normalized_node_modules);
775
-
776
761
  const normalized = normalize_id(id, normalized_aliases, normalized_cwd);
777
762
 
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);
763
+ let is_server_only = normalized === '$app/env/private' || normalized === '$app/server';
784
764
 
785
- const is_server_only =
786
- normalized === '$app/env/private' ||
787
- normalized === '$app/server' ||
788
- is_server_only_directory ||
789
- (is_internal && server_only_module_pattern.test(id));
765
+ // skip .server.js files outside the cwd or in node_modules, as the filename might not mean 'server-only module' in this context
766
+ if (id.startsWith(normalized_cwd) && !id.startsWith(normalized_node_modules)) {
767
+ // e.g. `server.ts` or `foo.server.ts`
768
+ is_server_only ||= server_only_module_pattern.test(id);
769
+
770
+ // e.g. `server/foo.ts`, unless in `src/routes` or `static`
771
+ is_server_only ||=
772
+ server_only_directory_pattern.test(id) &&
773
+ !id.startsWith(normalized_routes + '/') &&
774
+ !id.startsWith(normalized_assets + '/');
775
+ }
790
776
 
791
777
  if (!is_server_only) return;
792
778
 
@@ -803,6 +789,10 @@ function kit({ svelte_config }) {
803
789
  if (manifest_data.hooks.client) entrypoints.add(manifest_data.hooks.client);
804
790
  if (manifest_data.hooks.universal) entrypoints.add(manifest_data.hooks.universal);
805
791
 
792
+ if (service_worker_entry_file) {
793
+ entrypoints.add(posixify(path.relative(root, service_worker_entry_file)));
794
+ }
795
+
806
796
  // Walk up the import graph from the server-only module, looking for a chain
807
797
  // that leads back to a client entrypoint. We search all candidates (not just
808
798
  // the first) because a module can be imported by both server and client code,
@@ -837,6 +827,10 @@ function kit({ svelte_config }) {
837
827
  const chain = find_chain(normalized, [normalized]);
838
828
 
839
829
  if (chain) {
830
+ if (chain.some((id) => remote_module_pattern.test(id))) {
831
+ error_for_missing_config('remote functions', 'experimental.remoteFunctions', 'true');
832
+ }
833
+
840
834
  const pyramid = chain
841
835
  .reverse()
842
836
  .map((id, i) => {
@@ -907,109 +901,115 @@ function kit({ svelte_config }) {
907
901
  dev_server = _dev_server;
908
902
  },
909
903
 
910
- async transform(code, id) {
911
- const normalized = normalize_id(id, normalized_aliases, normalized_cwd);
912
- if (!svelte_config.kit.moduleExtensions.some((ext) => normalized.endsWith(`.remote${ext}`))) {
913
- return;
914
- }
904
+ transform: {
905
+ filter: {
906
+ id: remote_module_pattern
907
+ },
908
+ async handler(code, id) {
909
+ const file = posixify(path.relative(root, id));
910
+ const remote = {
911
+ hash: hash(file),
912
+ file
913
+ };
915
914
 
916
- const file = posixify(path.relative(root, id));
917
- const remote = {
918
- hash: hash(file),
919
- file
920
- };
915
+ remotes.push(remote);
921
916
 
922
- remotes.push(remote);
917
+ if (this.environment.config.consumer !== 'client') {
918
+ // we need to add an `await Promise.resolve()` because if the user imports this function
919
+ // on the client AND in a load function when loading the client module we will trigger
920
+ // an import during dev. During a link preload, the module can be mistakenly
921
+ // loaded and transformed twice and the first time all its exports would be undefined
922
+ // triggering a dev server error. By adding a microtask we ensure that the module is fully loaded
923
+ const ms = new MagicString(code);
923
924
 
924
- if (this.environment.config.consumer !== 'client') {
925
- // we need to add an `await Promise.resolve()` because if the user imports this function
926
- // on the client AND in a load function when loading the client module we will trigger
927
- // an ssrLoadModule during dev. During a link preload, the module can be mistakenly
928
- // loaded and transformed twice and the first time all its exports would be undefined
929
- // triggering a dev server error. By adding a microtask we ensure that the module is fully loaded
925
+ // Extra newlines to prevent syntax errors around missing semicolons or comments
926
+ ms.append(
927
+ '\n\n' +
928
+ dedent`
929
+ import * as $$_self_$$ from './${path.basename(id)}';
930
+ import { init_remote_functions as $$_init_$$ } from '@sveltejs/kit/internal/server';
930
931
 
931
- // Extra newlines to prevent syntax errors around missing semicolons or comments
932
- code +=
933
- '\n\n' +
934
- dedent`
935
- import * as $$_self_$$ from './${path.basename(id)}';
936
- import { init_remote_functions as $$_init_$$ } from '@sveltejs/kit/internal/server';
932
+ ${dev_server ? 'await Promise.resolve()' : ''}
937
933
 
938
- ${dev_server ? 'await Promise.resolve()' : ''}
934
+ $$_init_$$($$_self_$$, ${s(file)}, ${s(remote.hash)});
939
935
 
940
- $$_init_$$($$_self_$$, ${s(file)}, ${s(remote.hash)});
936
+ for (const [name, fn] of Object.entries($$_self_$$)) {
937
+ fn.__.id = ${s(remote.hash)} + '/' + name;
938
+ fn.__.name = name;
939
+ }
940
+ `
941
+ );
941
942
 
942
- for (const [name, fn] of Object.entries($$_self_$$)) {
943
- fn.__.id = ${s(remote.hash)} + '/' + name;
944
- fn.__.name = name;
945
- }
946
- `;
947
-
948
- // Emit a dedicated entry chunk for this remote in SSR builds (prod only)
949
- if (!dev_server) {
950
- remote_original_by_hash.set(remote.hash, id);
951
- if (!emitted_remote_hashes.has(remote.hash)) {
952
- this.emitFile({
953
- type: 'chunk',
954
- id: `\0sveltekit-remote:${remote.hash}`,
955
- name: `remote-${remote.hash}`
956
- });
957
- emitted_remote_hashes.add(remote.hash);
943
+ // Emit a dedicated entry chunk for this remote in SSR builds (prod only)
944
+ if (!dev_server) {
945
+ remote_original_by_hash.set(remote.hash, id);
946
+ if (!emitted_remote_hashes.has(remote.hash)) {
947
+ this.emitFile({
948
+ type: 'chunk',
949
+ id: `\0sveltekit-remote:${remote.hash}`,
950
+ name: `remote-${remote.hash}`
951
+ });
952
+ emitted_remote_hashes.add(remote.hash);
953
+ }
958
954
  }
959
- }
960
955
 
961
- return code;
962
- }
956
+ return {
957
+ code: ms.toString(),
958
+ map: ms.generateMap({ hires: 'boundary' })
959
+ };
960
+ }
963
961
 
964
- // For the client, read the exports and create a new module that only contains fetch functions with the correct metadata
962
+ // For the client, read the exports and create a new module that only contains fetch functions with the correct metadata
965
963
 
966
- /** @type {Map<string, RemoteInternals['type']>} */
967
- const map = new Map();
964
+ /** @type {Map<string, RemoteInternals['type']>} */
965
+ const map = new Map();
968
966
 
969
- // in dev, load the server module here (which will result in this hook
970
- // being called again with `opts.ssr === true` if the module isn't
971
- // already loaded) so we can determine what it exports
972
- if (dev_server) {
973
- const module = await dev_server.ssrLoadModule(id);
967
+ // in dev, load the server module here (which will result in this hook
968
+ // being called again with `opts.ssr === true` if the module isn't
969
+ // already loaded) so we can determine what it exports
970
+ if (dev_server) {
971
+ const module = await get_runner(dev_server).import(id);
974
972
 
975
- for (const [name, value] of Object.entries(module)) {
976
- const type = value?.__?.type;
977
- if (type) {
978
- map.set(name, type);
973
+ for (const [name, value] of Object.entries(module)) {
974
+ const type = value?.__?.type;
975
+ if (type) {
976
+ map.set(name, type);
977
+ }
979
978
  }
980
979
  }
981
- }
982
980
 
983
- // in prod, we already built and analysed the server code before
984
- // building the client code, so `remotes` is populated
985
- else if (build_metadata?.remotes) {
986
- const exports = build_metadata?.remotes.get(remote.hash);
987
- if (!exports) throw new Error('Expected to find metadata for remote file ' + id);
981
+ // in prod, we already built and analysed the server code before
982
+ // building the client code, so `remotes` is populated
983
+ else if (build_metadata?.remotes) {
984
+ const exports = build_metadata?.remotes.get(remote.hash);
985
+ if (!exports) throw new Error('Expected to find metadata for remote file ' + id);
988
986
 
989
- for (const [name, value] of exports) {
990
- map.set(name, value.type);
987
+ for (const [name, value] of exports) {
988
+ map.set(name, value.type);
989
+ }
991
990
  }
992
- }
993
991
 
994
- const { namespace, declarations, reexports } = create_exported_declarations(
995
- map.keys(),
996
- (name, ns) => `${ns}.${map.get(name)}('${remote.hash}/${name}')`,
997
- '__remote'
998
- );
992
+ const { namespace, declarations, reexports } = create_exported_declarations(
993
+ map.keys(),
994
+ (name, ns) => `${ns}.${map.get(name)}('${remote.hash}/${name}')`,
995
+ '__remote'
996
+ );
999
997
 
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';
998
+ let result = `import * as ${namespace} from '__sveltekit/remote';\n\n${declarations.join('\n')}`;
999
+ if (reexports.length > 0) {
1000
+ result += `\nexport { ${reexports.join(', ')} };`;
1001
+ }
1002
+ result += '\n';
1005
1003
 
1006
- if (dev_server) {
1007
- result += `\nimport.meta.hot?.accept();\n`;
1008
- }
1004
+ if (dev_server) {
1005
+ result += `\nimport.meta.hot?.accept();\n`;
1006
+ }
1009
1007
 
1010
- return {
1011
- code: result
1012
- };
1008
+ return {
1009
+ code: result,
1010
+ map: null
1011
+ };
1012
+ }
1013
1013
  }
1014
1014
  };
1015
1015
 
@@ -1043,7 +1043,7 @@ function kit({ svelte_config }) {
1043
1043
  /** @type {Set<string>} */
1044
1044
  let build_files;
1045
1045
  /** @type {string} */
1046
- let service_worker_code;
1046
+ let manifest_data_code;
1047
1047
 
1048
1048
  /**
1049
1049
  * Creates the service worker virtual modules
@@ -1054,7 +1054,6 @@ function kit({ svelte_config }) {
1054
1054
 
1055
1055
  config(config) {
1056
1056
  service_worker_entry_file = resolve_entry(kit.files.serviceWorker);
1057
- parsed_service_worker = path.parse(kit.files.serviceWorker);
1058
1057
 
1059
1058
  if (!service_worker_entry_file) return;
1060
1059
 
@@ -1109,46 +1108,23 @@ function kit({ svelte_config }) {
1109
1108
  return environment.name === 'serviceWorker';
1110
1109
  },
1111
1110
 
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);
1118
-
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
- );
1136
- }
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')) {
1111
+ resolveId: {
1112
+ filter: {
1113
+ id: [prefixRegex('__sveltekit/')]
1114
+ },
1115
+ handler(id) {
1145
1116
  return `\0virtual:${id}`;
1146
1117
  }
1147
1118
  },
1148
1119
 
1149
1120
  load: {
1150
1121
  filter: {
1151
- 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
+ ]
1152
1128
  },
1153
1129
  handler(id) {
1154
1130
  if (!build_files) {
@@ -1171,36 +1147,33 @@ function kit({ svelte_config }) {
1171
1147
  }
1172
1148
  }
1173
1149
 
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`
1179
- export const base = /*@__PURE__*/ ${base};
1180
-
1181
- export const build = [
1150
+ manifest_data_code = dedent`
1151
+ export const immutable = [
1182
1152
  ${Array.from(build_files)
1183
- .map((file) => `base + ${s(`/${file}`)}`)
1153
+ .map((file) => s({ path: file }))
1184
1154
  .join(',\n')}
1185
1155
  ];
1186
1156
 
1187
- export const files = [
1188
- ${manifest_data.assets
1189
- .filter((asset) => kit.serviceWorker.files(asset.file))
1190
- .map((asset) => `base + ${s(`/${asset.file}`)}`)
1191
- .join(',\n')}
1157
+ export const assets = [
1158
+ ${manifest_data.assets.map((asset) => s({ path: asset.file })).join(',\n')}
1192
1159
  ];
1193
1160
 
1194
1161
  export const prerendered = [
1195
- ${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')}
1196
1163
  ];
1197
1164
 
1198
- export const version = ${s(kit.version.name)};
1165
+ export const routes = [
1166
+ ${manifest_data.routes.map((route) => s({ id: route.id })).join(',\n')}
1167
+ ];
1199
1168
  `;
1200
1169
  }
1201
1170
 
1202
- if (id === service_worker) {
1203
- 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;
1204
1177
  }
1205
1178
 
1206
1179
  if (id === sveltekit_env_service_worker) {
@@ -1208,11 +1181,17 @@ function kit({ svelte_config }) {
1208
1181
  ? create_sveltekit_env_service_worker(
1209
1182
  explicit_env_config,
1210
1183
  env,
1184
+ kit.version.name,
1211
1185
  kit_global,
1212
1186
  kit.paths.base,
1213
1187
  kit.appDir
1214
1188
  )
1215
- : 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
+ );
1216
1195
  }
1217
1196
 
1218
1197
  if (id === sveltekit_env_public_client) {
@@ -1222,16 +1201,31 @@ function kit({ svelte_config }) {
1222
1201
  `const env = ${kit_global}.env;`
1223
1202
  );
1224
1203
  }
1204
+ }
1205
+ },
1225
1206
 
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);
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
+ }
1223
+
1224
+ if (invalid_modules.size > 0) {
1233
1225
  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.`
1226
+ `Cannot import ${Array.from(modules.values())
1227
+ .filter((module) => invalid_modules.has(module))
1228
+ .join(', ')} into service-worker code.`
1235
1229
  );
1236
1230
  }
1237
1231
  }
@@ -1243,17 +1237,19 @@ function kit({ svelte_config }) {
1243
1237
  applyToEnvironment(environment) {
1244
1238
  return !!service_worker_entry_file && environment.config.consumer === 'client';
1245
1239
  },
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
- };
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
+ }
1257
1253
  }
1258
1254
  };
1259
1255
 
@@ -1437,9 +1433,26 @@ function kit({ svelte_config }) {
1437
1433
  output: {
1438
1434
  format: inline ? 'iife' : 'esm',
1439
1435
  entryFileNames: `${app_immutable}/[name].[hash].js`,
1440
- 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
+ },
1441
1445
  codeSplitting:
1442
- 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
1443
1456
  },
1444
1457
  // This silences Rolldown warnings about not supporting `import.meta`
1445
1458
  // for the `iife` output format. We don't care because it's
@@ -1518,7 +1531,18 @@ function kit({ svelte_config }) {
1518
1531
  * @see https://vitejs.dev/guide/api-plugin.html#configureserver
1519
1532
  */
1520
1533
  async configureServer(server) {
1521
- 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
+ );
1522
1546
  },
1523
1547
 
1524
1548
  /**
@@ -1574,6 +1598,15 @@ function kit({ svelte_config }) {
1574
1598
  await builder.build(builder.environments.ssr)
1575
1599
  );
1576
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
+
1577
1610
  const verbose = builder.config.logLevel === 'info';
1578
1611
  const log = logger({ verbose });
1579
1612
 
@@ -1698,6 +1731,42 @@ function kit({ svelte_config }) {
1698
1731
  read(`${out}/client/.vite/manifest.json`)
1699
1732
  ));
1700
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
+
1701
1770
  /**
1702
1771
  * @param {string} entry
1703
1772
  * @param {boolean} [add_dynamic_css]
@@ -1844,17 +1913,51 @@ function kit({ svelte_config }) {
1844
1913
  }
1845
1914
 
1846
1915
  // ...and prerender
1847
- const prerender_results = await prerender({
1848
- hash: kit.router.type === 'hash',
1849
- out,
1850
- manifest_path,
1851
- metadata,
1852
- verbose,
1853
- env,
1854
- vite_config_file: vite_config.configFile
1855
- });
1916
+ let prerender_results;
1917
+ try {
1918
+ prerender_results = await prerender({
1919
+ hash: kit.router.type === 'hash',
1920
+ out,
1921
+ manifest_path,
1922
+ metadata,
1923
+ verbose,
1924
+ env,
1925
+ vite_config_file: vite_config.configFile
1926
+ });
1927
+
1928
+ // this silly hack is necessary to ensure that stderr from prerender is flushed before we continue
1929
+ await new Promise((f) => setTimeout(f, 0));
1930
+ } catch (e) {
1931
+ if (e instanceof Error && e.message === '__handled__') {
1932
+ // error details are already logged inside `prerender`, don't duplicate them
1933
+ throw stackless('Prerendering failed');
1934
+ } else {
1935
+ // Unforeseen error, rethrow as-is
1936
+ throw e;
1937
+ }
1938
+ }
1939
+
1856
1940
  prerendered = prerender_results.prerendered;
1857
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
+
1858
1961
  await treeshake_prerendered_remotes(
1859
1962
  vite,
1860
1963
  out,
@@ -2015,21 +2118,143 @@ function find_overridden_config(config, resolved_config, enforced_config, path,
2015
2118
  }
2016
2119
 
2017
2120
  /**
2018
- * @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}
2019
2134
  */
2020
- const create_service_worker_module = (config) => dedent`
2021
- if (typeof self === 'undefined' || self instanceof ServiceWorkerGlobalScope === false) {
2022
- 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
+ `;
2023
2147
  }
2024
2148
 
2025
- export const base = location.pathname.split('/').slice(0, -1).join('/');
2026
- export const build = [];
2027
- export const files = [
2028
- ${create_assets(config)
2029
- .filter((asset) => config.kit.serviceWorker.files(asset.file))
2030
- .map((asset) => `${s(`${config.kit.paths.base}/${asset.file}`)}`)
2031
- .join(',\n')}
2032
- ];
2033
- export const prerendered = [];
2034
- export const version = ${s(config.kit.version.name)};
2035
- `;
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
+ };