Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/neat-items-stay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': minor
---

feat: allow dynamic `env` access during prerender
7 changes: 3 additions & 4 deletions packages/kit/src/core/postbuild/analyse.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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}`));

Expand Down
4 changes: 2 additions & 2 deletions packages/kit/src/core/sync/write_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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%')},
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions packages/kit/src/exports/vite/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
};
}

Expand Down
33 changes: 6 additions & 27 deletions packages/kit/src/runtime/server/index.js
Original file line number Diff line number Diff line change
@@ -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<any>} */
let init_promise;

Expand Down Expand Up @@ -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<ReadableStream>
Expand Down
4 changes: 2 additions & 2 deletions packages/kit/src/runtime/server/page/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand Down
13 changes: 1 addition & 12 deletions packages/kit/src/runtime/shared-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>}
*/
export let public_env = {};

/**
* The same as `public_env`, but without the proxy. Use for `%sveltekit.env.PUBLIC_FOO%`
* @type {Record<string, string>}
*/
export let safe_public_env = {};

/** @param {any} error */
export let fix_stack_trace = (error) => error?.stack;

Expand All @@ -29,11 +23,6 @@ export function set_public_env(environment) {
public_env = environment;
}

/** @type {(environment: Record<string, string>) => 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;
Expand Down
1 change: 0 additions & 1 deletion packages/kit/src/types/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ export interface ServerInternalModule {
set_private_env(environment: Record<string, string>): void;
set_public_env(environment: Record<string, string>): void;
set_read_implementation(implementation: (path: string) => ReadableStream): void;
set_safe_public_env(environment: Record<string, string>): void;
set_version(version: string): void;
set_fix_stack_trace(fix_stack_trace: (error: unknown) => string): void;
get_hooks: () => Promise<Record<string, any>>;
Expand Down
2 changes: 0 additions & 2 deletions packages/kit/src/types/synthetic/$env+dynamic+private.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 0 additions & 2 deletions packages/kit/src/types/synthetic/$env+dynamic+public.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
28 changes: 4 additions & 24 deletions packages/kit/src/utils/env.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,13 @@
/**
* @param {Record<string, string>} env
* @param {{
* public_prefix: string;
* private_prefix: string;
* }} prefixes
* @param {string} allowed
* @param {string} disallowed
* @returns {Record<string, string>}
*/
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<string, string>} env
* @param {{
* public_prefix: string;
* private_prefix: string;
* }} prefixes
* @returns {Record<string, string>}
*/
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))
)
);
}
Loading