@sveltejs/kit 2.65.1 → 2.66.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 (39) hide show
  1. package/package.json +3 -3
  2. package/src/core/adapt/builder.js +33 -6
  3. package/src/core/config/index.js +60 -2
  4. package/src/core/config/options.js +12 -3
  5. package/src/core/env.js +7 -3
  6. package/src/core/postbuild/analyse.js +4 -1
  7. package/src/core/postbuild/prerender.js +13 -4
  8. package/src/core/sync/create_manifest_data/index.js +13 -2
  9. package/src/core/sync/write_client_manifest.js +2 -0
  10. package/src/core/sync/write_server.js +12 -10
  11. package/src/core/sync/write_tsconfig.js +1 -2
  12. package/src/exports/internal/env.js +1 -1
  13. package/src/exports/public.d.ts +13 -1
  14. package/src/exports/vite/build/build_server.js +6 -1
  15. package/src/exports/vite/build/build_service_worker.js +8 -2
  16. package/src/exports/vite/index.js +44 -16
  17. package/src/exports/vite/preview/index.js +13 -4
  18. package/src/exports/vite/static_analysis/index.js +8 -3
  19. package/src/runtime/app/paths/server.js +7 -1
  20. package/src/runtime/app/server/remote/form.js +2 -2
  21. package/src/runtime/client/client.js +30 -8
  22. package/src/runtime/client/fetcher.js +3 -2
  23. package/src/runtime/client/remote-functions/form.svelte.js +16 -6
  24. package/src/runtime/client/remote-functions/prerender.svelte.js +5 -0
  25. package/src/runtime/client/remote-functions/query/instance.svelte.js +8 -1
  26. package/src/runtime/client/remote-functions/query-live/instance.svelte.js +25 -8
  27. package/src/runtime/client/remote-functions/shared.svelte.js +7 -1
  28. package/src/runtime/client/types.d.ts +6 -0
  29. package/src/runtime/form-utils.js +1 -0
  30. package/src/runtime/server/page/render.js +15 -13
  31. package/src/runtime/server/remote.js +9 -3
  32. package/src/runtime/server/respond.js +4 -1
  33. package/src/types/internal.d.ts +3 -1
  34. package/src/types/private.d.ts +24 -1
  35. package/src/utils/shared-iterator.js +5 -0
  36. package/src/utils/url.js +1 -1
  37. package/src/version.js +1 -1
  38. package/types/index.d.ts +54 -11
  39. package/types/index.d.ts.map +2 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "2.65.1",
3
+ "version": "2.66.0",
4
4
  "description": "SvelteKit is the fastest way to build Svelte apps",
5
5
  "keywords": [
6
6
  "framework",
@@ -38,12 +38,12 @@
38
38
  "@types/connect": "^3.4.38",
39
39
  "@types/node": "^18.19.130",
40
40
  "@types/set-cookie-parser": "^2.4.7",
41
- "dts-buddy": "^0.8.0",
41
+ "dts-buddy": "^0.8.1",
42
42
  "jsdom": "^26.1.0",
43
43
  "rollup": "^4.59.0",
44
44
  "svelte": "^5.56.3",
45
45
  "typescript": "^5.3.3",
46
- "vite": "^6.4.2",
46
+ "vite": "^6.4.3",
47
47
  "vitest": "^4.1.7"
48
48
  },
