@sveltejs/kit 2.61.1 → 2.63.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 (53) hide show
  1. package/package.json +14 -3
  2. package/src/cli.js +32 -6
  3. package/src/core/adapt/builder.js +31 -5
  4. package/src/core/adapt/index.js +5 -2
  5. package/src/core/config/index.js +100 -36
  6. package/src/core/config/options.js +1 -0
  7. package/src/core/env.js +313 -8
  8. package/src/core/postbuild/analyse.js +2 -1
  9. package/src/core/postbuild/fallback.js +7 -8
  10. package/src/core/postbuild/prerender.js +6 -2
  11. package/src/core/sync/sync.js +16 -0
  12. package/src/core/sync/write_ambient.js +12 -6
  13. package/src/core/sync/write_env.js +32 -0
  14. package/src/core/sync/write_root.js +1 -1
  15. package/src/core/sync/write_server.js +3 -2
  16. package/src/core/sync/write_tsconfig.js +1 -0
  17. package/src/exports/hooks/index.js +13 -0
  18. package/src/exports/internal/env.js +71 -0
  19. package/src/exports/internal/types.d.ts +3 -0
  20. package/src/exports/node/index.js +2 -10
  21. package/src/exports/public.d.ts +41 -1
  22. package/src/exports/vite/build/build_service_worker.js +20 -4
  23. package/src/exports/vite/dev/index.js +2 -2
  24. package/src/exports/vite/index.js +157 -33
  25. package/src/exports/vite/module_ids.js +10 -1
  26. package/src/exports/vite/utils.js +6 -0
  27. package/src/runtime/app/env/index.js +2 -0
  28. package/src/runtime/app/env/internal.js +11 -0
  29. package/src/runtime/app/env/private.js +1 -0
  30. package/src/runtime/app/env/public/client.js +7 -0
  31. package/src/runtime/app/env/public/index.js +1 -0
  32. package/src/runtime/app/env/public/server.js +7 -0
  33. package/src/runtime/app/env/standard-schema.d.ts +0 -0
  34. package/src/runtime/app/env/types.d.ts +19 -0
  35. package/src/runtime/app/environment/index.js +10 -2
  36. package/src/runtime/app/server/remote/query.js +1 -1
  37. package/src/runtime/app/state/client.js +10 -6
  38. package/src/runtime/client/client.js +68 -51
  39. package/src/runtime/client/remote-functions/prerender.svelte.js +1 -1
  40. package/src/runtime/client/utils.js +1 -1
  41. package/src/runtime/server/env_module.js +13 -3
  42. package/src/runtime/server/fetch.js +12 -14
  43. package/src/runtime/server/index.js +2 -0
  44. package/src/runtime/server/page/render.js +11 -3
  45. package/src/runtime/server/respond.js +8 -11
  46. package/src/types/ambient-private.d.ts +25 -9
  47. package/src/types/global-private.d.ts +2 -0
  48. package/src/types/internal.d.ts +1 -0
  49. package/src/utils/http.js +21 -0
  50. package/src/utils/import.js +2 -2
  51. package/src/version.js +1 -1
  52. package/types/index.d.ts +76 -4
  53. package/types/index.d.ts.map +7 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "2.61.1",
3
+ "version": "2.63.0",
4
4
  "description": "SvelteKit is the fastest way to build Svelte apps",
5
5
  "keywords": [
6
6
  "framework",
@@ -33,10 +33,10 @@
33
33
  },
34
34
  "devDependencies": {
35
35
  "@opentelemetry/api": "^1.0.0",
36
- "@playwright/test": "^1.59.1",
36
+ "@playwright/test": "^1.60.0",
37
37
  "@sveltejs/vite-plugin-svelte": "^6.0.0-next.3",
38
38
  "@types/connect": "^3.4.38",
39
- "@types/node": "^18.19.119",
39
+ "@types/node": "^18.19.130",
40
40
  "@types/set-cookie-parser": "^2.4.7",
41
41
  "dts-buddy": "^0.8.0",
42
42
  "jsdom": "^26.1.0",
@@ -76,6 +76,10 @@
76
76
  "#app/paths": {
77
77
  "browser": "./src/runtime/app/paths/client.js",
78
78
  "default": "./src/runtime/app/paths/server.js"
79
+ },
80
+ "#app/env/public": {
81
+ "browser": "./src/runtime/app/env/public/client.js",
82
+ "default": "./src/runtime/app/env/public/server.js"
79
83
  }
80
84
  },
