@sveltejs/kit 2.56.0 → 2.57.0

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 (34) hide show
  1. package/package.json +2 -2
  2. package/src/core/config/index.js +22 -2
  3. package/src/core/config/options.js +20 -4
  4. package/src/core/postbuild/prerender.js +2 -1
  5. package/src/core/sync/utils.js +8 -1
  6. package/src/core/utils.js +1 -2
  7. package/src/exports/node/index.js +2 -1
  8. package/src/exports/public.d.ts +25 -18
  9. package/src/exports/vite/build/build_service_worker.js +1 -1
  10. package/src/exports/vite/build/remote.js +126 -0
  11. package/src/exports/vite/dev/index.js +21 -8
  12. package/src/exports/vite/index.js +35 -22
  13. package/src/exports/vite/preview/index.js +5 -1
  14. package/src/exports/vite/utils.js +18 -0
  15. package/src/runtime/app/forms.js +2 -1
  16. package/src/runtime/app/server/remote/prerender.js +2 -1
  17. package/src/runtime/app/server/remote/query.js +2 -4
  18. package/src/runtime/app/server/remote/requested.js +3 -2
  19. package/src/runtime/app/server/remote/shared.js +2 -1
  20. package/src/runtime/client/client.js +7 -8
  21. package/src/runtime/client/fetcher.js +2 -1
  22. package/src/runtime/client/remote-functions/form.svelte.js +10 -5
  23. package/src/runtime/client/remote-functions/query.svelte.js +3 -2
  24. package/src/runtime/client/utils.js +2 -1
  25. package/src/runtime/server/fetch.js +4 -3
  26. package/src/runtime/server/index.js +3 -2
  27. package/src/runtime/server/page/index.js +3 -2
  28. package/src/runtime/server/page/load_data.js +4 -3
  29. package/src/runtime/server/page/render.js +7 -1
  30. package/src/runtime/server/respond.js +0 -17
  31. package/src/utils/functions.js +2 -0
  32. package/src/version.js +1 -1
  33. package/types/index.d.ts +27 -19
  34. package/types/index.d.ts.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "2.56.0",
3
+ "version": "2.57.0",
4
4
  "description": "SvelteKit is the fastest way to build Svelte apps",
