diff --git a/packages/start/src/config/env.ts b/packages/start/src/config/env.ts new file mode 100644 index 000000000..dc3a0dff2 --- /dev/null +++ b/packages/start/src/config/env.ts @@ -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; + prefix?: string; + }; + client?: { + load?: () => 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])};`; + } + 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; + }, + }; +} 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({