49
49
  "peerDependencies": {
@@ -22,7 +22,19 @@ import { reserved } from '../env.js';
22
22
  import { handle_issues, validate } from '../../exports/internal/env.js';
23
23
 
24
24
  const pipe = promisify(pipeline);
25
- const extensions = ['.html', '.js', '.mjs', '.json', '.css', '.svg', '.xml', '.wasm', '.txt'];
25
+ const extensions = [
26
+ '.html',
27
+ '.js',
28
+ '.mjs',
29
+ '.json',
30
+ '.css',
31
+ '.svg',
32
+ '.xml',
33
+ '.wasm',
34
+ '.txt',
35
+ '.md',
36
+ '.mdx'
37
+ ];
26
38
 
27
39
  /**
28
40
  * Creates the Builder which is passed to adapters for building the application.
@@ -193,12 +205,12 @@ export function create_builder({
193
205
  },
194
206
 
195
207
  generateEnvModule() {
196
- if (!build_data.client?.uses_env_dynamic_public) return;
197
-
198
- const dest = `${config.kit.outDir}/output/prerendered/dependencies/${config.kit.appDir}/env.js`;
199
208
  const env = get_env(config.kit.env, vite_config.mode);
200
209
 
201
- const values = config.kit.experimental.explicitEnvironmentVariables ? {} : env.public;
210
+ const dest = `${config.kit.outDir}/output/prerendered/dependencies/${config.kit.appDir}`;
211
+
212
+ /** @type {string} */
213
+ let payload;
202
214
 
203
215
  if (config.kit.experimental.explicitEnvironmentVariables) {
204
216
  const variables = explicit_env_config ?? {};
@@ -206,15 +218,30 @@ export function create_builder({
206
218
  /** @type {Record<string, StandardSchemaV1.Issue[]>} */
207
219
  const issues = {};
208
220
 
221
+ /** @type {Record<string, any>} */
222
+ const values = {};
223
+
209
224
  for (const [name, config] of Object.entries(variables)) {
210
225
  if (config.static || !config.public) continue;
211
226
  values[name] = validate(variables, env.all[name], name, issues);
212
227
  }
213
228
 
214
229
  handle_issues(issues);
230
+
231
+ if (Object.keys(values).length === 0) return;
232
+
233
+ payload = devalue.uneval(values);
234
+
235
+ if (build_data.service_worker) {
236
+ write(`${dest}/env.script.js`, `globalThis.__sveltekit_sw={env:${payload}}`);
237
+ }
238
+ } else {
239
+ payload = devalue.uneval(env.public);
215
240
  }
216
241
 
217
- write(dest, `export const env=${devalue.uneval(values)}`);
242
+ if (build_data.client?.uses_env_dynamic_public) {
243
+ write(`${dest}/env.js`, `export const env=${payload}`);
244
+ }
218
245
  },
219
246
 
220
247
  generateManifest({ relativePath, routes: subset }) {
@@ -1,14 +1,72 @@
1
- /** @import { Config } from '@sveltejs/kit' */
1
+ /** @import { Config, KitConfig } from '@sveltejs/kit' */
2
+ /** @import { Options, SvelteConfig } from '@sveltejs/vite-plugin-svelte' */
2
3
  /** @import { ValidatedConfig } from 'types' */
3
4
  /** @import { ResolvedConfig } from 'vite' */
4
5
  import fs from 'node:fs';
5
6
  import path from 'node:path';
6
7
  import process from 'node:process';
7
8
  import * as url from 'node:url';
8
- import options from './options.js';
9
+ import { options, kit_options, kit_experimental_options } from './options.js';
9
10
  import { resolve_entry } from '../../utils/filesystem.js';
10
11
  import { import_peer } from '../../utils/import.js';
11
12
 
13
+ /**
14
+ * Splits the config passed to the `sveltekit` Vite plugin into the options that
15
+ * SvelteKit processes itself and the options that are forwarded to
16
+ * `vite-plugin-svelte`. SvelteKit makes no assumptions about which options
17
+ * `vite-plugin-svelte` accepts — it plucks out its own options and passes
18
+ * everything else along (`vite-plugin-svelte` does its own validation).
19
+ * @param {KitConfig & Omit<Options, 'onwarn'> & Pick<SvelteConfig, 'vitePlugin'>} config
20
+ * @returns {{ svelte_config: Config, vite_plugin_svelte_config: Record<string, any> }}
21
+ */
22
+ export function split_config(config) {
23
+ const { extensions, compilerOptions, vitePlugin, preprocess, ...rest } = config;
24
+
25
+ /** @type {KitConfig} */
26
+ const kit = {};
27
+
28
+ /** @type {Record<string, any>} */
29
+ const vite_plugin_svelte_config = {};
30
+
31
+ for (const key in rest) {
32
+ if (key === 'experimental') {
33
+ // `experimental` is a namespace that both SvelteKit and vite-plugin-svelte
34
+ // use, so pluck out the flags SvelteKit recognises and pass the rest along
35
+ const experimental = /** @type {Record<string, any>} */ (rest[key]) ?? {};
36
+
37
+ /** @type {Record<string, any>} */
38
+ const kit_experimental = {};
39
+ /** @type {Record<string, any>} */
40
+ const vps_experimental = {};
41
+
42
+ for (const flag in experimental) {
43
+ if (kit_experimental_options.includes(flag)) {
44
+ kit_experimental[flag] = experimental[flag];
45
+ } else {
46
+ vps_experimental[flag] = experimental[flag];
47
+ }
48
+ }
49
+
50
+ if (Object.keys(kit_experimental).length > 0) {
51
+ kit.experimental = kit_experimental;
52
+ }
53
+ if (Object.keys(vps_experimental).length > 0) {
54
+ vite_plugin_svelte_config.experimental = vps_experimental;
55
+ }
56
+ } else if (kit_options.includes(key)) {
57
+ // @ts-expect-error - we've verified this is one of SvelteKit's own options
58
+ kit[key] = rest[key];
59
+ } else {
60
+ vite_plugin_svelte_config[key] = /** @type {Record<string, any>} */ (rest)[key];
61
+ }
62
+ }
63
+
64
+ return {
65
+ svelte_config: { extensions, compilerOptions, vitePlugin, preprocess, kit },
66
+ vite_plugin_svelte_config
67
+ };
68
+ }
69
+
12
70
  /**
13
71
  * Loads the template (src/app.html by default) and validates that it has the
14
72
  * required content.
@@ -45,7 +45,7 @@ const directives = object({
45
45
  });
46
46
 
47
47
  /** @type {Validator} */
48
- const options = object(
48
+ export const options = object(
49
49
  {
50
50
  extensions: validate(['.svelte'], (input, keypath) => {
51
51
  if (!Array.isArray(input) || !input.every((page) => typeof page === 'string')) {
@@ -326,6 +326,17 @@ const options = object(
326
326
  true
327
327
  );
328
328
 
329
+ // Derive the names of SvelteKit's own config options from the schema, so they
330
+ // stay in sync automatically. These are used to separate Kit's options from
331
+ // `vite-plugin-svelte`'s options when config is passed via the Vite plugin.
332
+ const defaults = /** @type {Record<string, any>} */ (options({}, 'config'));
333
+
334
+ /** The names of the options that live under the `kit` namespace */
335
+ export const kit_options = Object.keys(defaults.kit);
336
+
337
+ /** The names of the options that live under the `kit.experimental` namespace */
338
+ export const kit_experimental_options = Object.keys(defaults.kit.experimental);
339
+
329
340
  /**
330
341
  * @param {Validator} fn
331
342
  * @param {(keypath: string) => string} get_message
@@ -502,5 +513,3 @@ function assert_trusted_types_supported(keypath) {
502
513
  );
503
514
  }
504
515
  }
505
-
506
- export default options;
package/src/core/env.js CHANGED
@@ -72,6 +72,10 @@ export async function load_explicit_env(kit, file, mode) {
72
72
  /** @type {Record<string, EnvVarConfig<any>>} */
73
73
  let variables;
74
74
 
75
+ /** @type {import('../runtime/app/env/internal.js')} */ (
76
+ await server.ssrLoadModule(`${runtime_directory}/app/env/internal.js`)
77
+ ).set_building();
78
+
75
79
  try {
76
80
  ({ variables } = await server.ssrLoadModule(file));
77
81
 
@@ -157,7 +161,7 @@ export function create_dynamic_module(type, dev_values, disabled) {
157
161
 
158
162
  /**
159
163
  * Creates the `__sveltekit/env` module
160
- * @param {Record<string, EnvVarConfig<any>> | null} variables
164
+ * @param {Record<string, EnvVarConfig<any> | undefined> | null} variables
161
165
  * @param {Record<string, string>} env
162
166
  * @param {string | null} entry
163
167
  */
@@ -176,7 +180,7 @@ export function create_sveltekit_env(variables, env, entry) {
176
180
  const issues = {};
177
181
 
178
182
  for (const [name, config] of Object.entries(variables ?? {})) {
179
- if (config.static) {
183
+ if (config?.static) {
180
184
  if (config.public) {
181
185
  const value = validate(variables ?? {}, env[name], name, issues);
182
186
  declarations.push(`explicit_public_env.${name} = ${devalue.uneval(value)};`);
@@ -186,7 +190,7 @@ export function create_sveltekit_env(variables, env, entry) {
186
190
  `const ${name} = validate(variables, env.${name}, ${JSON.stringify(name)}, issues);`
187
191
  );
188
192
 
189
- if (config.public) {
193
+ if (config?.public) {
190
194
  setters.push(`explicit_public_env.${name} = ${name};`);
191
195
  setters.push(`rendered_env.${name} = ${name};`);
192
196
  } else {
@@ -58,10 +58,13 @@ async function analyse({
58
58
  const public_env = filter_env(env, public_prefix, private_prefix);
59
59
  internal.set_private_env(private_env);
60
60
  internal.set_public_env(public_env);
61
- internal.set_env(env);
62
61
  internal.set_manifest(manifest);
63
62
  internal.set_read_implementation((file) => createReadableStream(`${server_root}/server/${file}`));
64
63
 
64
+ /** @type {import('__sveltekit/env')} */
65
+ const { set_env } = await import(pathToFileURL(`${server_root}/server/env.js`).href);
66
+ set_env(env);
67
+
65
68
  /** @type {import('types').ServerMetadata} */
66
69
  const metadata = {
67
70
  nodes: [],
@@ -43,14 +43,18 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) {
43
43
  /** @type {import('types').ServerInternalModule} */
44
44
  const internal = await import(pathToFileURL(`${out}/server/internal.js`).href);
45
45
 
46
- /** @type {import('types').ServerModule} */
47
- const { Server } = await import(pathToFileURL(`${out}/server/index.js`).href);
48
-
49
46
  // configure `import { building } from '$app/environment'` and `$app/env` —
50
47
  // essential we do this before analysing the code
51
48
  internal.set_building();
52
49
  internal.set_prerendering();
53
50
 
51
+ /** @type {import('__sveltekit/env')} */
52
+ const { set_env } = await import(pathToFileURL(`${out}/server/env.js`).href);
53
+ set_env(env);
54
+
55
+ /** @type {import('types').ServerModule} */
56
+ const { Server } = await import(pathToFileURL(`${out}/server/index.js`).href);
57
+
54
58
  /**
55
59
  * @template {{message: string}} T
56
60
  * @template {Omit<T, 'message'>} K
@@ -382,6 +386,12 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) {
382
386
  const type = headers['content-type'];
383
387
  const is_html = response_type === REDIRECT || type === 'text/html';
384
388
 
389
+ if (!is_html && response.status === 200 && decoded.slice(config.paths.base.length + 1) === '') {
390
+ throw new Error(
391
+ `Cannot prerender a root +server.js that returns a non-HTML response - static hosts always serve an HTML file for \`${config.paths.base || '/'}\``
392
+ );
393
+ }
394
+
385
395
  const file = output_filename(decoded, is_html);
386
396
  const dest = `${config.outDir}/output/prerendered/${category}/${file}`;
387
397
 
@@ -499,7 +509,6 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) {
499
509
  const public_env = filter_env(env, public_prefix, private_prefix);
500
510
  internal.set_private_env(private_env);
501
511
  internal.set_public_env(public_env);
502
- internal.set_env(env);
503
512
  internal.set_manifest(manifest);
504
513
  internal.set_read_implementation((file) => createReadableStream(`${out}/server/${file}`));
505
514
 
@@ -426,7 +426,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
426
426
 
427
427
  const indexes = new Map(nodes.map((node, i) => [node, i]));
428
428
 
429
- const node_analyser = create_node_analyser();
429
+ const node_analyser = create_node_analyser(cwd);
430
430
 
431
431
  // add the related layout, page, and error nodes for a route
432
432
  for (const route of routes) {
@@ -495,7 +495,18 @@ function create_routes_and_nodes(cwd, config, fallback) {
495
495
 
496
496
  for (const route of routes) {
497
497
  if (route.endpoint) {
498
- route.endpoint.page_options = get_page_options(route.endpoint.file);
498
+ route.endpoint.page_options = get_page_options(path.join(cwd, route.endpoint.file));
499
+ }
500
+
501
+ if (route.page && route.endpoint) {
502
+ const page = nodes[route.page.leaf];
503
+ if (page.page_options?.prerender || route.endpoint.page_options?.prerender) {
504
+ const endpoint_file = route.endpoint.file.split('/').pop();
505
+
506
+ throw new Error(
507
+ `Cannot prerender a route (${route.id}) with both a \`+page.svelte\` and a \`${endpoint_file}\``
508
+ );
509
+ }
499
510
  }
500
511
  }
501
512
 
@@ -183,6 +183,8 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
183
183
  export const decode = (type, value) => decoders[type](value);
184
184
 
185
185
  export { default as root } from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}';
186
+
187
+ export const get_error_template = () => import('../shared/error-template.js').then(m => m.default);
186
188
  `
187
189
  );
188
190
 
@@ -17,7 +17,6 @@ import { escape_html } from '../../utils/escape.js';
17
17
  * has_service_worker: boolean;
18
18
  * runtime_directory: string;
19
19
  * template: string;
20
- * error_page: string;
21
20
  * }} opts
22
21
  */
23
22
  const server_template = ({
@@ -26,15 +25,14 @@ const server_template = ({
26
25
  universal_hooks,
27
26
  has_service_worker,
28
27
  runtime_directory,
29
- template,
30
- error_page
28
+ template
31
29
  }) => `
32
30
  import root from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}';
33
31
  import { set_building, set_prerendering } from '$app/env/internal';
34
32
  import { set_assets } from '$app/paths/internal/server';
35
33
  import { set_manifest, set_read_implementation } from '__sveltekit/server';
36
- import { set_env } from '__sveltekit/env';
37
34
  import { set_private_env, set_public_env } from '${runtime_directory}/shared-server.js';
35
+ import error from '../shared/error-template.js';
38
36
 
39
37
  export const options = {
40
38
  app_template_contains_nonce: ${template.includes('%sveltekit.nonce%')},
@@ -63,9 +61,7 @@ export const options = {
63
61
  /%sveltekit\.env\.([^%]+)%/g,
64
62
  (_match, capture) => `" + (env[${s(capture)}] ?? "") + "`
65
63
  )},
66
- error: ({ status, message }) => ${s(error_page)
67
- .replace(/%sveltekit\.status%/g, '" + status + "')
68
- .replace(/%sveltekit\.error\.message%/g, '" + message + "')}
64
+ error
69
65
  },
70
66
  version_hash: ${s(hash(config.kit.version.name))}
71
67
  };
@@ -93,7 +89,7 @@ export async function get_hooks() {
93
89
  };
94
90
  }
95
91
 
96
- export { set_assets, set_building, set_env, set_manifest, set_prerendering, set_private_env, set_public_env, set_read_implementation };
92
+ export { set_assets, set_building, set_manifest, set_prerendering, set_private_env, set_public_env, set_read_implementation };
97
93
  `;
98
94
 
99
95
  // TODO need to re-run this whenever src/app.html or src/error.html are
@@ -126,6 +122,13 @@ export function write_server(config, output) {
126
122
  return posixify(path.relative(`${output}/server`, file));
127
123
  }
128
124
 
125
+ write_if_changed(
126
+ `${output}/shared/error-template.js`,
127
+ `export default ({ status, message }) => ${s(load_error_page(config))
128
+ .replace(/%sveltekit\.status%/g, '" + status + "')
129
+ .replace(/%sveltekit\.error\.message%/g, '" + message + "')};`
130
+ );
131
+
129
132
  // Contains the stringified version of
130
133
  /** @type {import('types').SSROptions} */
131
134
  write_if_changed(
@@ -137,8 +140,7 @@ export function write_server(config, output) {
137
140
  has_service_worker:
138
141
  config.kit.serviceWorker.register && !!resolve_entry(config.kit.files.serviceWorker),
139
142
  runtime_directory: relative(runtime_directory),
140
- template: load_template(process.cwd(), config),
141
- error_page: load_error_page(config)
143
+ template: load_template(process.cwd(), config)
142
144
  })
143
145
  );
144
146
  }
@@ -122,8 +122,7 @@ export function get_tsconfig(kit) {
122
122
  moduleResolution: 'bundler',
123
123
  module: 'esnext',
124
124
  noEmit: true, // prevent tsconfig error "overwriting input files" - Vite handles the build and ignores this
125
- target: 'esnext',
126
- types: ['node']
125
+ target: 'esnext'
127
126
  },
128
127
  include: [...include],
129
128
  exclude
@@ -16,7 +16,7 @@ const ASYNC_VALIDATOR = {
16
16
  };
17
17
 
18
18
  /**
19
- * @param {Record<string, EnvVarConfig<any>>} variables
19
+ * @param {Record<string, EnvVarConfig<any> | undefined>} variables
20
20
  * @param {string | undefined} value
21
21
  * @param {string} name
22
22
  * @param {Record<string, StandardSchemaV1.Issue[]>} issues
@@ -141,7 +141,7 @@ export interface Builder {
141
141
  generateFallback: (dest: string) => Promise<void>;
142
142
 
143
143
  /**
144
- * Generate a module exposing build-time environment variables as `$env/dynamic/public` if the app uses it.
144
+ * Generate a module exposing build-time environment variables as `$env/dynamic/public` or `$app/env/public` if the app uses it.
145
145
  */
146
146
  generateEnvModule: () => void;
147
147
 
@@ -1550,6 +1550,10 @@ export interface RequestEvent<
1550
1550
  locals: App.Locals;
1551
1551
  /**
1552
1552
  * The parameters of the current route - e.g. for a route like `/blog/[slug]`, a `{ slug: string }` object.
1553
+ *
1554
+ * In the context of a remote function request initiated by the client, this relates to the page the remote function
1555
+ * was called from, _not_ the URL of the endpoint SvelteKit creates for the remote function. Never use this to determine
1556
+ * whether or not a user is authorized to access certain data, as these values are part of the request which could be manipulated.
1553
1557
  */
1554
1558
  params: Params;
1555
1559
  /**
@@ -1566,6 +1570,10 @@ export interface RequestEvent<
1566
1570
  route: {
1567
1571
  /**
1568
1572
  * The ID of the current route - e.g. for `src/routes/blog/[slug]`, it would be `/blog/[slug]`. It is `null` when no route is matched.
1573
+ *
1574
+ * In the context of a remote function request initiated by the client, this relates to the page the remote function
1575
+ * was called from, _not_ the URL of the endpoint SvelteKit creates for the remote function. Never use this to determine
1576
+ * whether or not a user is authorized to access certain data, as these values are part of the request which could be manipulated.
1569
1577
  */
1570
1578
  id: RouteId;
1571
1579
  };
@@ -1594,6 +1602,10 @@ export interface RequestEvent<
1594
1602
  setHeaders: (headers: Record<string, string>) => void;
1595
1603
  /**
1596
1604
  * The requested URL.
1605
+ *
1606
+ * In the context of a remote function request initiated by the client, this relates to the page the remote function
1607
+ * was called from, _not_ the URL of the endpoint SvelteKit creates for the remote function. Never use this to determine
1608
+ * whether or not a user is authorized to access certain data, as these values are part of the request which could be manipulated.
1597
1609
  */
1598
1610
  url: URL;
1599
1611
  /**
@@ -147,7 +147,12 @@ export function build_server_nodes(
147
147
  }
148
148
 
149
149
  if (node.universal) {
150
- if (!!node.page_options && node.page_options.ssr === false) {
150
+ if (
151
+ (kit.router.type === 'hash' &&
152
+ node.page_options !== null &&
153
+ node.page_options?.ssr === undefined) ||
154
+ (node.page_options && node.page_options.ssr === false)
155
+ ) {
151
156
  exports.push(`export const universal = ${s(node.page_options, null, 2)};`);
152
157
  } else {
153
158
  imports.push(
@@ -106,11 +106,17 @@ export async function build_service_worker(
106
106
  }
107
107
 
108
108
  if (id === '\0virtual:app/env/public') {
109
- // TODO ideally we would only add the `importScripts` if there are dynamic vars that are known to be used
109
+ const has_dynamic_public_env = Object.values(env_config ?? {}).some(
110
+ (variable) => variable.public && !variable.static
111
+ );
112
+
110
113
  return create_sveltekit_env_public(
111
114
  env_config,
112
115
  env.all,
113
- `importScripts('${kit.paths.base}/${kit.appDir}/env.script.js'); const env = globalThis.__sveltekit_sw.env;`
116
+ has_dynamic_public_env
117
+ ? // the service worker isn't registered as ESM yet, so we need to use `importScripts`
118
+ `importScripts('${kit.paths.base}/${kit.appDir}/env.script.js'); const env = globalThis.__sveltekit_sw.env;`
119
+ : ''
114
120
  );
115
121
  }
116
122
 
@@ -22,7 +22,7 @@ import {
22
22
  import * as sync from '../../core/sync/sync.js';
23
23
  import { create_assets } from '../../core/sync/create_manifest_data/index.js';
24
24
  import { runtime_directory, logger } from '../../core/utils.js';
25
- import { load_svelte_config, process_config } from '../../core/config/index.js';
25
+ import { load_svelte_config, process_config, split_config } from '../../core/config/index.js';
26
26
  import { generate_manifest } from '../../core/generate_manifest/index.js';
27
27
  import { build_server_nodes } from './build/build_server.js';
28
28
  import { build_service_worker } from './build/build_service_worker.js';
@@ -143,19 +143,27 @@ const warning_preprocessor = {
143
143
  /**
144
144
  * Returns the SvelteKit Vite plugins.
145
145
  * Since version 2.62.0 you can pass [configuration](configuration) directly, in which case `svelte.config.js` is ignored.
146
- * @param {KitConfig & Omit<SvelteConfig, 'onwarn'>} [config]
146
+ * Any options that don't belong to SvelteKit are passed through to `vite-plugin-svelte`.
147
+ * @param {KitConfig & Omit<Options, 'onwarn'> & Pick<SvelteConfig, 'vitePlugin'>} [config]
147
148
  * @returns {Promise<Plugin[]>}
148
149
  */
149
150
  export async function sveltekit(config) {
150
151
  /** @type {ValidatedConfig} */
151
152
  let svelte_config;
152
153
 
154
+ // any options passed to the plugin that SvelteKit doesn't use itself are
155
+ // forwarded to vite-plugin-svelte, which does its own validation
156
+ /** @type {Record<string, any>} */
157
+ let vite_plugin_svelte_config = {};
158
+
153
159
  if (config !== undefined) {
154
- const { extensions, compilerOptions, vitePlugin, preprocess, ...kit } = config;
155
- svelte_config = process_config(
156
- { extensions, compilerOptions, vitePlugin, preprocess, kit },
157
- { cwd, source: 'SvelteKit options from Vite config' }
158
- );
160
+ const split = split_config(config);
161
+ vite_plugin_svelte_config = split.vite_plugin_svelte_config;
162
+
163
+ svelte_config = process_config(split.svelte_config, {
164
+ cwd,
165
+ source: 'SvelteKit options from Vite config'
166
+ });
159
167
 
160
168
  const config_file = ['svelte.config.js', 'svelte.config.ts'].find((file) =>
161
169
  fs.existsSync(file)
@@ -180,6 +188,9 @@ export async function sveltekit(config) {
180
188
 
181
189
  /** @type {Options} */
182
190
  const vite_plugin_svelte_options = {
191
+ // pass through any options that SvelteKit doesn't use itself first, so
192
+ // that the options SvelteKit manages below always take precedence
193
+ ...vite_plugin_svelte_config,
183
194
  configFile: false,
184
195
  extensions: svelte_config.extensions,
185
196
  preprocess,
@@ -293,8 +304,11 @@ async function kit({ svelte_config }) {
293
304
  const allow = new Set([
294
305
  kit.files.lib,
295
306
  kit.files.routes,
307
+ kit.files.src,
296
308
  kit.outDir,
297
- path.resolve('src'), // TODO this isn't correct if user changed all his files to sth else than src (like in test/options)
309
+ // ensures that the client entry is served even if it's located outside
310
+ // the local node_modules, such as the pnpm global virtual store
311
+ runtime_directory,
298
312
  path.resolve('node_modules'),
299
313
  path.resolve(vite.searchForWorkspaceRoot(cwd), 'node_modules')
300
314
  ]);
@@ -958,6 +972,7 @@ async function kit({ svelte_config }) {
958
972
  if (ssr) {
959
973
  input.index = `${runtime_directory}/server/index.js`;
960
974
  input.internal = `${out_dir}/generated/server/internal.js`;
975
+ input.env = `__sveltekit/env`;
961
976
  input['remote-entry'] = `${runtime_directory}/app/server/remote/index.js`;
962
977
 
963
978
  // add entry points for every endpoint...
@@ -1375,6 +1390,17 @@ async function kit({ svelte_config }) {
1375
1390
  const deps_of = (entry, add_dynamic_css = false) =>
1376
1391
  find_deps(manifest, posixify(path.relative('.', entry)), add_dynamic_css);
1377
1392
 
1393
+ const has_explicit_dynamic_public_env = Object.values(explicit_env_config ?? {}).some(
1394
+ (variable) => variable.public && !variable.static
1395
+ );
1396
+
1397
+ const uses_env_dynamic_public = client_chunks.some(
1398
+ (chunk) =>
1399
+ chunk.type === 'chunk' &&
1400
+ (chunk.modules[env_dynamic_public] ||
1401
+ (has_explicit_dynamic_public_env && chunk.modules[sveltekit_env_public_client]))
1402
+ );
1403
+
1378
1404
  if (svelte_config.kit.output.bundleStrategy === 'split') {
1379
1405
  const start = deps_of(`${runtime_directory}/client/entry.js`);
1380
1406
  const app = deps_of(`${out_dir}/generated/client-optimized/app.js`);
@@ -1385,9 +1411,7 @@ async function kit({ svelte_config }) {
1385
1411
  imports: [...start.imports, ...app.imports],
1386
1412
  stylesheets: [...start.stylesheets, ...app.stylesheets],
1387
1413
  fonts: [...start.fonts, ...app.fonts],
1388
- uses_env_dynamic_public: client_chunks.some(
1389
- (chunk) => chunk.type === 'chunk' && chunk.modules[env_dynamic_public]
1390
- )
1414
+ uses_env_dynamic_public
1391
1415
  };
1392
1416
 
1393
1417
  // In case of server-side route resolution, we create a purpose-built route manifest that is
@@ -1434,9 +1458,7 @@ async function kit({ svelte_config }) {
1434
1458
  imports: start.imports,
1435
1459
  stylesheets: start.stylesheets,
1436
1460
  fonts: start.fonts,
1437
- uses_env_dynamic_public: client_chunks.some(
1438
- (chunk) => chunk.type === 'chunk' && chunk.modules[env_dynamic_public]
1439
- )
1461
+ uses_env_dynamic_public
1440
1462
  };
1441
1463
 
1442
1464
  if (svelte_config.kit.output.bundleStrategy === 'inline') {
@@ -1633,13 +1655,18 @@ function find_overridden_config(config, resolved_config, enforced_config, path,
1633
1655
  for (const key in enforced_config) {
1634
1656
  if (typeof config === 'object' && key in config && key in resolved_config) {
1635
1657
  const enforced = enforced_config[key];
1658
+ const resolved = resolved_config[key];
1636
1659
 
1637
1660
  if (enforced === true) {
1638
- if (config[key] !== resolved_config[key]) {
1661
+ // Normalize path separators before comparing to avoid false positives on Windows,
1662
+ // where config values like `root` may use backslashes while SvelteKit uses forward slashes.
1663
+ const a = typeof config[key] === 'string' ? posixify(config[key]) : config[key];
1664
+ const b = typeof resolved === 'string' ? posixify(resolved) : resolved;
1665
+ if (a !== b) {
1639
1666
  out.push(path + key);
1640
1667
  }
1641
1668
  } else {
1642
- find_overridden_config(config[key], resolved_config[key], enforced, path + key + '.', out);
1669
+ find_overridden_config(config[key], resolved, enforced, path + key + '.', out);
1643
1670
  }
1644
1671
  }
1645
1672
  }
@@ -1654,6 +1681,7 @@ const create_service_worker_module = (config) => dedent`
1654
1681
  throw new Error('This module can only be imported inside a service worker');
1655
1682
  }
1656
1683
 
1684
+ export const base = location.pathname.split('/').slice(0, -1).join('/');
1657
1685
  export const build = [];
1658
1686
  export const files = [
1659
1687
  ${create_assets(config)