5
5
  "keywords": [
6
6
  "framework",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "devDependencies": {
35
35
  "@opentelemetry/api": "^1.0.0",
36
- "@playwright/test": "1.58.2",
36
+ "@playwright/test": "^1.59.1",
37
37
  "@sveltejs/vite-plugin-svelte": "^6.0.0-next.3",
38
38
  "@types/connect": "^3.4.38",
39
39
  "@types/node": "^18.19.119",
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import process from 'node:process';
4
4
  import * as url from 'node:url';
5
5
  import options from './options.js';
6
+ import { resolve_entry } from '../../utils/filesystem.js';
6
7
 
7
8
  /**
8
9
  * Loads the template (src/app.html by default) and validates that it has the
@@ -96,7 +97,7 @@ export async function load_config({ cwd = process.cwd() } = {}) {
96
97
  * @returns {import('types').ValidatedConfig}
97
98
  */
98
99
  function process_config(config, { cwd = process.cwd() } = {}) {
99
- const validated = validate_config(config);
100
+ const validated = validate_config(config, cwd);
100
101
 
101
102
  validated.kit.outDir = path.resolve(cwd, validated.kit.outDir);
102
103
 
@@ -116,15 +117,17 @@ function process_config(config, { cwd = process.cwd() } = {}) {
116
117
 
117
118
  /**
118
119
  * @param {import('@sveltejs/kit').Config} config
120
+ * @param {string} [cwd]
119
121
  * @returns {import('types').ValidatedConfig}
120
122
  */
121
- export function validate_config(config) {
123
+ export function validate_config(config, cwd = process.cwd()) {
122
124
  if (typeof config !== 'object') {
123
125
  throw new Error(
124
126
  'The Svelte config file must have a configuration object as its default export. See https://svelte.dev/docs/kit/configuration'
125
127
  );
126
128
  }
127
129
 
130
+ /** @type {import('types').ValidatedConfig} */
128
131
  const validated = options(config, 'config');
129
132
  const files = validated.kit.files;
130
133
 
@@ -151,5 +154,22 @@ export function validate_config(config) {
151
154
  }
152
155
  }
153
156
 
157
+ if (validated.kit.csp?.directives?.['require-trusted-types-for']?.includes('script')) {
158
+ if (!validated.kit.csp?.directives?.['trusted-types']?.includes('svelte-trusted-html')) {
159
+ throw new Error(
160
+ "The `csp.directives['trusted-types']` option must include 'svelte-trusted-html'"
161
+ );
162
+ }
163
+ if (
164
+ validated.kit.serviceWorker?.register &&
165
+ resolve_entry(path.resolve(cwd, validated.kit.files.serviceWorker)) &&
166
+ !validated.kit.csp?.directives?.['trusted-types']?.includes('sveltekit-trusted-url')
167
+ ) {
168
+ throw new Error(
169
+ "The `csp.directives['trusted-types']` option must include 'sveltekit-trusted-url' when `serviceWorker.register` is true"
170
+ );
171
+ }
172
+ }
173
+
154
174
  return validated;
155
175
  }
@@ -1,7 +1,8 @@
1
+ /** @import { Validator } from './types.js' */
2
+
1
3
  import process from 'node:process';
2
4
  import colors from 'kleur';
3
-
4
- /** @typedef {import('./types.js').Validator} Validator */
5
+ import { supportsTrustedTypes } from '../sync/utils.js';
5
6
 
6
7
  const directives = object({
7
8
  'child-src': string_array(),
@@ -28,8 +29,14 @@ const directives = object({
28
29
  'navigate-to': string_array(),
29
30
  'report-uri': string_array(),
30
31
  'report-to': string_array(),
31
- 'require-trusted-types-for': string_array(),
32
- 'trusted-types': string_array(),
32
+ 'require-trusted-types-for': validate(undefined, (input, keypath) => {
33
+ assert_trusted_types_supported(keypath);
34
+ return string_array()(input, keypath);
35
+ }),
36
+ 'trusted-types': validate(undefined, (input, keypath) => {
37
+ assert_trusted_types_supported(keypath);
38
+ return string_array()(input, keypath);
39
+ }),
33
40
  'upgrade-insecure-requests': boolean(false),
34
41
  'require-sri-for': string_array(),
35
42
  'block-all-mixed-content': boolean(false),
@@ -486,4 +493,13 @@ function assert_string(input, keypath) {
486
493
  }
487
494
  }
488
495
 
496
+ /** @param {string} keypath */
497
+ function assert_trusted_types_supported(keypath) {
498
+ if (!supportsTrustedTypes()) {
499
+ throw new Error(
500
+ `${keypath} is not supported by your version of Svelte. Please upgrade to Svelte 5.51.0 or later to use this directive.`
501
+ );
502
+ }
503
+ }
504
+
489
505
  export default options;
@@ -3,6 +3,7 @@ import { dirname, join } from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
4
  import { installPolyfills } from '../../exports/node/polyfills.js';
5
5
  import { mkdirp, posixify, walk } from '../../utils/filesystem.js';
6
+ import { noop } from '../../utils/functions.js';
6
7
  import { decode_uri, is_root_relative, resolve } from '../../utils/url.js';
7
8
  import { escape_html } from '../../utils/escape.js';
8
9
  import { logger } from '../utils.js';
@@ -69,7 +70,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) {
69
70
  log.error(format(details));
70
71
  };
71
72
  case 'ignore':
72
- return () => {};
73
+ return noop;
73
74
  default:
74
75
  // @ts-expect-error TS thinks T might be of a different kind, but it's not
75
76
  return (details) => input({ ...details, message: format(details) });
@@ -6,6 +6,8 @@ import { import_peer } from '../../utils/import.js';
6
6
  /** @type {{ VERSION: string }} */
7
7
  const { VERSION } = await import_peer('svelte/compiler');
8
8
 
9
+ const [MAJOR, MINOR] = VERSION.split('.').map(Number);
10
+
9
11
  /** @type {Map<string, string>} */
10
12
  const previous_contents = new Map();
11
13
 
@@ -74,5 +76,10 @@ export function dedent(strings, ...values) {
74
76
  }
75
77
 
76
78
  export function isSvelte5Plus() {
77
- return Number(VERSION[0]) >= 5;
79
+ return MAJOR >= 5;
80
+ }
81
+
82
+ // TODO 3.0 remove this once we can bump the peerDep range
83
+ export function supportsTrustedTypes() {
84
+ return (MAJOR === 5 && MINOR >= 51) || MAJOR > 5;
78
85
  }
package/src/core/utils.js CHANGED
@@ -4,6 +4,7 @@ import process from 'node:process';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import colors from 'kleur';
6
6
  import { posixify, to_fs } from '../utils/filesystem.js';
7
+ import { noop } from '../utils/functions.js';
7
8
 
8
9
  /**
9
10
  * Resolved path of the `runtime` directory
@@ -24,8 +25,6 @@ export const runtime_base = runtime_directory.startsWith(process.cwd())
24
25
  ? `/${path.relative('.', runtime_directory)}`
25
26
  : to_fs(runtime_directory);
26
27
 
27
- function noop() {}
28
-
29
28
  /** @param {{ verbose: boolean }} opts */
30
29
  export function logger({ verbose }) {
31
30
  /** @type {import('types').Logger} */
@@ -2,6 +2,7 @@ import { createReadStream } from 'node:fs';
2
2
  import { Readable } from 'node:stream';
3
3
  import * as set_cookie_parser from 'set-cookie-parser';
4
4
  import { SvelteKitError } from '../internal/index.js';
5
+ import { noop } from '../../utils/functions.js';
5
6
 
6
7
  /**
7
8
  * @param {import('http').IncomingMessage} req
@@ -200,7 +201,7 @@ export async function setResponse(res, response) {
200
201
 
201
202
  // If the reader has already been interrupted with an error earlier,
202
203
  // then it will appear here, it is useless, but it needs to be catch.
203
- reader.cancel(error).catch(() => {});
204
+ reader.cancel(error).catch(noop);
204
205
  if (error) res.destroy(error);
205
206
  };
206
207
 
@@ -1893,28 +1893,35 @@ type InputElementProps<T extends keyof InputTypeMap> = T extends 'checkbox' | 'r
1893
1893
  get files(): FileList | null;
1894
1894
  set files(v: FileList | null);
1895
1895
  }
1896
- : T extends 'select' | 'select multiple'
1896
+ : T extends 'select'
1897
1897
  ? {
1898
1898
  name: string;
1899
- multiple: T extends 'select' ? false : true;
1900
1899
  'aria-invalid': boolean | 'false' | 'true' | undefined;
1901
- get value(): string | number;
1902
- set value(v: string | number);
1900
+ get value(): string;
1901
+ set value(v: string);
1903
1902
  }
1904
- : T extends 'text'
1903
+ : T extends 'select multiple'
1905
1904
  ? {
1906
1905
  name: string;
1906
+ multiple: true;
1907
1907
  'aria-invalid': boolean | 'false' | 'true' | undefined;
1908
- get value(): string | number;
1909
- set value(v: string | number);
1908
+ get value(): string[];
1909
+ set value(v: string[]);
1910
1910
  }
1911
- : {
1912
- name: string;
1913
- type: T;
1914
- 'aria-invalid': boolean | 'false' | 'true' | undefined;
1915
- get value(): string | number;
1916
- set value(v: string | number);
1917
- };
1911
+ : T extends 'text'
1912
+ ? {
1913
+ name: string;
1914
+ 'aria-invalid': boolean | 'false' | 'true' | undefined;
1915
+ get value(): string | number;
1916
+ set value(v: string | number);
1917
+ }
1918
+ : {
1919
+ name: string;
1920
+ type: T;
1921
+ 'aria-invalid': boolean | 'false' | 'true' | undefined;
1922
+ get value(): string | number;
1923
+ set value(v: string | number);
1924
+ };
1918
1925
 
1919
1926
  type RemoteFormFieldMethods<T> = {
1920
1927
  /** The values that will be submitted */
@@ -2075,10 +2082,10 @@ export type RemoteForm<Input extends RemoteFormInput | void, Output> = {
2075
2082
  callback: (opts: {
2076
2083
  form: HTMLFormElement;
2077
2084
  data: Input;
2078
- submit: () => Promise<void> & {
2079
- updates: (...updates: RemoteQueryUpdate[]) => Promise<void>;
2085
+ submit: () => Promise<boolean> & {
2086
+ updates: (...updates: RemoteQueryUpdate[]) => Promise<boolean>;
2080
2087
  };
2081
- }) => void | Promise<void>
2088
+ }) => void
2082
2089
  ): {
2083
2090
  method: 'POST';
2084
2091
  action: string;
@@ -2172,7 +2179,7 @@ export type RemoteQuery<T> = RemoteResource<T> & {
2172
2179
  */
2173
2180
  refresh(): Promise<void>;
2174
2181
  /**
2175
- * Temporarily override the value of a query. This is used with the `updates` method of a [command](https://svelte.dev/docs/kit/remote-functions#command-Updating-queries) or [enhanced form submission](https://svelte.dev/docs/kit/remote-functions#form-enhance) to provide optimistic updates.
2182
+ * Temporarily override a query's value during a [single-flight mutation](https://svelte.dev/docs/kit/remote-functions#Single-flight-mutations) to provide optimistic updates.
2176
2183
  *
2177
2184
  * ```svelte
2178
2185
  * <script>
@@ -110,7 +110,7 @@ export async function build_service_worker(
110
110
  // .mjs so that esbuild doesn't incorrectly inject `export` https://github.com/vitejs/vite/issues/15379
111
111
  entryFileNames: `service-worker.${is_rolldown ? 'js' : 'mjs'}`,
112
112
  assetFileNames: `${kit.appDir}/immutable/assets/[name].[hash][extname]`,
113
- inlineDynamicImports: !is_rolldown
113
+ inlineDynamicImports: !is_rolldown ? true : undefined
114
114
  }
115
115
  },
116
116
  outDir: `${out}/client`,
@@ -0,0 +1,126 @@
1
+ /** @import { ServerMetadata } from 'types' */
2
+ /** @import { Rollup } from 'vite' */
3
+
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { Parser } from 'acorn';
7
+ import MagicString from 'magic-string';
8
+ import { posixify } from '../../../utils/filesystem.js';
9
+ import { import_peer } from '../../../utils/import.js';
10
+
11
+ /**
12
+ * @param {string} out
13
+ * @param {Array<{ hash: string, file: string }>} remotes
14
+ * @param {ServerMetadata} metadata
15
+ * @param {string} cwd
16
+ * @param {Rollup.OutputBundle} server_bundle
17
+ * @param {NonNullable<import('vitest/config').ViteUserConfig['build']>['sourcemap']} sourcemap
18
+ */
19
+ export async function treeshake_prerendered_remotes(
20
+ out,
21
+ remotes,
22
+ metadata,
23
+ cwd,
24
+ server_bundle,
25
+ sourcemap
26
+ ) {
27
+ if (remotes.length === 0) return;
28
+
29
+ const vite = /** @type {typeof import('vite')} */ (await import_peer('vite'));
30
+
31
+ for (const remote of remotes) {
32
+ const exports_map = metadata.remotes.get(remote.hash);
33
+ if (!exports_map) continue;
34
+
35
+ /** @type {string[]} */
36
+ const dynamic = [];
37
+ /** @type {string[]} */
38
+ const prerendered = [];
39
+
40
+ for (const [name, value] of exports_map) {
41
+ (value.dynamic ? dynamic : prerendered).push(name);
42
+ }
43
+
44
+ if (prerendered.length === 0) continue; // nothing to treeshake
45
+
46
+ // remove file extension
47
+ const remote_filename = path.basename(remote.file).split('.').slice(0, -1).join('.');
48
+
49
+ const remote_chunk = Object.values(server_bundle).find((chunk) => {
50
+ return chunk.name === remote_filename;
51
+ });
52
+
53
+ if (!remote_chunk) continue;
54
+
55
+ const chunk_path = posixify(path.relative(cwd, `${out}/server/${remote_chunk.fileName}`));
56
+
57
+ const code = fs.readFileSync(chunk_path, 'utf-8');
58
+ const parsed = Parser.parse(code, { sourceType: 'module', ecmaVersion: 'latest' });
59
+ const modified_code = new MagicString(code);
60
+
61
+ for (const fn of prerendered) {
62
+ for (const node of parsed.body) {
63
+ const declaration =
64
+ node.type === 'ExportNamedDeclaration'
65
+ ? node.declaration
66
+ : node.type === 'VariableDeclaration'
67
+ ? node
68
+ : null;
69
+
70
+ if (!declaration || declaration.type !== 'VariableDeclaration') continue;
71
+
72
+ for (const declarator of declaration.declarations) {
73
+ if (declarator.id.type === 'Identifier' && declarator.id.name === fn) {
74
+ modified_code.overwrite(
75
+ node.start,
76
+ node.end,
77
+ `const ${fn} = prerender('unchecked', () => { throw new Error('Unexpectedly called prerender function. Did you forget to set { dynamic: true } ?') });`
78
+ );
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ for (const node of parsed.body) {
85
+ if (node.type === 'ExportDefaultDeclaration') {
86
+ modified_code.remove(node.start, node.end);
87
+ }
88
+ }
89
+
90
+ const stubbed = modified_code.toString();
91
+ fs.writeFileSync(chunk_path, stubbed);
92
+
93
+ const bundle = /** @type {import('vite').Rollup.RollupOutput} */ (
94
+ await vite.build({
95
+ configFile: false,
96
+ build: {
97
+ write: false,
98
+ ssr: true,
99
+ target: 'esnext',
100
+ sourcemap,
101
+ rollupOptions: {
102
+ // avoid resolving imports
103
+ external: (id) => !id.endsWith(chunk_path),
104
+ input: {
105
+ treeshaken: chunk_path
106
+ }
107
+ }
108
+ }
109
+ })
110
+ );
111
+
112
+ const chunk = bundle.output.find(
113
+ (output) => output.type === 'chunk' && output.name === 'treeshaken'
114
+ );
115
+ if (chunk && chunk.type === 'chunk') {
116
+ fs.writeFileSync(chunk_path, chunk.code);
117
+
118
+ const chunk_sourcemap = bundle.output.find(
119
+ (output) => output.type === 'asset' && output.fileName === chunk.fileName + '.map'
120
+ );
121
+ if (chunk_sourcemap && chunk_sourcemap.type === 'asset') {
122
+ fs.writeFileSync(chunk_path + '.map', chunk_sourcemap.source);
123
+ }
124
+ }
125
+ }
126
+ }
@@ -1,3 +1,5 @@
1
+ /** @import { RequestEvent } from '@sveltejs/kit' */
2
+ /** @import { PrerenderOption, UniversalNode } from 'types' */
1
3
  import fs from 'node:fs';
2
4
  import path from 'node:path';
3
5
  import process from 'node:process';
@@ -15,7 +17,7 @@ import { SVELTE_KIT_ASSETS } from '../../../constants.js';
15
17
  import * as sync from '../../../core/sync/sync.js';
16
18
  import { get_mime_lookup, runtime_base } from '../../../core/utils.js';
17
19
  import { compact } from '../../../utils/array.js';
18
- import { not_found } from '../utils.js';
20
+ import { is_chrome_devtools_request, not_found } from '../utils.js';
19
21
  import { SCHEME } from '../../../utils/url.js';
20
22
  import { check_feature } from '../../../utils/features.js';
21
23
  import { escape_html } from '../../../utils/escape.js';
@@ -34,13 +36,19 @@ const vite_css_query_regex = /(?:\?|&)(?:raw|url|inline)(?:&|$)/;
34
36
  export async function dev(vite, vite_config, svelte_config, get_remotes) {
35
37
  installPolyfills();
36
38
 
39
+ /** @type {AsyncLocalStorage<{ event: RequestEvent, config: any, prerender: PrerenderOption }>} */
37
40
  const async_local_storage = new AsyncLocalStorage();
38
41
 
39
42
  globalThis.__SVELTEKIT_TRACK__ = (label) => {
40
43
  const context = async_local_storage.getStore();
41
44
  if (!context || context.prerender === true) return;
42
45
 
43
- check_feature(context.event.route.id, context.config, label, svelte_config.kit.adapter);
46
+ check_feature(
47
+ /** @type {string} */ (context.event.route.id),
48
+ context.config,
49
+ label,
50
+ svelte_config.kit.adapter
51
+ );
44
52
  };
45
53
 
46
54
  const fetch = globalThis.fetch;
@@ -68,7 +76,8 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) {
68
76
  async function loud_ssr_load_module(url) {
69
77
  try {
70
78
  return await vite.ssrLoadModule(url, { fixStacktrace: true });
71
- } catch (/** @type {any} */ err) {
79
+ } catch (/** @type {unknown} */ e) {
80
+ const err = /** @type {import('rollup').RollupError} */ (e);
72
81
  const msg = buildErrorMessage(err, [colors.red(`Internal server error: ${err.message}`)]);
73
82
 
74
83
  if (!vite.config.logger.hasErrorLogged(err)) {
@@ -77,13 +86,13 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) {
77
86
 
78
87
  vite.ws.send({
79
88
  type: 'error',
80
- err: {
89
+ err: /** @type {import('vite').ErrorPayload['err']} */ ({
81
90
  ...err,
82
91
  // these properties are non-enumerable and will
83
92
  // not be serialized unless we explicitly include them
84
93
  message: err.message,
85
- stack: err.stack
86
- }
94
+ stack: err.stack ?? ''
95
+ })
87
96
  });
88
97
 
89
98
  throw err;
@@ -204,7 +213,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) {
204
213
 
205
214
  if (node.universal) {
206
215
  if (node.page_options?.ssr === false) {
207
- result.universal = node.page_options;
216
+ result.universal = /** @type {UniversalNode} */ (node.page_options);
208
217
  } else {
209
218
  // TODO: explain why the file was loaded on the server if we fail to load it
210
219
  const { module, module_node } = await resolve(node.universal);
@@ -437,7 +446,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) {
437
446
  return () => {
438
447
  const serve_static_middleware = vite.middlewares.stack.find(
439
448
  (middleware) =>
440
- /** @type {function} */ (middleware.handle).name === 'viteServeStaticMiddleware'
449
+ /** @type {Function} */ (middleware.handle).name === 'viteServeStaticMiddleware'
441
450
  );
442
451
 
443
452
  // Vite will give a 403 on URLs like /test, /static, and /package.json preventing us from
@@ -467,6 +476,10 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) {
467
476
  return;
468
477
  }
469
478
 
479
+ if (is_chrome_devtools_request(decoded, res)) {
480
+ return;
481
+ }
482
+
470
483
  if (!decoded.startsWith(svelte_config.kit.paths.base)) {
471
484
  return not_found(req, res, svelte_config.kit.paths.base);
472
485
  }