From 82c4b75e6210df525ad2092f45f429d6c6d256a7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 16 Aug 2025 17:18:27 -0400 Subject: [PATCH 1/2] feat: allow dynamic `env` access during prerender --- .changeset/neat-items-stay.md | 5 +++ packages/kit/src/core/postbuild/analyse.js | 7 ++-- packages/kit/src/core/sync/write_server.js | 4 +-- packages/kit/src/exports/vite/utils.js | 6 ++-- packages/kit/src/runtime/server/index.js | 33 ++++--------------- .../kit/src/runtime/server/page/render.js | 4 +-- packages/kit/src/runtime/shared-server.js | 13 +------- packages/kit/src/types/internal.d.ts | 1 - packages/kit/src/utils/env.js | 28 +++------------- 9 files changed, 26 insertions(+), 75 deletions(-) create mode 100644 .changeset/neat-items-stay.md diff --git a/.changeset/neat-items-stay.md b/.changeset/neat-items-stay.md new file mode 100644 index 000000000000..d991b8565f38 --- /dev/null +++ b/.changeset/neat-items-stay.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': minor +--- + +feat: allow dynamic `env` access during prerender diff --git a/packages/kit/src/core/postbuild/analyse.js b/packages/kit/src/core/postbuild/analyse.js index 789e06677464..e991996eeb42 100644 --- a/packages/kit/src/core/postbuild/analyse.js +++ b/packages/kit/src/core/postbuild/analyse.js @@ -5,7 +5,7 @@ import { load_config } from '../config/index.js'; import { forked } from '../../utils/fork.js'; import { installPolyfills } from '../../exports/node/polyfills.js'; import { ENDPOINT_METHODS } from '../../constants.js'; -import { filter_private_env, filter_public_env } from '../../utils/env.js'; +import { filter_env } from '../../utils/env.js'; import { has_server_load, resolve_route } from '../../utils/routing.js'; import { check_feature } from '../../utils/features.js'; import { createReadableStream } from '@sveltejs/kit/node'; @@ -56,11 +56,10 @@ async function analyse({ // set env, in case it's used in initialisation const { publicPrefix: public_prefix, privatePrefix: private_prefix } = config.env; - const private_env = filter_private_env(env, { public_prefix, private_prefix }); - const public_env = filter_public_env(env, { public_prefix, private_prefix }); + const private_env = filter_env(env, private_prefix, public_prefix); + const public_env = filter_env(env, public_prefix, private_prefix); internal.set_private_env(private_env); internal.set_public_env(public_env); - internal.set_safe_public_env(public_env); internal.set_manifest(manifest); internal.set_read_implementation((file) => createReadableStream(`${server_root}/server/${file}`)); diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js index 308603d7c33e..4c8a3a40e680 100644 --- a/packages/kit/src/core/sync/write_server.js +++ b/packages/kit/src/core/sync/write_server.js @@ -32,7 +32,7 @@ import root from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}'; import { set_building, set_prerendering } from '__sveltekit/environment'; import { set_assets } from '__sveltekit/paths'; import { set_manifest, set_read_implementation } from '__sveltekit/server'; -import { set_private_env, set_public_env, set_safe_public_env } from '${runtime_directory}/shared-server.js'; +import { set_private_env, set_public_env } from '${runtime_directory}/shared-server.js'; export const options = { app_template_contains_nonce: ${template.includes('%sveltekit.nonce%')}, @@ -87,7 +87,7 @@ export async function get_hooks() { }; } -export { set_assets, set_building, set_manifest, set_prerendering, set_private_env, set_public_env, set_read_implementation, set_safe_public_env }; +export { set_assets, set_building, set_manifest, set_prerendering, set_private_env, set_public_env, set_read_implementation }; `; // TODO need to re-run this whenever src/app.html or src/error.html are diff --git a/packages/kit/src/exports/vite/utils.js b/packages/kit/src/exports/vite/utils.js index 864080b09a7b..77743bda77c9 100644 --- a/packages/kit/src/exports/vite/utils.js +++ b/packages/kit/src/exports/vite/utils.js @@ -2,7 +2,7 @@ import path from 'node:path'; import { loadEnv } from 'vite'; import { posixify } from '../../utils/filesystem.js'; import { negotiate } from '../../utils/http.js'; -import { filter_private_env, filter_public_env } from '../../utils/env.js'; +import { filter_env } from '../../utils/env.js'; import { escape_html } from '../../utils/escape.js'; import { dedent } from '../../core/sync/utils.js'; import { @@ -71,8 +71,8 @@ export function get_env(env_config, mode) { const env = loadEnv(mode, env_config.dir, ''); return { - public: filter_public_env(env, { public_prefix, private_prefix }), - private: filter_private_env(env, { public_prefix, private_prefix }) + public: filter_env(env, public_prefix, private_prefix), + private: filter_env(env, private_prefix, public_prefix) }; } diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 6ea5e85c1aef..d6c27d7a3069 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -1,21 +1,11 @@ import { respond } from './respond.js'; -import { set_private_env, set_public_env, set_safe_public_env } from '../shared-server.js'; +import { set_private_env, set_public_env } from '../shared-server.js'; import { options, get_hooks } from '__SERVER__/internal.js'; import { DEV } from 'esm-env'; -import { filter_private_env, filter_public_env } from '../../utils/env.js'; -import { prerendering } from '__sveltekit/environment'; +import { filter_env } from '../../utils/env.js'; import { set_read_implementation, set_manifest } from '__sveltekit/server'; import { set_app } from './app.js'; -/** @type {ProxyHandler<{ type: 'public' | 'private' }>} */ -const prerender_env_handler = { - get({ type }, prop) { - throw new Error( - `Cannot read values from $env/dynamic/${type} while prerendering (attempted to read env.${prop.toString()}). Use $env/static/${type} instead` - ); - } -}; - /** @type {Promise} */ let init_promise; @@ -44,21 +34,10 @@ export class Server { // been done already. // set env, in case it's used in initialisation - const prefixes = { - public_prefix: this.#options.env_public_prefix, - private_prefix: this.#options.env_private_prefix - }; - - const private_env = filter_private_env(env, prefixes); - const public_env = filter_public_env(env, prefixes); - - set_private_env( - prerendering ? new Proxy({ type: 'private' }, prerender_env_handler) : private_env - ); - set_public_env( - prerendering ? new Proxy({ type: 'public' }, prerender_env_handler) : public_env - ); - set_safe_public_env(public_env); + const { env_public_prefix, env_private_prefix } = this.#options; + + set_private_env(filter_env(env, env_private_prefix, env_public_prefix)); + set_public_env(filter_env(env, env_public_prefix, env_private_prefix)); if (read) { // Wrap the read function to handle MaybePromise diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 0d22352839bf..8e5f35c3ac16 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -9,7 +9,7 @@ import { s } from '../../../utils/misc.js'; import { Csp } from './csp.js'; import { uneval_action_response } from './actions.js'; import { clarify_devalue_error, handle_error_and_jsonify, serialize_uses } from '../utils.js'; -import { public_env, safe_public_env } from '../../shared-server.js'; +import { public_env } from '../../shared-server.js'; import { create_async_iterator } from '../../../utils/streaming.js'; import { SVELTE_KIT_ASSETS } from '../../../constants.js'; import { SCHEME } from '../../../utils/url.js'; @@ -558,7 +558,7 @@ export async function render_response({ body, assets, nonce: /** @type {string} */ (csp.nonce), - env: safe_public_env + env: public_env }); // TODO flush chunks as early as we can diff --git a/packages/kit/src/runtime/shared-server.js b/packages/kit/src/runtime/shared-server.js index 7bd6bde4c234..5a535448449f 100644 --- a/packages/kit/src/runtime/shared-server.js +++ b/packages/kit/src/runtime/shared-server.js @@ -5,17 +5,11 @@ export let private_env = {}; /** - * `$env/dynamic/public`. When prerendering, this will be a proxy that forbids reads + * `$env/dynamic/public` * @type {Record} */ export let public_env = {}; -/** - * The same as `public_env`, but without the proxy. Use for `%sveltekit.env.PUBLIC_FOO%` - * @type {Record} - */ -export let safe_public_env = {}; - /** @param {any} error */ export let fix_stack_trace = (error) => error?.stack; @@ -29,11 +23,6 @@ export function set_public_env(environment) { public_env = environment; } -/** @type {(environment: Record) => void} */ -export function set_safe_public_env(environment) { - safe_public_env = environment; -} - /** @param {(error: Error) => string} value */ export function set_fix_stack_trace(value) { fix_stack_trace = value; diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index ac3a0cfc3bbe..24d7d2bd4a7b 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -44,7 +44,6 @@ export interface ServerInternalModule { set_private_env(environment: Record): void; set_public_env(environment: Record): void; set_read_implementation(implementation: (path: string) => ReadableStream): void; - set_safe_public_env(environment: Record): void; set_version(version: string): void; set_fix_stack_trace(fix_stack_trace: (error: unknown) => string): void; get_hooks: () => Promise>; diff --git a/packages/kit/src/utils/env.js b/packages/kit/src/utils/env.js index e2106f342d4c..960846b9d821 100644 --- a/packages/kit/src/utils/env.js +++ b/packages/kit/src/utils/env.js @@ -1,33 +1,13 @@ /** * @param {Record} env - * @param {{ - * public_prefix: string; - * private_prefix: string; - * }} prefixes + * @param {string} allowed + * @param {string} disallowed * @returns {Record} */ -export function filter_private_env(env, { public_prefix, private_prefix }) { +export function filter_env(env, allowed, disallowed) { return Object.fromEntries( Object.entries(env).filter( - ([k]) => - k.startsWith(private_prefix) && (public_prefix === '' || !k.startsWith(public_prefix)) - ) - ); -} - -/** - * @param {Record} env - * @param {{ - * public_prefix: string; - * private_prefix: string; - * }} prefixes - * @returns {Record} - */ -export function filter_public_env(env, { public_prefix, private_prefix }) { - return Object.fromEntries( - Object.entries(env).filter( - ([k]) => - k.startsWith(public_prefix) && (private_prefix === '' || !k.startsWith(private_prefix)) + ([k]) => k.startsWith(allowed) && (disallowed === '' || !k.startsWith(disallowed)) ) ); } From 85d236cf2d97bfb81458a2af5de7373704973466 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 17 Aug 2025 07:30:39 -0400 Subject: [PATCH 2/2] update docs --- packages/kit/src/types/synthetic/$env+dynamic+private.md | 2 -- packages/kit/src/types/synthetic/$env+dynamic+public.md | 2 -- 2 files changed, 4 deletions(-) diff --git a/packages/kit/src/types/synthetic/$env+dynamic+private.md b/packages/kit/src/types/synthetic/$env+dynamic+private.md index 9000c4226e49..173419b6788d 100644 --- a/packages/kit/src/types/synthetic/$env+dynamic+private.md +++ b/packages/kit/src/types/synthetic/$env+dynamic+private.md @@ -2,8 +2,6 @@ This module provides access to runtime environment variables, as defined by the This module cannot be imported into client-side code. -Dynamic environment variables cannot be used during prerendering. - ```ts import { env } from '$env/dynamic/private'; console.log(env.DEPLOYMENT_SPECIFIC_VARIABLE); diff --git a/packages/kit/src/types/synthetic/$env+dynamic+public.md b/packages/kit/src/types/synthetic/$env+dynamic+public.md index 6e826d2fe2c0..d01ec645e9f1 100644 --- a/packages/kit/src/types/synthetic/$env+dynamic+public.md +++ b/packages/kit/src/types/synthetic/$env+dynamic+public.md @@ -2,8 +2,6 @@ Similar to [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-pri Note that public dynamic environment variables must all be sent from the server to the client, causing larger network requests — when possible, use `$env/static/public` instead. -Dynamic environment variables cannot be used during prerendering. - ```ts import { env } from '$env/dynamic/public'; console.log(env.PUBLIC_DEPLOYMENT_SPECIFIC_VARIABLE);