81
85
  "exports": {
@@ -88,6 +92,13 @@
88
92
  "types": "./types/index.d.ts",
89
93
  "import": "./src/exports/internal/index.js"
90
94
  },
95
+ "./internal/env": {
96
+ "types": "./types/index.d.ts",
97
+ "import": "./src/exports/internal/env.js"
98
+ },
99
+ "./internal/types": {
100
+ "import": "./src/exports/internal/types.js"
101
+ },
91
102
  "./internal/server": {
92
103
  "types": "./types/index.d.ts",
93
104
  "import": "./src/exports/internal/server.js"
package/src/cli.js CHANGED
@@ -4,6 +4,7 @@ import { parseArgs } from 'node:util';
4
4
  import colors from 'kleur';
5
5
  import { load_config } from './core/config/index.js';
6
6
  import { coalesce_to_error } from './utils/error.js';
7
+ import { resolve_explicit_env_entry } from './core/env.js';
7
8
 
8
9
  /** @param {unknown} e */
9
10
  function handle_error(e) {
@@ -73,20 +74,45 @@ if (!command) {
73
74
  }
74
75
 
75
76
  if (command === 'sync') {
76
- const config_files = ['js', 'ts']
77
- .map((ext) => `svelte.config.${ext}`)
78
- .filter((f) => fs.existsSync(f));
79
- if (config_files.length === 0) {
80
- console.warn(`Missing Svelte config file in ${process.cwd()} — skipping`);
81
- process.exit(0);
77
+ // create placeholder .svelte-kit/tsconfig.json if necessary, to squelch warnings.
78
+ // this isn't bulletproof — if someone has some esoteric config, it will continue
79
+ // to harmlessly warn — but we handle the 90% case and clean up after ourselves
80
+ const sveltekit_dir = '.svelte-kit';
81
+ const base_tsconfig = `${sveltekit_dir}/tsconfig.json`;
82
+ const base_tsconfig_json = '{}';
83
+
84
+ const sveltekit_dir_exists = fs.existsSync(sveltekit_dir);
85
+ const base_tsconfig_exists = fs.existsSync(base_tsconfig);
86
+
87
+ if (!base_tsconfig_exists) {
88
+ try {
89
+ fs.mkdirSync('.svelte-kit');
90
+ } catch {
91
+ // ignore
92
+ }
93
+
94
+ fs.writeFileSync(base_tsconfig, base_tsconfig_json);
82
95
  }
83
96
 
84
97
  try {
85
98
  const config = await load_config();
86
99
  const sync = await import('./core/sync/sync.js');
87
100
  sync.all_types(config, values.mode);
101
+
102
+ const explicit_env_entry = resolve_explicit_env_entry(config.kit);
103
+ await sync.env(config.kit, explicit_env_entry, values.mode);
88
104
  } catch (error) {
89
105
  handle_error(error);
106
+ } finally {
107
+ // if we errored, or accidentally created the wrong file
108
+ // (could happen!) then clean up after ourselves
109
+ if (fs.readFileSync(base_tsconfig, 'utf-8') === base_tsconfig_json) {
110
+ fs.unlinkSync(base_tsconfig);
111
+ }
112
+
113
+ if (!sveltekit_dir_exists && fs.readdirSync(sveltekit_dir).length === 0) {
114
+ fs.rmSync(sveltekit_dir, { recursive: true });
115
+ }
90
116
  }
91
117
  } else {
92
118
  console.error(colors.bold().red(`> Unknown command: ${command}`));
@@ -1,8 +1,10 @@
1
+ /** @import { StandardSchemaV1 } from '@standard-schema/spec' */
1
2
  /** @import { Builder } from '@sveltejs/kit' */
2
3
  /** @import { ResolvedConfig } from 'vite' */
3
- /** @import { RouteDefinition } from '@sveltejs/kit' */
4
+ /** @import { RouteDefinition, EnvVarConfig } from '@sveltejs/kit' */
4
5
  /** @import { RouteData, ValidatedConfig, BuildData, ServerMetadata, ServerMetadataRoute, Prerendered, PrerenderMap, Logger, RemoteChunk } from 'types' */
5
6
  import colors from 'kleur';
7
+ import * as devalue from 'devalue';
6
8
  import { createReadStream, createWriteStream, existsSync, statSync } from 'node:fs';
7
9
  import { extname, resolve, join, dirname, relative } from 'node:path';
8
10
  import { pipeline } from 'node:stream';
@@ -17,6 +19,7 @@ import { write } from '../sync/utils.js';
17
19
  import { list_files } from '../utils.js';
18
20
  import { find_server_assets } from '../generate_manifest/find_server_assets.js';
19
21
  import { reserved } from '../env.js';
22
+ import { handle_issues, validate } from '../../exports/internal/env.js';
20
23
 
21
24
  const pipe = promisify(pipeline);
22
25
  const extensions = ['.html', '.js', '.mjs', '.json', '.css', '.svg', '.xml', '.wasm', '.txt'];
@@ -32,7 +35,8 @@ const extensions = ['.html', '.js', '.mjs', '.json', '.css', '.svg', '.xml', '.w
32
35
  * prerender_map: PrerenderMap;
33
36
  * log: Logger;
34
37
  * vite_config: ResolvedConfig;
35
- * remotes: RemoteChunk[]
38
+ * remotes: RemoteChunk[];
39
+ * explicit_env_config: Record<string, EnvVarConfig<any>> | null;
36
40
  * }} opts
37
41
  * @returns {Builder}
38
42
  */
@@ -45,7 +49,8 @@ export function create_builder({
45
49
  prerender_map,
46
50
  log,
47
51
  vite_config,
48
- remotes
52
+ remotes,
53
+ explicit_env_config
49
54
  }) {
50
55
  /** @type {Map<RouteDefinition, RouteData>} */
51
56
  const lookup = new Map();
@@ -168,7 +173,10 @@ export function create_builder({
168
173
 
169
174
  const fallback = await generate_fallback({
170
175
  manifest_path,
171
- env: { ...env.private, ...env.public }
176
+ env: env.all,
177
+ out_dir: config.kit.outDir,
178
+ origin: config.kit.prerender.origin,
179
+ assets: config.kit.files.assets
172
180
  });
173
181
 
174
182
  if (existsSync(dest)) {
@@ -185,10 +193,28 @@ export function create_builder({
185
193
  },
186
194
 
187
195
  generateEnvModule() {
196
+ if (!build_data.client?.uses_env_dynamic_public) return;
197
+
188
198
  const dest = `${config.kit.outDir}/output/prerendered/dependencies/${config.kit.appDir}/env.js`;
189
199
  const env = get_env(config.kit.env, vite_config.mode);
190
200
 
191
- write(dest, `export const env=${JSON.stringify(env.public)}`);
201
+ const values = config.kit.experimental.explicitEnvironmentVariables ? {} : env.public;
202
+
203
+ if (config.kit.experimental.explicitEnvironmentVariables) {
204
+ const variables = explicit_env_config ?? {};
205
+
206
+ /** @type {Record<string, StandardSchemaV1.Issue[]>} */
207
+ const issues = {};
208
+
209
+ for (const [name, config] of Object.entries(variables)) {
210
+ if (config.static || !config.public) continue;
211
+ values[name] = validate(variables, env.all[name], name, issues);
212
+ }
213
+
214
+ handle_issues(issues);
215
+ }
216
+
217
+ write(dest, `export const env=${devalue.uneval(values)}`);
192
218
  },
193
219
 
194
220
  generateManifest({ relativePath, routes: subset }) {
@@ -10,6 +10,7 @@ import { create_builder } from './builder.js';
10
10
  * @param {import('types').Logger} log
11
11
  * @param {import('types').RemoteChunk[]} remotes
12
12
  * @param {import('vite').ResolvedConfig} vite_config
13
+ * @param {Record<string, import('@sveltejs/kit').EnvVarConfig<any>> | null} explicit_env_config
13
14
  */
14
15
  export async function adapt(
15
16
  config,
@@ -19,7 +20,8 @@ export async function adapt(
19
20
  prerender_map,
20
21
  log,
21
22
  remotes,
22
- vite_config
23
+ vite_config,
24
+ explicit_env_config
23
25
  ) {
24
26
  // This is only called when adapter is truthy, so the cast is safe
25
27
  const { name, adapt } = /** @type {import('@sveltejs/kit').Adapter} */ (config.kit.adapter);
@@ -35,7 +37,8 @@ export async function adapt(
35
37
  prerender_map,
36
38
  log,
37
39
  remotes,
38
- vite_config
40
+ vite_config,
41
+ explicit_env_config
39
42
  });
40
43
 
41
44
  await adapt(builder);
@@ -1,15 +1,19 @@
1
+ /** @import { Config } from '@sveltejs/kit' */
2
+ /** @import { ValidatedConfig } from 'types' */
3
+ /** @import { ResolvedConfig } from 'vite' */
1
4
  import fs from 'node:fs';
2
5
  import path from 'node:path';
3
6
  import process from 'node:process';
4
7
  import * as url from 'node:url';
5
8
  import options from './options.js';
6
9
  import { resolve_entry } from '../../utils/filesystem.js';
10
+ import { import_peer } from '../../utils/import.js';
7
11
 
8
12
  /**
9
13
  * Loads the template (src/app.html by default) and validates that it has the
10
14
  * required content.
11
15
  * @param {string} cwd
12
- * @param {import('types').ValidatedConfig} config
16
+ * @param {ValidatedConfig} config
13
17
  */
14
18
  export function load_template(cwd, { kit }) {
15
19
  const { env, files } = kit;
@@ -29,11 +33,13 @@ export function load_template(cwd, { kit }) {
29
33
  }
30
34
  });
31
35
 
32
- for (const match of contents.matchAll(/%sveltekit\.env\.([^%]+)%/g)) {
33
- if (!match[1].startsWith(env.publicPrefix)) {
34
- throw new Error(
35
- `Environment variables in ${relative} must start with ${env.publicPrefix} (saw %sveltekit.env.${match[1]}%)`
36
- );
36
+ if (!kit.experimental.explicitEnvironmentVariables) {
37
+ for (const match of contents.matchAll(/%sveltekit\.env\.([^%]+)%/g)) {
38
+ if (!match[1].startsWith(env.publicPrefix)) {
39
+ throw new Error(
40
+ `Environment variables in ${relative} must start with ${env.publicPrefix} (saw %sveltekit.env.${match[1]}%)`
41
+ );
42
+ }
37
43
  }
38
44
  }
39
45
 
@@ -43,7 +49,7 @@ export function load_template(cwd, { kit }) {
43
49
  /**
44
50
  * Loads the error page (src/error.html by default) if it exists.
45
51
  * Falls back to a generic error page content.
46
- * @param {import('types').ValidatedConfig} config
52
+ * @param {ValidatedConfig} config
47
53
  */
48
54
  export function load_error_page(config) {
49
55
  let { errorTemplate } = config.kit.files;
@@ -58,11 +64,34 @@ export function load_error_page(config) {
58
64
  }
59
65
 
60
66
  /**
61
- * Loads and validates Svelte config file
67
+ * Loads and validates Svelte config file. Tries Vite config first, falls back to svelte.config.js
62
68
  * @param {{ cwd?: string }} options
63
- * @returns {Promise<import('types').ValidatedConfig>}
69
+ * @returns {Promise<ValidatedConfig>}
64
70
  */
65
71
  export async function load_config({ cwd = process.cwd() } = {}) {
72
+ try {
73
+ const vite_config = await load_config_from_vite({ cwd });
74
+ if (vite_config) {
75
+ return vite_config;
76
+ }
77
+ } catch (e) {
78
+ // TODO SvelteKit 3: fail completely instead
79
+ console.error(
80
+ 'Loading Svelte config from Vite config failed:',
81
+ e,
82
+ '\n\nFalling back to loading svelte.config.js'
83
+ );
84
+ }
85
+
86
+ return load_svelte_config(cwd);
87
+ }
88
+
89
+ /**
90
+ * Loads and validates Svelte config file
91
+ * @param {string} [cwd]
92
+ * @returns {Promise<ValidatedConfig>}
93
+ */
94
+ export async function load_svelte_config(cwd = process.cwd()) {
66
95
  const config_files = ['js', 'ts']
67
96
  .map((ext) => path.join(cwd, `svelte.config.${ext}`))
68
97
  .filter((f) => fs.existsSync(f));
@@ -73,52 +102,87 @@ export async function load_config({ cwd = process.cwd() } = {}) {
73
102
  );
74
103
  return process_config({}, { cwd });
75
104
  }
105
+
76
106
  const config_file = config_files[0];
77
107
  if (config_files.length > 1) {
78
108
  console.log(
79
109
  `Found multiple Svelte config files in ${cwd}: ${config_files.map((f) => path.basename(f)).join(', ')}. Using ${path.basename(config_file)}`
80
110
  );
81
111
  }
112
+
82
113
  const config = await import(`${url.pathToFileURL(config_file).href}?ts=${Date.now()}`);
114
+ return process_config(config.default, { cwd, source: path.relative(cwd, config_file) });
115
+ }
83
116
 
84
- try {
85
- return process_config(config.default, { cwd });
86
- } catch (e) {
87
- const error = /** @type {Error} */ (e);
117
+ /**
118
+ * Loads and validates Svelte config via Vite config resolution (if set that way).
119
+ * @param {{ cwd?: string; mode?: string }} options
120
+ * @returns {Promise<ValidatedConfig | undefined>}
121
+ */
122
+ async function load_config_from_vite({ cwd = process.cwd(), mode } = {}) {
123
+ const { resolveConfig } = await import_peer('vite');
124
+ const current_cwd = process.cwd();
88
125
 
89
- // redact the stack trace — it's not helpful to users
90
- error.stack = `Could not load ${config_file}: ${error.message}\n`;
91
- throw error;
126
+ if (cwd !== current_cwd) {
127
+ process.chdir(cwd);
128
+ }
129
+
130
+ /** @type {ResolvedConfig} */
131
+ let resolved;
132
+
133
+ try {
134
+ resolved = await resolveConfig({}, 'build', mode ?? process.env.MODE ?? 'production');
135
+ } finally {
136
+ if (cwd !== current_cwd) {
137
+ process.chdir(current_cwd);
138
+ }
92
139
  }
140
+
141
+ const plugin = resolved.plugins.find(
142
+ (plugin) => plugin.name === 'vite-plugin-sveltekit-setup' && plugin.api?.options
143
+ );
144
+
145
+ return plugin?.api.options;
93
146
  }
94
147
 
95
148
  /**
96
- * @param {import('@sveltejs/kit').Config} config
97
- * @returns {import('types').ValidatedConfig}
149
+ * @param {Config} config
150
+ * @returns {ValidatedConfig}
98
151
  */
99
- function process_config(config, { cwd = process.cwd() } = {}) {
100
- const validated = validate_config(config, cwd);
101
-
102
- validated.kit.outDir = path.resolve(cwd, validated.kit.outDir);
103
-
104
- for (const key in validated.kit.files) {
105
- if (key === 'hooks') {
106
- validated.kit.files.hooks.client = path.resolve(cwd, validated.kit.files.hooks.client);
107
- validated.kit.files.hooks.server = path.resolve(cwd, validated.kit.files.hooks.server);
108
- validated.kit.files.hooks.universal = path.resolve(cwd, validated.kit.files.hooks.universal);
109
- } else {
110
- // @ts-expect-error
111
- validated.kit.files[key] = path.resolve(cwd, validated.kit.files[key]);
152
+ export function process_config(config, { cwd = process.cwd(), source = 'svelte.config.js' } = {}) {
153
+ try {
154
+ const validated = validate_config(config, cwd);
155
+
156
+ validated.kit.outDir = path.resolve(cwd, validated.kit.outDir);
157
+
158
+ for (const key in validated.kit.files) {
159
+ if (key === 'hooks') {
160
+ validated.kit.files.hooks.client = path.resolve(cwd, validated.kit.files.hooks.client);
161
+ validated.kit.files.hooks.server = path.resolve(cwd, validated.kit.files.hooks.server);
162
+ validated.kit.files.hooks.universal = path.resolve(
163
+ cwd,
164
+ validated.kit.files.hooks.universal
165
+ );
166
+ } else {
167
+ // @ts-expect-error
168
+ validated.kit.files[key] = path.resolve(cwd, validated.kit.files[key]);
169
+ }
112
170
  }
113
- }
114
171
 
115
- return validated;
172
+ return validated;
173
+ } catch (e) {
174
+ const error = /** @type {Error} */ (e);
175
+
176
+ // redact the stack trace — it's not helpful to users
177
+ error.stack = `Error loading ${source}: ${error.message}\n`;
178
+ throw error;
179
+ }
116
180
  }
117
181
 
118
182
  /**
119
- * @param {import('@sveltejs/kit').Config} config
183
+ * @param {Config} config
120
184
  * @param {string} [cwd]
121
- * @returns {import('types').ValidatedConfig}
185
+ * @returns {ValidatedConfig}
122
186
  */
123
187
  export function validate_config(config, cwd = process.cwd()) {
124
188
  if (typeof config !== 'object') {
@@ -127,7 +191,7 @@ export function validate_config(config, cwd = process.cwd()) {
127
191
  );
128
192
  }
129
193
 
130
- /** @type {import('types').ValidatedConfig} */
194
+ /** @type {ValidatedConfig} */
131
195
  const validated = options(config, 'config');
132
196
  const files = validated.kit.files;
133
197
 
@@ -139,6 +139,7 @@ const options = object(
139
139
  instrumentation: object({
140
140
  server: boolean(false)
141
141
  }),
142
+ explicitEnvironmentVariables: boolean(false),
142
143
  remoteFunctions: boolean(false),
143
144
  forkPreloads: boolean(false),
144
145
  handleRenderingErrors: boolean(false)