@sveltejs/kit 3.0.0-next.23 → 3.0.0-next.24

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 (92) hide show
  1. package/package.json +40 -5
  2. package/src/cli.js +3 -3
  3. package/src/constants.js +13 -1
  4. package/src/core/adapt/builder.js +2 -3
  5. package/src/core/env.js +139 -181
  6. package/src/core/postbuild/analyse.js +9 -4
  7. package/src/core/postbuild/prerender.js +3 -4
  8. package/src/core/sync/create_manifest_data/index.js +29 -33
  9. package/src/core/sync/sync.js +23 -40
  10. package/src/core/sync/write_app_manifest.js +83 -0
  11. package/src/core/sync/write_client_manifest.js +6 -4
  12. package/src/core/sync/write_server.js +10 -6
  13. package/src/core/sync/write_types/index.js +2 -2
  14. package/src/core/utils.js +0 -28
  15. package/src/exports/hooks/public.d.ts +9 -1
  16. package/src/exports/node/index.js +77 -5
  17. package/src/exports/vite/build/build_server.js +3 -3
  18. package/src/exports/vite/build/utils.js +48 -4
  19. package/src/exports/vite/dev/index.js +11 -22
  20. package/src/exports/vite/index.js +62 -345
  21. package/src/exports/vite/module_ids.js +1 -11
  22. package/src/exports/vite/plugins/env-vars.js +117 -0
  23. package/src/exports/vite/preview/index.js +6 -4
  24. package/src/exports/vite/utils.js +2 -4
  25. package/src/runtime/app/env/{types.d.ts → client.js} +9 -4
  26. package/src/runtime/app/env/index.js +1 -2
  27. package/src/runtime/app/env/private/index.js +1 -0
  28. package/src/runtime/app/env/public/client.js +1 -1
  29. package/src/runtime/app/env/public/server.js +1 -1
  30. package/src/runtime/app/env/{internal.js → server.js} +4 -4
  31. package/src/runtime/app/forms/client.js +263 -0
  32. package/src/runtime/app/forms/index.js +1 -263
  33. package/src/runtime/app/forms/public.d.ts +1 -1
  34. package/src/runtime/app/forms/server.js +6 -0
  35. package/src/runtime/app/forms/shared.js +34 -0
  36. package/src/runtime/app/manifest/index.js +1 -1
  37. package/src/runtime/app/navigation/client.js +15 -0
  38. package/src/runtime/app/navigation/index.js +1 -15
  39. package/src/runtime/app/navigation/public.d.ts +1 -1
  40. package/src/runtime/app/navigation/server.js +15 -0
  41. package/src/runtime/app/paths/server.js +1 -1
  42. package/src/runtime/app/server/remote/form.js +11 -1
  43. package/src/runtime/app/server/remote/prerender.js +1 -2
  44. package/src/runtime/app/server/remote/query.js +1 -1
  45. package/src/runtime/app/state/client.svelte.js +207 -0
  46. package/src/runtime/app/state/index.js +1 -66
  47. package/src/runtime/app/state/public.d.ts +1 -1
  48. package/src/runtime/client/bundle.js +1 -1
  49. package/src/runtime/client/client.js +206 -274
  50. package/src/runtime/client/fetcher.js +18 -8
  51. package/src/runtime/client/remote-functions/form.svelte.js +9 -6
  52. package/src/runtime/client/remote-functions/prerender.svelte.js +1 -1
  53. package/src/runtime/client/remote-functions/query-live/iterator.js +1 -1
  54. package/src/runtime/client/remote-functions/shared.svelte.js +2 -2
  55. package/src/runtime/client/snapshots.js +1 -3
  56. package/src/runtime/client/stream.js +27 -20
  57. package/src/runtime/client/utils.js +7 -3
  58. package/src/runtime/components/root.svelte +5 -5
  59. package/src/runtime/env/dynamic/private.js +1 -1
  60. package/src/runtime/env/static/private.js +1 -1
  61. package/src/runtime/error-chain.js +54 -0
  62. package/src/runtime/form-utils.js +53 -77
  63. package/src/runtime/invalid-import.js +1 -0
  64. package/src/runtime/props.svelte.js +1 -1
  65. package/src/runtime/server/data/index.js +1 -1
  66. package/src/runtime/server/endpoint.js +8 -3
  67. package/src/runtime/server/env_module.js +1 -1
  68. package/src/runtime/server/errors.js +2 -2
  69. package/src/runtime/server/index.js +2 -2
  70. package/src/runtime/server/page/actions.js +77 -122
  71. package/src/runtime/server/page/index.js +37 -69
  72. package/src/runtime/server/page/render.js +18 -23
  73. package/src/runtime/server/remote-functions.js +14 -42
  74. package/src/runtime/server/respond.js +23 -3
  75. package/src/telemetry.js +1 -33
  76. package/src/types/ambient-private.d.ts +9 -17
  77. package/src/types/internal.d.ts +20 -4
  78. package/src/types/private.d.ts +1 -1
  79. package/src/utils/exports.js +1 -0
  80. package/src/utils/filesystem.js +9 -21
  81. package/src/utils/functions.js +10 -0
  82. package/src/utils/routing.js +0 -11
  83. package/src/utils/url.js +0 -11
  84. package/src/version.js +1 -1
  85. package/types/index.d.ts +34 -28
  86. package/types/index.d.ts.map +7 -5
  87. package/src/runtime/app/env/private.js +0 -1
  88. package/src/runtime/app/env/standard-schema.d.ts +0 -0
  89. package/src/runtime/app/state/client.js +0 -63
  90. package/src/runtime/client/state.svelte.js +0 -98
  91. package/src/runtime/server/ambient.d.ts +0 -4
  92. package/src/utils/path.js +0 -23
