diff --git a/apps/tests/src/e2e/server-function.test.ts b/apps/tests/src/e2e/server-function.test.ts
index c5bb48a02..556c7ab94 100644
--- a/apps/tests/src/e2e/server-function.test.ts
+++ b/apps/tests/src/e2e/server-function.test.ts
@@ -81,4 +81,10 @@ test.describe("server-function", () => {
await page.goto("http://localhost:3000/server-function-blob");
await expect(page.locator("#server-fn-test")).toContainText('{"result":true}');
});
+
+ // TODO not sure if this is the correct place
+ test("should build with a env:server", async ({ page }) => {
+ await page.goto("http://localhost:3000/server-env");
+ await expect(page.locator("#server-fn-test")).toContainText('{"result":true}');
+ });
});
diff --git a/apps/tests/src/env.d.ts b/apps/tests/src/env.d.ts
new file mode 100644
index 000000000..42e11435f
--- /dev/null
+++ b/apps/tests/src/env.d.ts
@@ -0,0 +1,10 @@
+declare module "env:server" {
+ export const SERVER_EXAMPLE: string;
+}
+declare module "env:server/runtime" {
+ const env: {
+ NODE_ENV: string;
+ };
+
+ export default env;
+}
diff --git a/apps/tests/src/routes/server-env.tsx b/apps/tests/src/routes/server-env.tsx
new file mode 100644
index 000000000..db8f63325
--- /dev/null
+++ b/apps/tests/src/routes/server-env.tsx
@@ -0,0 +1,41 @@
+import { SERVER_EXAMPLE } from "env:server";
+import env from "env:server/runtime";
+import { createEffect, createSignal } from "solid-js";
+
+async function getServerCompiledEnv() {
+ "use server";
+
+ return await Promise.resolve(SERVER_EXAMPLE);
+}
+
+async function getServerRuntimeEnv() {
+ "use server";
+
+ return await Promise.resolve(env.NODE_ENV);
+}
+
+async function checkServerEnvOnClient() {
+ try {
+ await import("env:server");
+ return false;
+ } catch {
+ return true;
+ }
+}
+
+export default function App() {
+ const [output, setOutput] = createSignal<{ result?: boolean }>({});
+
+ createEffect(async () => {
+ const resultA = await getServerCompiledEnv();
+ const resultB = await getServerRuntimeEnv();
+ const checkImport = await checkServerEnvOnClient();
+ setOutput(prev => ({ ...prev, result: !!resultA && !!resultB && checkImport }));
+ });
+
+ return (
+
+ {JSON.stringify(output())}
+
+ );
+}
diff --git a/apps/tests/vite.config.ts b/apps/tests/vite.config.ts
index 4149be667..92f27738b 100644
--- a/apps/tests/vite.config.ts
+++ b/apps/tests/vite.config.ts
@@ -1,10 +1,30 @@
import { defineConfig } from "vite";
-import { solidStart } from "../../packages/start/src/config";
import { nitroV2Plugin } from "../../packages/start-nitro-v2-vite-plugin/src";
+import { solidStart } from "../../packages/start/src/config";
export default defineConfig({
server: {
port: 3000,
},
- plugins: [solidStart(), nitroV2Plugin()],
+ plugins: [
+ solidStart({
+ env: {
+ server: {
+ load() {
+ return {
+ SERVER_EXAMPLE: "This is a server example.",
+ };
+ },
+ },
+ client: {
+ load() {
+ return {
+ CLIENT_EXAMPLE: "This is a client example.",
+ };
+ },
+ },
+ },
+ }),
+ nitroV2Plugin(),
+ ],
});
diff --git a/packages/start/src/config/env.ts b/packages/start/src/config/env.ts
new file mode 100644
index 000000000..fb98c200e
--- /dev/null
+++ b/packages/start/src/config/env.ts
@@ -0,0 +1,105 @@
+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?: (mode: string) => Record;
+ prefix?: string;
+ };
+ client?: {
+ load?: (mode: string) => Record;
+ 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.');`;
+
+const SERVER_RUNTIME_CODE = `import load from '${SERVER_RUNTIME_LOADER}';
+
+export default new Proxy({}, {
+ get(_, key) {
+ return load(key);
+ },
+})`;
+
+function convertObjectToModule(object: Record): string {
+ let result = "";
+ for (const key in object) {
+ result += `export const ${key} = ${JSON.stringify(object[key])};`;
+ }
+ 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(env)
+ : loadEnv(env, '.', serverPrefix);
+ return convertObjectToModule(vars);
+ }
+ if (id === CLIENT_ENV) {
+ const vars = currentOptions.client?.load
+ ? currentOptions.client.load(env)
+ : 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;
+ },
+ };
+}
diff --git a/packages/start/src/config/index.ts b/packages/start/src/config/index.ts
index b8cf5bf58..653018db4 100644
--- a/packages/start/src/config/index.ts
+++ b/packages/start/src/config/index.ts
@@ -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";
@@ -32,6 +33,7 @@ export interface SolidStartOptions {
*/
mode?: "js" | "json";
};
+ env?: EnvPluginOptions;
}
const absolute = (path: string, root: string) =>
@@ -175,6 +177,7 @@ export function solidStart(options?: SolidStartOptions): Array {
},
}),
lazy(),
+ envPlugin(options?.env),
// Must be placed after fsRoutes, as treeShake will remove the
// server fn exports added in by this plugin
TanStackServerFnPlugin({