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: 0 additions & 5 deletions .changeset/env-availability.md

This file was deleted.

20 changes: 8 additions & 12 deletions documentation/docs/20-core-concepts/70-environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,31 +121,27 @@ export const variables = defineEnvVars({
});
```

If a value is invalid, the app will fail to start (or build). If the variable is only available at run time (for example a secret that isn't set during the build), use `availability: 'runtime'` so that it is only validated when the app starts:
If a value is invalid, the app will fail to start (or build). To opt out of one or the other, use [`building`]($app-env#building) from `$app/env` along with a validator that accepts an optional value:

```ts
/// file: src/env.ts
import { defineEnvVars } from '@sveltejs/kit/hooks';
+++import { building } from '$app/env'+++
import * as v from 'valibot';

export const variables = defineEnvVars({
SECRET: {
// required when the app starts, but not validated during the build
+++availability: 'runtime',+++
schema: v.string()
// optional when building but required when starting the app
+++schema: building ? v.optional(v.string()) : v.string()+++
}
});
```

You can use validators to make values optional, or transform them (such as turning a string into a boolean, or parsing JSON) — see your validation library's documentation to learn how.

### Availability
### Static variables

The `availability` property controls when a variable's value is available, and therefore when it is validated. It has three possible values:

- `'dynamic'` (default) — the value is validated and used during runtime and build time.
- `'inline'` — the value is inlined into your application code at build time, enabling optimisations like dead-code elimination. It is validated at build time.
- `'runtime'` — the value is validated and used during runtime only. The value is `undefined` during the build, so the variable is typed as `T | undefined`.
By default, variables are dynamic. If a variable is configured with `static: true`, it will be inlined into your application code, enabling optimisations like dead-code elimination:

```ts
/// file: src/env.ts
Expand All @@ -155,7 +151,7 @@ import * as v from 'valibot';
export const variables = defineEnvVars({
SHOW_DEBUG_OVERLAY: {
public: true,
+++availability: 'inline',+++
+++static: true,+++

// coerce to true/false
schema: v.pipe(
Expand All @@ -166,7 +162,7 @@ export const variables = defineEnvVars({
});
```

Because this variable is `inline`, the `<DebugOverlay>` component shown here will be excluded from the JavaScript bundle unless `SHOW_DEBUG_OVERLAY` is truthy:
Because this variable is `static`, the `<DebugOverlay>` component shown here will be excluded from the JavaScript bundle unless `SHOW_DEBUG_OVERLAY` is truthy:

```svelte
<script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export function load() {
<p>This staging environment was deployed from {data.deploymentGitBranch}.</p>
```

Since all of these variables are unchanged between build time and run time when building on Vercel, we recommend configuring the variable with `availability: 'inline'` — which will statically replace the variables, enabling optimisations like dead code elimination.
Since all of these variables are unchanged between build time and run time when building on Vercel, we recommend configuring the variable with `static: true` — which will statically replace the variables, enabling optimisations like dead code elimination.

## Skew protection

Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-static/test/apps/prerendered/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import { defineEnvVars } from '@sveltejs/kit/hooks';
export const variables = defineEnvVars({
PUBLIC_ANSWER: {
public: true,
availability: 'inline'
static: true
}
});
2 changes: 1 addition & 1 deletion packages/adapter-static/test/apps/spa/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import { defineEnvVars } from '@sveltejs/kit/hooks';
export const variables = defineEnvVars({
PUBLIC_VALUE: {
public: true,
availability: 'inline'
static: true
}
});
5 changes: 1 addition & 4 deletions packages/kit/src/core/adapt/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,7 @@ export function create_builder({
const issues = {};

for (const [name, config] of Object.entries(variables)) {
const availability = config.availability ?? 'dynamic';
// only `dynamic` public vars are included in the prerendered env.js.
// `inline` is inlined, and `runtime` isn't available at build time
if (!config.public || availability !== 'dynamic') continue;
if (config.static || !config.public) continue;
values[name] = validate(variables, env[name], name, issues);
}

Expand Down
53 changes: 15 additions & 38 deletions packages/kit/src/core/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,46 +99,33 @@ export async function load_explicit_env(kit, file, root, mode) {
export function create_sveltekit_env(variables, env, entry, is_dev) {
const imports = entry
? [
`import { building } from '$app/env/internal';`,
`import { variables } from ${JSON.stringify(entry)};`,
`import { validate, handle_issues } from '@sveltejs/kit/internal/env';`
]
: [`const variables = {};`, `const handle_issues = () => {};`];

/** @type {string[]} */
const declarations = [];
/** @type {string[]} */
const setters = [];
/** @type {string[]} */
const runtime_setters = [];

/** @type {Record<string, StandardSchemaV1.Issue[]>} */
const issues = {};

for (const [name, config] of Object.entries(variables ?? {})) {
if (/** @type {any} */ (config)?.static) {
throw new Error('`static: true` has been removed. Use `availability: "inline"` instead.');
}

const availability = config?.availability ?? 'dynamic';

if (availability === 'inline') {
if (config?.public) {
if (config?.static) {
if (config.public) {
const value = validate(variables ?? {}, env[name], name, issues);
declarations.push(`explicit_public_env.${name} = ${devalue.uneval(value)};`);
}
} else {
const target = availability === 'runtime' ? runtime_setters : setters;

target.push(
setters.push(
`const ${name} = validate(variables, env.${name}, ${JSON.stringify(name)}, issues);`
);

if (config?.public) {
target.push(`explicit_public_env.${name} = ${name};`);
target.push(`rendered_env.${name} = ${name};`);
setters.push(`explicit_public_env.${name} = ${name};`);
setters.push(`rendered_env.${name} = ${name};`);
} else {
target.push(`dynamic_private_env.${name} = ${name};`);
setters.push(`dynamic_private_env.${name} = ${name};`);
}
}
}
Expand All @@ -159,11 +146,6 @@ export function create_sveltekit_env(variables, env, entry, is_dev) {
export function set_env(env) {
const issues = {};
${setters.join('\n')}
${
runtime_setters.length > 0
? `if (!building) {\n\t\t\t\t\t${runtime_setters.join('\n')}\n\t\t\t\t}`
: ''
}
handle_issues(issues);
}`
];
Expand Down Expand Up @@ -205,10 +187,9 @@ export function create_sveltekit_env_private(variables, env) {
for (const [name, config] of Object.entries(variables)) {
if (config.public) continue;

const value =
config.availability === 'inline'
? devalue.uneval(validate(variables, env[name], name, issues))
: `env.${name}`;
const value = config.static
? devalue.uneval(validate(variables, env[name], name, issues))
: `env.${name}`;

exports.push(`export const ${name} = ${value};\n`);
}
Expand Down Expand Up @@ -238,10 +219,9 @@ export function create_sveltekit_env_public(variables, env, prelude) {
for (const [name, config] of Object.entries(variables)) {
if (!config.public) continue;

const value =
config.availability === 'inline'
? devalue.uneval(validate(variables, env[name], name, issues))
: `env.${name}`;
const value = config.static
? devalue.uneval(validate(variables, env[name], name, issues))
: `env.${name}`;

exports.push(`export const ${name} = ${value};\n`);
}
Expand All @@ -263,7 +243,7 @@ export function create_sveltekit_env_public(variables, env, prelude) {
*/
export function create_sveltekit_env_service_worker(variables, env, global, base, app_dir) {
const has_dynamic_public_env = Object.values(variables ?? {}).some(
(config) => config.public && config.availability !== 'inline'
(config) => config.public && !config.static
);

if (!has_dynamic_public_env) {
Expand Down Expand Up @@ -330,12 +310,9 @@ export function create_explicit_env_types(variables, relative, type) {
.filter(([_, config]) => !!config.public === (type === 'public'))
.map(([name, config]) => {
const comment = config.description ? `${create_jsdoc(config.description)}\n` : '';
const maybe_undefined = config.availability === 'runtime';
const type = config.schema
? `import('@sveltejs/kit/internal/types').StandardSchemaV1.InferOutput<typeof import('${relative}').variables.${name}.schema>${maybe_undefined ? ' | undefined' : ''}`
: maybe_undefined
? 'string | undefined'
: 'string';
? `import('@sveltejs/kit/internal/types').StandardSchemaV1.InferOutput<typeof import('${relative}').variables.${name}.schema>`
: 'string';
return `${comment}export const ${name}: ${type};`;
});

Expand Down
11 changes: 5 additions & 6 deletions packages/kit/src/exports/public.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2454,13 +2454,12 @@ export interface EnvVarConfig<T> {
*/
public?: boolean;
/**
* When the variable's value is available, and therefore when it is validated.
* - `'dynamic'` — the value is validated and used during runtime and build time.
* - `'inline'` — the value is inlined into your application code at build time, enabling optimisations like dead-code elimination. It is validated at build time.
* - `'runtime'` — the value is validated and used during runtime only. The value is `undefined` during the build, so the variable is typed as `T | undefined`.
* @default 'dynamic'
* Whether the value is determined at build time or when the app runs.
* - if `true`, the build time value is inlined into the bundle. This enables optimisations like dead-code elimination
* - if `false`, the value is read from the environment when the app starts
* @default false
*/
availability?: 'inline' | 'dynamic' | 'runtime';
static?: boolean;
/**
* A [Standard Schema](https://standardschema.dev/) validator that is applied to the value when the app starts.
* The validator can output any value — not necessarily a string — but public, non-static values must be
Expand Down
4 changes: 2 additions & 2 deletions packages/kit/src/exports/vite/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1656,11 +1656,11 @@ function kit({ svelte_config }) {
find_deps(vite_manifest, posixify(path.relative(root, entry)), add_dynamic_css, root);

const has_explicit_dynamic_public_env = Object.values(explicit_env_config ?? {}).some(
(variable) => variable.public && variable.availability !== 'inline'
(variable) => variable.public && !variable.static
);

// the app only depends on runtime public env if it imports `$app/env/public`
// *and* at least one public env var is actually dynamic (not inlined at build time)
// *and* at least one public env var is actually dynamic (non-static)
const uses_env_dynamic_public =
has_explicit_dynamic_public_env &&
client_chunks.some(
Expand Down
8 changes: 4 additions & 4 deletions packages/kit/test/apps/async/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ import { defineEnvVars } from '@sveltejs/kit/hooks';
export const variables = defineEnvVars({
PRIVATE_STATIC: {
public: false,
availability: 'inline'
static: true
},
PRIVATE_DYNAMIC: {
public: false,
availability: 'dynamic'
static: false
},
PUBLIC_STATIC: {
public: true,
availability: 'inline'
static: true
},
PUBLIC_DYNAMIC: {
public: true,
availability: 'dynamic'
static: false
}
});
14 changes: 7 additions & 7 deletions packages/kit/test/apps/basics/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,30 @@ import { defineEnvVars } from '@sveltejs/kit/hooks';
export const variables = defineEnvVars({
PRIVATE_STATIC: {
public: false,
availability: 'inline'
static: true
},
PRIVATE_DYNAMIC: {
public: false,
availability: 'dynamic'
static: false
},
PUBLIC_STATIC: {
public: true,
availability: 'inline'
static: true
},
PUBLIC_DYNAMIC: {
public: true,
availability: 'dynamic'
static: false
},
PUBLIC_THEME: {
public: true,
availability: 'dynamic'
static: false
},
PUBLIC_PRERENDERING: {
public: true,
availability: 'dynamic'
static: false
},
SOME_JSON: {
public: false,
availability: 'inline'
static: true
}
});
8 changes: 4 additions & 4 deletions packages/kit/test/apps/options-2/src/env.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import process from 'node:process';
import { building } from '$app/env';
import * as v from 'valibot';

export const variables = {
MESSAGE: {
public: true,
description: 'Public env var loaded from the shared test env directory',
availability: process.env.DYNAMIC_PUBLIC_ENV ? 'dynamic' : 'inline'
static: !process.env.DYNAMIC_PUBLIC_ENV
},
PRIVATE_EXPLICIT_ENV: {},
PRIVATE_STATIC_EXPLICIT_ENV: {
availability: 'inline'
static: true
},
PRIVATE_VALIDATED_DEFAULT_ENV: {
schema: v.optional(v.picklist(['foo', 'bar']), 'foo')
},
RUNTIME_ONLY: {
availability: 'runtime',
schema: v.string()
schema: building ? v.optional(v.string()) : v.string()
}
};
2 changes: 1 addition & 1 deletion packages/kit/test/apps/options/source/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export const variables = defineEnvVars({
public: true
},
TOP_SECRET_SHH_PLS: {
availability: 'inline'
static: true
}
});
4 changes: 2 additions & 2 deletions packages/kit/test/prerendering/basics/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { defineEnvVars } from '@sveltejs/kit/hooks';
export const variables = defineEnvVars({
PUBLIC_STATIC: {
public: true,
availability: 'inline'
static: true
},
PRIVATE_STATIC: {
public: false,
availability: 'inline'
static: true
}
});
11 changes: 5 additions & 6 deletions packages/kit/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2425,13 +2425,12 @@ declare module '@sveltejs/kit' {
*/
public?: boolean;
/**
* When the variable's value is available, and therefore when it is validated.
* - `'dynamic'` — the value is validated and used during runtime and build time.
* - `'inline'` — the value is inlined into your application code at build time, enabling optimisations like dead-code elimination. It is validated at build time.
* - `'runtime'` — the value is validated and used during runtime only. The value is `undefined` during the build, so the variable is typed as `T | undefined`.
* @default 'dynamic'
* Whether the value is determined at build time or when the app runs.
* - if `true`, the build time value is inlined into the bundle. This enables optimisations like dead-code elimination
* - if `false`, the value is read from the environment when the app starts
* @default false
*/
availability?: 'inline' | 'dynamic' | 'runtime';
static?: boolean;
/**
* A [Standard Schema](https://standardschema.dev/) validator that is applied to the value when the app starts.
* The validator can output any value — not necessarily a string — but public, non-static values must be
Expand Down
Loading