@@ -1,11 +1,12 @@
1
+ /** @import { ValidatedConfig } from 'types' */
1
2
  import { lookup } from '../../../utils/mime.js';
2
3
  import fs from 'node:fs';
3
4
  import path from 'node:path';
4
5
  import { styleText } from 'node:util';
5
- import { resolve_entry } from '../../../utils/filesystem.js';
6
+ import { resolve_entry, walk } from '../../../utils/filesystem.js';
6
7
  import { posixify } from '../../../utils/os.js';
7
8
  import { parse_route_id } from '../../../utils/routing.js';
8
- import { list_files, runtime_directory } from '../../utils.js';
9
+ import { runtime_directory } from '../../utils.js';
9
10
  import { prevent_conflicts } from './conflict.js';
10
11
  import { sort_routes } from './sort.js';
11
12
  import {
@@ -13,24 +14,27 @@ import {
13
14
  get_page_options
14
15
  } from '../../../exports/vite/static_analysis/index.js';
15
16
 
17
+ const component_name_pattern = /^\+(?:(page(?:@(.*))?)|(layout(?:@(.*))?)|(error))$/;
18
+
19
+ const module_name_pattern =
20
+ /^\+(?:(server)|(page(?:(@[a-zA-Z0-9_-]*))?(\.server)?)|(layout(?:(@[a-zA-Z0-9_-]*))?(\.server)?))$/;
21
+
16
22
  /**
17
23
  * Generates the manifest data used for the client-side manifest and types generation.
18
- * @param {{
19
- * config: import('types').ValidatedConfig;
20
- * fallback?: string;
21
- * cwd: string;
22
- * }} opts
24
+ * @param {ValidatedConfig} config
25
+ * @param {string} root
26
+ * @param {string} [fallback] Where to look for fallback components
23
27
  * @returns {import('types').ManifestData}
24
28
  */
25
- export default function create_manifest_data({
29
+ export default function create_manifest_data(
26
30
  config,
27
- fallback = `${runtime_directory}/components`,
28
- cwd
29
- }) {
31
+ root,
32
+ fallback = `${runtime_directory}/components`
33
+ ) {
30
34
  const assets = create_assets(config);
31
- const hooks = create_hooks(config, cwd);
32
- const params = resolve_params(config, cwd);
33
- const { nodes, routes } = create_routes_and_nodes(cwd, config, fallback);
35
+ const hooks = create_hooks(config, root);
36
+ const params = resolve_params(config, root);
37
+ const { nodes, routes } = create_routes_and_nodes(root, config, fallback);
34
38
 
35
39
  return {
36
40
  assets,
@@ -74,7 +78,9 @@ export function is_app_route(route) {
74
78
  * @param {import('types').ValidatedConfig} config
75
79
  */
76
80
  export function create_assets(config) {
77
- return list_files(config.files.assets).map((file) => ({
81
+ if (!fs.existsSync(config.files.assets)) return [];
82
+
83
+ return [...walk(config.files.assets)].map((file) => ({
78
84
  file,
79
85
  size: fs.statSync(path.resolve(config.files.assets, file)).size,
80
86
  type: lookup(file) || null
@@ -232,13 +238,12 @@ function create_routes_and_nodes(cwd, config, fallback) {
232
238
 
233
239
  if (!file.name.startsWith('+')) {
234
240
  const name = file.name.slice(0, -ext.length);
241
+ const pattern = config.extensions.includes(ext)
242
+ ? component_name_pattern
243
+ : module_name_pattern;
244
+
235
245
  // check if it is a valid route filename but missing the + prefix
236
- const typo =
237
- /^(?:(page(?:@(.*))?)|(layout(?:@(.*))?)|(error))$/.test(name) ||
238
- /^(?:(server)|(page(?:(@[a-zA-Z0-9_-]*))?(\.server)?)|(layout(?:(@[a-zA-Z0-9_-]*))?(\.server)?))$/.test(
239
- name
240
- );
241
- if (typo) {
246
+ if (pattern.test(`+${name}`)) {
242
247
  console.log(
243
248
  styleText(
244
249
  ['bold', 'yellow'],
@@ -265,13 +270,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
265
270
  const ext = valid_extensions.find((ext) => name.endsWith(ext));
266
271
  if (ext) name = name.slice(0, -ext.length);
267
272
 
268
- const valid =
269
- /^\+(?:(page(?:@(.*))?)|(layout(?:@(.*))?)|(error))$/.test(name) ||
270
- /^\+(?:(server)|(page(?:(@[a-zA-Z0-9_-]*))?(\.server)?)|(layout(?:(@[a-zA-Z0-9_-]*))?(\.server)?))$/.test(
271
- name
272
- );
273
-
274
- if (valid) continue;
273
+ if (component_name_pattern.test(name) || module_name_pattern.test(name)) continue;
275
274
  }
276
275
 
277
276
  const project_relative = posixify(path.relative(cwd, path.join(dir, file.name)));
@@ -535,8 +534,7 @@ function analyze(project_relative, file, component_extensions, module_extensions
535
534
  const component_extension = component_extensions.find((ext) => file.endsWith(ext));
536
535
  if (component_extension) {
537
536
  const name = file.slice(0, -component_extension.length);
538
- const pattern = /^\+(?:(page(?:@(.*))?)|(layout(?:@(.*))?)|(error))$/;
539
- const match = pattern.exec(name);
537
+ const match = component_name_pattern.exec(name);
540
538
  if (!match) {
541
539
  throw new Error(`Files prefixed with + are reserved (saw ${project_relative})`);
542
540
  }
@@ -553,9 +551,7 @@ function analyze(project_relative, file, component_extensions, module_extensions
553
551
  const module_extension = module_extensions.find((ext) => file.endsWith(ext));
554
552
  if (module_extension) {
555
553
  const name = file.slice(0, -module_extension.length);
556
- const pattern =
557
- /^\+(?:(server)|(page(?:(@[a-zA-Z0-9_-]*))?(\.server)?)|(layout(?:(@[a-zA-Z0-9_-]*))?(\.server)?))$/;
558
- const match = pattern.exec(name);
554
+ const match = module_name_pattern.exec(name);
559
555
  if (!match) {
560
556
  throw new Error(`Files prefixed with + are reserved (saw ${project_relative})`);
561
557
  } else if (match[3] || match[6]) {
@@ -1,3 +1,4 @@
1
+ /** @import { ValidatedConfig, ManifestData } from 'types' */
1
2
  import path from 'node:path';
2
3
  import create_manifest_data from './create_manifest_data/index.js';
3
4
  import { write_client_manifest } from './write_client_manifest.js';
@@ -11,39 +12,30 @@ import {
11
12
  } from '../../exports/vite/static_analysis/index.js';
12
13
  import { load_explicit_env } from '../env.js';
13
14
  import { write_env } from './write_env.js';
14
-
15
- /**
16
- * Initialize SvelteKit's generated files that only depend on the config and mode.
17
- * @param {import('types').ValidatedConfig} config
18
- * @param {string} root The project root directory
19
- */
20
- export function init(config, root) {
21
- write_tsconfig(config, root);
22
- }
15
+ import { write_app_manifest } from './write_app_manifest.js';
23
16
 
24
17
  /**
25
18
  * Update SvelteKit's generated files
26
- * @param {import('types').ValidatedConfig} config
19
+ * @param {ValidatedConfig} config
27
20
  * @param {string} root The project root directory
21
+ * @param {ManifestData} manifest_data
22
+ * @param {boolean} is_build
28
23
  */
29
- export function create(config, root) {
30
- const manifest_data = create_manifest_data({ config, cwd: root });
24
+ export function create(config, root, manifest_data, is_build) {
25
+ const output = path.join(config.outDir, `generated/${is_build ? 'build' : 'dev'}`);
31
26
 
32
- const output = path.join(config.outDir, 'generated');
33
-
34
- write_client_manifest(config, manifest_data, `${output}/client`);
27
+ write_app_manifest(output, manifest_data, is_build);
28
+ write_client_manifest(config, manifest_data, `${output}/client`, root);
35
29
  write_server(config, output, root);
36
30
  write_all_types(config, manifest_data, root);
37
31
  write_app_types(config, manifest_data, root);
38
-
39
- return { manifest_data };
40
32
  }
41
33
 
42
34
  /**
43
35
  * Update SvelteKit's generated files in response to a single file content update.
44
36
  * Do not call this when the file in question was created/deleted.
45
37
  *
46
- * @param {import('types').ValidatedConfig} config
38
+ * @param {ValidatedConfig} config
47
39
  * @param {import('types').ManifestData} manifest_data
48
40
  * @param {string} file
49
41
  * @param {string} root The project root directory
@@ -79,48 +71,39 @@ export function update(config, manifest_data, file, root) {
79
71
  }
80
72
 
81
73
  /**
82
- * Run sync.init and sync.create in series, returning the result from sync.create.
83
- * @param {import('types').ValidatedConfig} config
74
+ * Run write_tsconfig and sync.create in series during build
75
+ * @param {ValidatedConfig} config
84
76
  * @param {string} root The project root directory
77
+ * @param {ManifestData} manifest_data
85
78
  */
86
- export function all(config, root) {
87
- init(config, root);
88
- return create(config, root);
79
+ export function all(config, root, manifest_data) {
80
+ write_tsconfig(config, root);
81
+ create(config, root, manifest_data, true);
89
82
  }
90
83
 
91
84
  /**
92
85
  * Run sync.init and then generate all type files.
93
- * @param {import('types').ValidatedConfig} config
86
+ * @param {ValidatedConfig} config
94
87
  * @param {string} root
95
88
  */
96
89
  export function all_types(config, root) {
97
- init(config, root);
98
- const manifest_data = create_manifest_data({ config, cwd: root });
90
+ write_tsconfig(config, root);
91
+ const manifest_data = create_manifest_data(config, root);
99
92
  write_all_types(config, manifest_data, root);
100
93
  write_app_types(config, manifest_data, root);
101
94
  }
102
95
 
103
96
  /**
104
97
  * Generate modules and types for explicit env vars
105
- * @param {typeof import('vite')} vite
106
- * @param {import('types').ValidatedConfig} kit
98
+ * @param {ValidatedConfig} kit
107
99
  * @param {string | null} entry
108
100
  * @param {string} root The Vite root
109
101
  * @param {string} mode The Vite mode
110
102
  */
111
- export async function env(vite, kit, entry, root, mode) {
112
- const env_config = await load_explicit_env(vite, kit, entry, root, mode);
103
+ export async function env(kit, entry, root, mode) {
104
+ const env_config = await load_explicit_env(kit, entry, root, mode);
113
105
 
114
- write_env(entry, env_config, root);
106
+ write_env(entry, env_config.variables, root);
115
107
 
116
108
  return env_config;
117
109
  }
118
-
119
- /**
120
- * Regenerate __SERVER__/internal.js in response to src/{app.html,error.html,service-worker.js} changing
121
- * @param {import('types').ValidatedConfig} config
122
- * @param {string} root The project root directory
123
- */
124
- export function server(config, root) {
125
- write_server(config, path.join(config.outDir, 'generated'), root);
126
- }
@@ -0,0 +1,83 @@
1
+ /** @import { Asset, ManifestData, RouteData } from 'types' */
2
+
3
+ import { s } from '../../utils/misc.js';
4
+ import { is_app_route, is_endpoint_route, is_page_route } from './create_manifest_data/index.js';
5
+ import { dedent, write_if_changed } from './utils.js';
6
+
7
+ /**
8
+ * Creates the `$app/manifest` data module. During development, real values
9
+ * are emitted for `assets` and `routes` (the only data known at that point).
10
+ *
11
+ * During build, bare identifier placeholders (fake globals) are emitted.
12
+ * The bundler leaves these as unresolved global references in the output,
13
+ * which are then replaced with real values by scanning the output chunks
14
+ * after each build completes. This avoids the content-hash feedback loop:
15
+ * the manifest data lives in its own chunk with a fixed filename, so
16
+ * importers' hashes are stable regardless of the manifest content.
17
+ *
18
+ * @param {string} out
19
+ * @param {ManifestData | undefined} manifest_data
20
+ * @param {boolean} is_build
21
+ */
22
+ export function write_app_manifest(out, manifest_data, is_build) {
23
+ const code = is_build
24
+ ? // Bare identifiers (fake globals) — the bundler leaves these as
25
+ // unresolved global references in the output. They are replaced
26
+ // with real values by `replace_manifest_placeholder_variables`
27
+ // after each build completes.
28
+ dedent`
29
+ export const immutable = __SVELTEKIT_MANIFEST_IMMUTABLE__;
30
+ export const assets = __SVELTEKIT_MANIFEST_ASSETS__;
31
+ export const prerendered = __SVELTEKIT_MANIFEST_PRERENDERED__;
32
+ export const routes = __SVELTEKIT_MANIFEST_ROUTES__;
33
+ `
34
+ : // In dev, an empty manifest is written before dependency scanning starts.
35
+ // `configureServer` replaces it with the full manifest before modules are served.
36
+ dedent`
37
+ // empty during dev
38
+ export const immutable = [];
39
+ export const prerendered = [];
40
+
41
+ export const assets = [
42
+ ${stringify_assets(manifest_data?.assets)}
43
+ ];
44
+
45
+ export const routes = [
46
+ ${stringify_routes(manifest_data?.routes)}
47
+ ];
48
+ `;
49
+
50
+ write_if_changed(`${out}/app-manifest.js`, code);
51
+ }
52
+
53
+ /**
54
+ * @param {Asset[] | undefined} assets
55
+ * @returns {string}
56
+ */
57
+ function stringify_assets(assets) {
58
+ return assets?.map((asset) => s({ path: asset.file })).join(',\n') ?? '';
59
+ }
60
+
61
+ /**
62
+ * @param {RouteData[] | undefined} routes
63
+ * @returns {Array<{ id: string; page: boolean; endpoint: boolean }>}
64
+ */
65
+ export function get_manifest_routes(routes) {
66
+ return (
67
+ routes?.filter(is_app_route).map((route) => ({
68
+ id: route.id,
69
+ page: is_page_route(route),
70
+ endpoint: is_endpoint_route(route)
71
+ })) ?? []
72
+ );
73
+ }
74
+
75
+ /**
76
+ * @param {RouteData[] | undefined} routes
77
+ * @returns {string}
78
+ */
79
+ function stringify_routes(routes) {
80
+ return get_manifest_routes(routes)
81
+ .map((route) => s(route))
82
+ .join(',\n');
83
+ }
@@ -1,3 +1,4 @@
1
+ import path from 'node:path';
1
2
  import { check_spelling, dedent, write_if_changed } from './utils.js';
2
3
  import { relative_path, resolve_entry } from '../../utils/filesystem.js';
3
4
  import { s } from '../../utils/misc.js';
@@ -8,9 +9,10 @@ import { s } from '../../utils/misc.js';
8
9
  * @param {import('types').ValidatedConfig} kit
9
10
  * @param {import('types').ManifestData} manifest_data
10
11
  * @param {string} output
12
+ * @param {string} root The project root directory
11
13
  * @param {import('types').ServerMetadata['nodes']} [metadata] If this is omitted, we have to assume that all routes with a `+layout/page.server.js` file have a server load function
12
14
  */
13
- export function write_client_manifest(kit, manifest_data, output, metadata) {
15
+ export function write_client_manifest(kit, manifest_data, output, root, metadata) {
14
16
  const client_routing = kit.router.resolution === 'client';
15
17
 
16
18
  /**
@@ -22,7 +24,7 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
22
24
 
23
25
  if (node.universal) {
24
26
  declarations.push(
25
- `import * as universal from ${s(relative_path(`${output}/nodes`, node.universal))};`,
27
+ `import * as universal from ${s(relative_path(`${output}/nodes`, path.resolve(root, node.universal)))};`,
26
28
  'export { universal };'
27
29
  );
28
30
  }
@@ -30,7 +32,7 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
30
32
  if (node.component) {
31
33
  declarations.push(
32
34
  `export { default as component } from ${s(
33
- relative_path(`${output}/nodes`, node.component)
35
+ relative_path(`${output}/nodes`, path.resolve(root, node.component))
34
36
  )};`
35
37
  );
36
38
  }
@@ -180,7 +182,7 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
180
182
  const module =
181
183
  !manifest_data.params || !uses_matchers
182
184
  ? 'export const matchers = {};'
183
- : `import { params as matchers } from ${s(relative_path(output, manifest_data.params))};\n\nexport { matchers };`;
185
+ : `import { params as matchers } from ${s(relative_path(output, path.resolve(root, manifest_data.params)))};\n\nexport { matchers };`;
184
186
 
185
187
  write_if_changed(`${output}/matchers.js`, module);
186
188
  }
@@ -6,6 +6,7 @@ import { s } from '../../utils/misc.js';
6
6
  import { load_error_page, load_template } from '../config/index.js';
7
7
  import { check_spelling, write_if_changed } from './utils.js';
8
8
  import { escape_html } from '../../utils/escape.js';
9
+ import { runtime_directory } from '../utils.js';
9
10
 
10
11
  /**
11
12
  * @param {{
@@ -14,6 +15,7 @@ import { escape_html } from '../../utils/escape.js';
14
15
  * config: import('types').ValidatedConfig;
15
16
  * has_service_worker: boolean;
16
17
  * template: string;
18
+ * runtime_directory: string;
17
19
  * }} opts
18
20
  */
19
21
  const server_template = ({
@@ -21,12 +23,13 @@ const server_template = ({
21
23
  server_hooks,
22
24
  universal_hooks,
23
25
  has_service_worker,
24
- template
26
+ template,
27
+ runtime_directory
25
28
  }) => `
26
- import { set_building, set_prerendering } from '$app/env/internal';
29
+ import { set_building, set_prerendering } from '$app/env/server';
27
30
  import { set_assets } from '$app/paths/internal/server';
28
- import { set_fix_stack_trace, set_manifest, set_read_implementation, log_response } from '__sveltekit/server';
29
- import error from '../shared/error-template.js';
31
+ import { set_fix_stack_trace, set_manifest, set_read_implementation, log_response } from '${runtime_directory}/server/internal.js';
32
+ import error from './shared/error-template.js';
30
33
 
31
34
  export const options = {
32
35
  app_template_contains_nonce: ${template.includes('%sveltekit.nonce%')},
@@ -103,7 +106,7 @@ export function write_server(config, output, root) {
103
106
 
104
107
  /** @param {string} file */
105
108
  function relative(file) {
106
- return posixify(path.relative(`${output}/server`, file));
109
+ return posixify(path.relative(output, file));
107
110
  }
108
111
 
109
112
  write_if_changed(
@@ -116,9 +119,10 @@ export function write_server(config, output, root) {
116
119
  // Contains the stringified version of
117
120
  /** @type {import('types').SSROptions} */
118
121
  write_if_changed(
119
- `${output}/server/internal.js`,
122
+ `${output}/server.js`,
120
123
  server_template({
121
124
  config,
125
+ runtime_directory: relative(runtime_directory),
122
126
  server_hooks: server_hooks_file ? relative(server_hooks_file) : null,
123
127
  universal_hooks: universal_hooks_file ? relative(universal_hooks_file) : null,
124
128
  has_service_worker:
@@ -42,12 +42,12 @@ export function write_all_types(config, manifest_data, root) {
42
42
  posixify(path.relative(root, config.files.routes))
43
43
  );
44
44
  const expected_directories = new Set(
45
- manifest_data.routes.map((route) => path.join(routes_dir, route.id))
45
+ manifest_data.routes.map((route) => path.posix.join(routes_dir, route.id))
46
46
  );
47
47
 
48
48
  if (fs.existsSync(types_dir)) {
49
49
  for (const file of walk(types_dir)) {
50
- const dir = path.dirname(file);
50
+ const dir = path.posix.dirname(file);
51
51
  if (!expected_directories.has(dir)) {
52
52
  fs.rmSync(path.join(types_dir, file), { force: true, recursive: true });
53
53
  }
package/src/core/utils.js CHANGED
@@ -1,4 +1,3 @@
1
- import fs from 'node:fs';
2
1
  import path from 'node:path';
3
2
  import { fileURLToPath } from 'node:url';
4
3
  import { styleText } from 'node:util';
@@ -85,30 +84,3 @@ export function get_mime_lookup(manifest_data) {
85
84
 
86
85
  return mime;
87
86
  }
88
-
89
- /**
90
- * @param {string} dir
91
- * @param {(file: string) => boolean} [filter]
92
- */
93
- export function list_files(dir, filter) {
94
- /** @type {string[]} */
95
- const files = [];
96
-
97
- /** @param {string} current */
98
- function walk(current) {
99
- for (const file of fs.readdirSync(path.resolve(dir, current))) {
100
- const child = path.posix.join(current, file);
101
- if (fs.statSync(path.resolve(dir, child)).isDirectory()) {
102
- walk(child);
103
- } else {
104
- if (!filter || filter(child)) {
105
- files.push(child);
106
- }
107
- }
108
- }
109
- }
110
-
111
- if (fs.existsSync(dir)) walk('');
112
-
113
- return files;
114
- }
@@ -170,9 +170,17 @@ export interface ResolveOptions {
170
170
  * `<head>` tag; if `output.linkHeaderPreload` is enabled, dynamically rendered pages use the
171
171
  * [`Link` response header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link) instead.
172
172
  * By default, `js` and `css` files will be preloaded.
173
+ *
174
+ * For `font` files, `input` also has a `filename` property, the source file's pathname relative
175
+ * to the project root, so that a filter can match on it instead of the hashed path. `js` and
176
+ * `css` files are bundled and have no single source file name.
173
177
  * @param input the type of the file and its path
174
178
  */
175
- preload?: (input: { type: 'font' | 'css' | 'js' | 'asset'; path: string }) => boolean;
179
+ preload?: (
180
+ input:
181
+ | { type: 'css' | 'js' | 'asset'; path: string }
182
+ | { type: 'font'; path: string; filename: string }
183
+ ) => boolean;
176
184
  }
177
185
 
178
186
  type AppErrorWithOptionalDefaults = Omit<App.Error, 'status' | 'message'> & {
@@ -116,12 +116,13 @@ function get_raw_body(req, body_size_limit) {
116
116
  /**
117
117
  * @param {{
118
118
  * request: import('http').IncomingMessage;
119
+ * response?: import('http').ServerResponse;
119
120
  * base: string;
120
121
  * bodySizeLimit?: number;
121
122
  * }} options
122
123
  * @returns {Request}
123
124
  */
124
- export function getRequest({ request, base, bodySizeLimit }) {
125
+ export function getRequest({ request, response, base, bodySizeLimit }) {
125
126
  let headers = /** @type {Record<string, string>} */ (request.headers);
126
127
  if (request.httpVersionMajor >= 2) {
127
128
  // the Request constructor rejects headers with ':' in the name
@@ -144,6 +145,15 @@ export function getRequest({ request, base, bodySizeLimit }) {
144
145
  }
145
146
  });
146
147
 
148
+ // `readableAborted` stays false once the request has been fully read (or drained),
149
+ // so a client disconnect must also be detected on the response side. `writableEnded`
150
+ // rather than `writableFinished` because HTTP/2 marks cancelled streams as finished
151
+ response?.once('close', () => {
152
+ if (!response.writableEnded) {
153
+ controller.abort();
154
+ }
155
+ });
156
+
147
157
  return new Request(base + request.url, {
148
158
  // @ts-expect-error
149
159
  duplex: 'half',
@@ -214,14 +224,14 @@ export function setResponse(res, response) {
214
224
  }
215
225
  }
216
226
 
217
- res.writeHead(response.status);
218
-
219
227
  if (!response.body) {
228
+ res.writeHead(response.status);
220
229
  res.end();
221
230
  return;
222
231
  }
223
232
 
224
233
  if (response.body.locked) {
234
+ res.writeHead(response.status);
225
235
  res.end(
226
236
  'Fatal error: Response body is locked. ' +
227
237
  "This can happen when the response was already read (for example through 'response.json()' or 'response.text()')."
@@ -249,11 +259,73 @@ export function setResponse(res, response) {
249
259
  res.on('close', cancel);
250
260
  res.on('error', cancel);
251
261
 
252
- void next();
262
+ /** @type {Uint8Array<ArrayBuffer>[]} */
263
+ const buffered = [];
264
+
265
+ /** @type {ReturnType<typeof reader.read> | null} */
266
+ let pending = null;
267
+
268
+ void probe();
269
+
270
+ // a fixed body (a string, buffer or blob, however constructed) settles all its
271
+ // reads before the next macrotask, so it can be measured and sent with a
272
+ // `content-length`; a genuine stream leaves a read pending and only has its
273
+ // headers delayed by a single tick
274
+ async function probe() {
275
+ try {
276
+ /** @type {Promise<undefined>} */
277
+ const deadline = new Promise((fulfil) => setImmediate(() => fulfil(undefined)));
278
+
279
+ while (buffered.length < 2) {
280
+ pending = reader.read();
281
+ const result = await Promise.race([pending, deadline]);
282
+
283
+ if (!result) break; // deadline hit — treat the body as a stream
284
+
285
+ pending = null;
286
+
287
+ if (result.done) {
288
+ // a `content-length` next to a `transfer-encoding` would be invalid
289
+ if (!res.hasHeader('content-length') && !res.hasHeader('transfer-encoding')) {
290
+ res.setHeader(
291
+ 'content-length',
292
+ buffered.reduce((total, chunk) => total + chunk.byteLength, 0)
293
+ );
294
+ }
295
+ break;
296
+ }
297
+
298
+ buffered.push(result.value);
299
+ }
300
+
301
+ if (res.destroyed) return;
302
+
303
+ res.writeHead(response.status);
304
+ await next();
305
+ } catch (error) {
306
+ if (!res.headersSent) res.writeHead(response.status);
307
+ cancel(error instanceof Error ? error : new Error(String(error)));
308
+ }
309
+ }
310
+
253
311
  async function next() {
254
312
  try {
255
313
  for (;;) {
256
- const { done, value } = await reader.read();
314
+ /** @type {Awaited<ReturnType<typeof reader.read>>} */
315
+ let result;
316
+ if (buffered.length > 0) {
317
+ result = {
318
+ done: false,
319
+ value: /** @type {Uint8Array<ArrayBuffer>} */ (buffered.shift())
320
+ };
321
+ } else if (pending) {
322
+ result = await pending;
323
+ pending = null;
324
+ } else {
325
+ result = await reader.read();
326
+ }
327
+
328
+ const { done, value } = result;
257
329
 
258
330
  if (done) break;
259
331
 
@@ -128,7 +128,7 @@ export function build_server_nodes(
128
128
  /** @type {string[]} */
129
129
  let stylesheets = [];
130
130
 
131
- /** @type {string[]} */
131
+ /** @type {import('types').FontDependency[]} */
132
132
  let fonts = [];
133
133
 
134
134
  /** @type {Set<string>} */
@@ -187,7 +187,7 @@ export function build_server_nodes(
187
187
  }
188
188
 
189
189
  if (client_manifest) {
190
- const entry_path = `${out_dir}/generated/client-optimized/nodes/${i}.js`;
190
+ const entry_path = `${out_dir}/generated/build/client-optimized/nodes/${i}.js`;
191
191
  const entry = find_deps(client_manifest, entry_path, true, root);
192
192
 
193
193
  // Eagerly load client stylesheets and fonts imported by the SSR-ed page to avoid FOUC.
@@ -212,7 +212,7 @@ export function build_server_nodes(
212
212
 
213
213
  imported = entry.imports;
214
214
  stylesheets = Array.from(eager_css);
215
- fonts = filter_fonts(Array.from(eager_assets));
215
+ fonts = filter_fonts(Array.from(eager_assets), client_manifest, root);
216
216
  } else {
217
217
  for (const entry of [component, universal]) {
218
218
  if (!entry) continue;