Skip to content
Open
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
106 changes: 106 additions & 0 deletions packages/start/src/config/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { loadEnv, type Plugin } from "vite";

const LOADERS = {
node: `export default key => process.env[key];`,
"cloudflare-workers": `import { env } from 'cloudflare:workers';export default key => env[key];`,
"netlify-edge": `export default key => Netlify.env.get(key);`,
};

export interface EnvPluginOptions {
server?: {
runtime: keyof typeof LOADERS | (string & {});
load?: () => Record<string, string>;
prefix?: string;
};
client?: {
load?: () => Record<string, string>;
prefix?: string;
};
}

const SERVER_ENV = "env:server";
const CLIENT_ENV = "env:client";

const SERVER_RUNTIME_ENV = `${SERVER_ENV}/runtime`;

const SERVER_RUNTIME_LOADER = `${SERVER_RUNTIME_ENV}/loader`;

const DEFAULT_SERVER_PREFIX = "SERVER_";
const DEFAULT_CLIENT_PREFIX = "CLIENT_";

const SERVER_ONLY_MODULE = `throw new Error('Attempt to load server-only environment variables in client runtime.');`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we error on build-time, instead of run-time?
Maybe if it is done after the server-function & tree-shake

Copy link
Member Author

@lxsmnsyc lxsmnsyc Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yinonburgansky This actually yields error on build, but not dev.

fun fact: despite the "throw" side-effect, the missing module exports error yields first on runtime.


const SERVER_RUNTIME_CODE = `import load from '${SERVER_RUNTIME_LOADER}';

export default new Proxy({}, {
get(_, key) {
return load(key);
},
})`;

function convertObjectToModule(object: Record<string, string>): string {
let result = "";
for (const key in object) {
result += `export const ${key} = ${JSON.stringify(object[key])};`;
}
console.log(result);
return result;
}

export function envPlugin(options?: EnvPluginOptions): Plugin {
const currentOptions = options ?? {};
let env: string;
const serverPrefix = currentOptions.server?.prefix ?? DEFAULT_SERVER_PREFIX;
const clientPrefix = currentOptions.client?.prefix ?? DEFAULT_CLIENT_PREFIX;
const runtime = options?.server?.runtime ?? "node";
const runtimeCode = runtime in LOADERS ? LOADERS[runtime as keyof typeof LOADERS] : runtime;

return {
name: "solid-start:env",
enforce: "pre",
configResolved(config) {
env = config.mode !== "production" ? "development" : "production";
},
resolveId(id) {
if (
id === SERVER_ENV ||
id === CLIENT_ENV ||
id === SERVER_RUNTIME_ENV ||
id === SERVER_RUNTIME_LOADER
) {
return id;
}
return undefined;
},
load(id, opts) {
if (id === SERVER_ENV) {
if (!opts?.ssr) {
return SERVER_ONLY_MODULE;
}
const vars = currentOptions.server?.load
? currentOptions.server.load()
: loadEnv(env, '.', serverPrefix);
return convertObjectToModule(vars);
}
if (id === CLIENT_ENV) {
const vars = currentOptions.client?.load
? currentOptions.client.load()
: loadEnv(env, '.', clientPrefix);
return convertObjectToModule(vars);
}
if (id === SERVER_RUNTIME_LOADER) {
if (!opts?.ssr) {
return SERVER_ONLY_MODULE;
}
return runtimeCode;
}
if (id === SERVER_RUNTIME_ENV) {
if (!opts?.ssr) {
return SERVER_ONLY_MODULE;
}
return SERVER_RUNTIME_CODE;
}
return undefined;
},
};
}
3 changes: 3 additions & 0 deletions packages/start/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import solid, { type Options as SolidOptions } from "vite-plugin-solid";

import { DEFAULT_EXTENSIONS, VIRTUAL_MODULES, VITE_ENVIRONMENTS } from "./constants.ts";
import { devServer } from "./dev-server.ts";
import { type EnvPluginOptions, envPlugin } from "./env.ts";
import { SolidStartClientFileRouter, SolidStartServerFileRouter } from "./fs-router.ts";
import { fsRoutes } from "./fs-routes/index.ts";
import type { BaseFileSystemRouter } from "./fs-routes/router.ts";
Expand All @@ -32,6 +33,7 @@ export interface SolidStartOptions {
*/
mode?: "js" | "json";
};
env?: EnvPluginOptions;
}

const absolute = (path: string, root: string) =>
Expand Down Expand Up @@ -175,6 +177,7 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
},
}),
lazy(),
envPlugin(options?.env),
// Must be placed after fsRoutes, as treeShake will remove the
// server fn exports added in by this plugin
TanStackServerFnPlugin({
Expand Down
Loading