Skip to content

Commit 265a930

Browse files
fix: eagerly initialise dynamic env vars from process.env at module-eval time
Previously, `$app/env/private` and `$app/env/public` dynamic variable values were only populated when `Server.init()` called `set_env()`. If any module reading these values was evaluated before `Server.init()` ran (e.g. via bundler chunk colocation with `instrumentation.server.js`), the values would be silently `undefined`. The `__sveltekit/env` module now calls `set_env(process.env)` at module-eval time, ensuring values are populated and validated before any module that imports `$app/env/*` is evaluated. On platforms without `process.env` (Cloudflare Workers, Vercel Edge), the guard skips initialization and `Server.init()` populates env as before. If required env vars are missing from `process.env`, `set_env` will throw — this is intentional, as the app cannot function without them. This also replaces the dev-only auto-init that was added in #16223, extending the same safety to production builds.
1 parent 2b5ae26 commit 265a930

5 files changed

Lines changed: 49 additions & 20 deletions

File tree

.changeset/eager-env-init.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
fix: eagerly initialise dynamic env vars from `process.env` at module-eval time
6+
7+
Previously, `$app/env/private` and `$app/env/public` dynamic variable values were only
8+
populated when `Server.init()` called `set_env()`. If any module reading these
9+
values was evaluated before `Server.init()` ran (e.g. via bundler chunk
10+
colocation with `instrumentation.server.js`), the values would be silently
11+
`undefined`.
12+
13+
The `__sveltekit/env` module now calls `set_env(process.env)` at module-eval
14+
time, ensuring values are populated and validated before any module that
15+
imports `$app/env/*` is evaluated. On platforms without `process.env`
16+
(Cloudflare Workers, Vercel Edge), the guard skips initialization and
17+
`Server.init()` populates env as before. If required env vars are missing from
18+
`process.env`, `set_env` will throw — this is intentional, as the app cannot
19+
function without them.

packages/kit/src/core/env.js

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,8 @@ export async function load_explicit_env(kit, file, root, mode) {
9494
* @param {Record<string, EnvVarConfig<any> | undefined> | null} variables
9595
* @param {Record<string, string>} env
9696
* @param {string | null} entry
97-
* @param {boolean} is_dev
9897
*/
99-
export function create_sveltekit_env(variables, env, entry, is_dev) {
98+
export function create_sveltekit_env(variables, env, entry) {
10099
const imports = entry
101100
? [
102101
`import { variables } from ${JSON.stringify(entry)};`,
@@ -132,6 +131,23 @@ export function create_sveltekit_env(variables, env, entry, is_dev) {
132131

133132
handle_issues(issues);
134133

134+
// Initialise env immediately from `process.env` at module-eval time. This ensures
135+
// dynamic env vars are populated (and validated) before any module that reads
136+
// `$app/env/private` or `$app/env/public` is evaluated — including modules that may
137+
// be pulled in transitively by `instrumentation.server.js` before `Server.init()` runs.
138+
// On platforms without `process.env` (e.g. Cloudflare Workers, Vercel Edge), the guard
139+
// skips initialization and `Server.init()` populates env instead. If required env vars
140+
// are missing from `process.env`, `set_env` will throw — this is intentional, as the
141+
// app cannot function without them.
142+
const has_dynamic_vars = setters.length > 0;
143+
144+
const eager_init = has_dynamic_vars
145+
? dedent`
146+
if (typeof process !== 'undefined' && process.env) {
147+
set_env(process.env);
148+
}`
149+
: '';
150+
135151
const blocks = [
136152
GENERATED_COMMENT,
137153
imports.join('\n'),
@@ -147,21 +163,9 @@ export function create_sveltekit_env(variables, env, entry, is_dev) {
147163
const issues = {};
148164
${setters.join('\n')}
149165
handle_issues(issues);
150-
}`
151-
];
152-
153-
// In dev, initialise the env immediately. Tools like `vite-node` load modules
154-
// through the Vite config but don't run the SvelteKit dev server, which is what
155-
// normally calls `set_env`. Without this, dynamic env vars imported from
156-
// `$app/env/public` and `$app/env/private` would be `undefined` in such contexts.
157-
if (is_dev) {
158-
/** @type {Record<string, string>} */
159-
const dev_env = {};
160-
for (const name of Object.keys(variables ?? {})) {
161-
if (name in env) dev_env[name] = env[name];
162-
}
163-
blocks.push(`set_env(${devalue.uneval(dev_env)});`);
164-
}
166+
}`,
167+
eager_init
168+
].filter(Boolean);
165169

166170
const module = blocks.join('\n\n');
167171

packages/kit/src/core/postbuild/analyse.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ async function analyse({
5353
internal.set_read_implementation((file) => createReadableStream(`${server_root}/server/${file}`));
5454

5555
// `set_env` lives in a separate module that imports the user's `src/env` config. We import it
56-
// *after* `set_building()` so that `building`-dependent expressions resolve correctly
56+
// *after* `set_building()` so that `building`-dependent expressions resolve correctly. The `env`
57+
// module also eagerly initialises from `process.env` at module-eval time, but the build context
58+
// may use values from `.env` files (via `loadEnv`) that aren't in `process.env`, so we override
59+
// with the correct values here.
5760
/** @type {import('__sveltekit/env')} */
5861
const { set_env } = await import(pathToFileURL(`${server_root}/server/env.js`).href);
5962
set_env(env);

packages/kit/src/core/postbuild/prerender.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
4949
internal.set_prerendering();
5050

5151
// `set_env` and `Server` live in modules that import the user's `src/env` config. We import them
52-
// *after* `set_building()` so that `building`-dependent expressions resolve correctly
52+
// *after* `set_building()` so that `building`-dependent expressions resolve correctly. The `env`
53+
// module also eagerly initialises from `process.env` at module-eval time, but the build context
54+
// may use values from `.env` files (via `loadEnv`) that aren't in `process.env`, so we override
55+
// with the correct values here.
5356
/** @type {import('__sveltekit/env')} */
5457
const { set_env } = await import(pathToFileURL(`${out}/server/env.js`).href);
5558
set_env(env);

packages/kit/src/exports/vite/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,7 @@ function kit({ svelte_config }) {
628628
return create_service_worker_module(svelte_config);
629629

630630
case sveltekit_env:
631-
return create_sveltekit_env(explicit_env_config, env, explicit_env_entry, !is_build);
631+
return create_sveltekit_env(explicit_env_config, env, explicit_env_entry);
632632

633633
case sveltekit_env_public_client:
634634
return create_sveltekit_env_public(

0 commit comments

Comments
 (0)