diff --git a/packages/plugin-vite/demo/islands/tests/EnvIsland.tsx b/packages/plugin-vite/demo/islands/tests/EnvIsland.tsx
new file mode 100644
index 00000000000..389e9c2d661
--- /dev/null
+++ b/packages/plugin-vite/demo/islands/tests/EnvIsland.tsx
@@ -0,0 +1,18 @@
+import { useEffect, useState } from "preact/hooks";
+
+export function EnvIsland() {
+ const [ready, setReady] = useState(false);
+ useEffect(() => {
+ setReady(true);
+ }, []);
+
+ const deno = Deno.env.get("FRESH_PUBLIC_FOO");
+ // deno-lint-ignore no-process-global
+ const nodeEnv = process.env.FRESH_PUBLIC_FOO;
+
+ return (
+
+
{JSON.stringify({ deno,nodeEnv},null,2)}
+
+ );
+}
diff --git a/packages/plugin-vite/demo/routes/tests/env.tsx b/packages/plugin-vite/demo/routes/tests/env.tsx
new file mode 100644
index 00000000000..a6a7b1daeee
--- /dev/null
+++ b/packages/plugin-vite/demo/routes/tests/env.tsx
@@ -0,0 +1,5 @@
+import { EnvIsland } from "../../islands/tests/EnvIsland.tsx";
+
+export default function Page() {
+ return ;
+}
diff --git a/packages/plugin-vite/src/plugins/patches.ts b/packages/plugin-vite/src/plugins/patches.ts
index 734cccc80c3..d11b902ec35 100644
--- a/packages/plugin-vite/src/plugins/patches.ts
+++ b/packages/plugin-vite/src/plugins/patches.ts
@@ -3,6 +3,7 @@ import * as babel from "@babel/core";
import { cjsPlugin } from "./patches/commonjs.ts";
import { jsxComments } from "./patches/jsx_comment.ts";
import babelReact from "@babel/preset-react";
+import { inlineEnvVarsPlugin } from "./patches/inline_env_vars.ts";
export function patches(): Plugin {
let isDev = false;
@@ -16,12 +17,8 @@ export function patches(): Plugin {
return true;
},
transform(code, id, options) {
- if (code.includes("__commonJS") || /\.(tsx?|m[jt]s)$/.test(id)) {
- return;
- }
-
const presets = [];
- if (!options?.ssr) {
+ if (!options?.ssr && /\.(tsx?|m[jt]s)$/.test(id)) {
presets.push([babelReact, {
runtime: "automatic",
importSource: "preact",
@@ -32,7 +29,13 @@ export function patches(): Plugin {
const res = babel.transformSync(code, {
filename: id,
babelrc: false,
- plugins: [cjsPlugin, jsxComments],
+ plugins: [
+ cjsPlugin,
+ jsxComments,
+ inlineEnvVarsPlugin(
+ isDev ? "development" : "production",
+ ),
+ ],
presets,
});
diff --git a/packages/plugin-vite/src/plugins/patches/inline_env_vars.ts b/packages/plugin-vite/src/plugins/patches/inline_env_vars.ts
new file mode 100644
index 00000000000..9f0f557ce81
--- /dev/null
+++ b/packages/plugin-vite/src/plugins/patches/inline_env_vars.ts
@@ -0,0 +1,66 @@
+import type { NodePath, types } from "@babel/core";
+
+export function inlineEnvVarsPlugin(mode: string) {
+ const allowed = new Map();
+ for (const [name, value] of Object.entries(Deno.env.toObject())) {
+ if (name.startsWith("FRESH_PUBLIC_")) {
+ allowed.set(name, value);
+ }
+ }
+
+ allowed.set("NODE_ENV", Deno.env.get("NODE_ENV") ?? mode);
+
+ return (
+ { types: t }: { types: typeof types },
+ ): babel.PluginObj => {
+ function replace(path: NodePath, name: string) {
+ if (allowed.has(name)) {
+ const value = allowed.get(name);
+
+ if (value !== undefined) {
+ path.replaceWith(t.stringLiteral(value));
+ } else {
+ path.replaceWith(t.identifier("undefined"));
+ }
+ }
+ }
+
+ return {
+ name: "fresh-env-var",
+ visitor: {
+ MemberExpression(path) {
+ // Check: process.env.*
+ if (
+ t.isMemberExpression(path.node.object) &&
+ t.isIdentifier(path.node.object.object) &&
+ path.node.object.object.name === "process" &&
+ t.isIdentifier(path.node.object.property) &&
+ path.node.object.property.name === "env" &&
+ t.isIdentifier(path.node.property)
+ ) {
+ const name = path.node.property.name;
+ replace(path, name);
+ }
+ },
+ CallExpression(path) {
+ // Check: Deno.env.get("")
+ if (
+ t.isMemberExpression(path.node.callee) &&
+ t.isMemberExpression(path.node.callee.object) &&
+ t.isIdentifier(path.node.callee.object.object) &&
+ path.node.callee.object.object.name === "Deno" &&
+ t.isIdentifier(path.node.callee.object.property) &&
+ path.node.callee.object.property.name === "env" &&
+ t.isIdentifier(path.node.callee.property) &&
+ path.node.callee.property.name === "get" &&
+ path.node.arguments.length > 0 &&
+ t.isStringLiteral(path.node.arguments[0])
+ ) {
+ const name = path.node.arguments[0].value;
+ replace(path, name);
+ }
+ },
+ },
+ };
+ };
+}
diff --git a/packages/plugin-vite/src/plugins/patches/inline_env_vars_test.ts b/packages/plugin-vite/src/plugins/patches/inline_env_vars_test.ts
new file mode 100644
index 00000000000..da0ff1f3bba
--- /dev/null
+++ b/packages/plugin-vite/src/plugins/patches/inline_env_vars_test.ts
@@ -0,0 +1,71 @@
+import { expect } from "@std/expect/expect";
+import * as babel from "@babel/core";
+import { inlineEnvVarsPlugin } from "./inline_env_vars.ts";
+import { usingEnv } from "../../../tests/test_utils.ts";
+
+function runTest(options: { input: string; expected: string; mode?: string }) {
+ const res = babel.transformSync(options.input, {
+ filename: "foo.js",
+ babelrc: false,
+ plugins: [inlineEnvVarsPlugin(options.mode ?? "development")],
+ });
+
+ const output = res?.code ?? "";
+ expect(output).toEqual(options.expected);
+}
+
+Deno.test("env vars - inline NODE_ENV", () => {
+ using _ = usingEnv("NODE_ENV", "foobar");
+ runTest({
+ input: `() => process.env.NODE_ENV`,
+ expected: `() => "foobar";`,
+ });
+});
+
+Deno.test("env vars - inline NODE_ENV mode", () => {
+ runTest({
+ input: `() => process.env.NODE_ENV`,
+ expected: `() => "asdf";`,
+ mode: "asdf",
+ });
+});
+
+Deno.test("env vars - inline custom process.env.*", () => {
+ using _ = usingEnv("FRESH_PUBLIC_FOO", "a");
+ runTest({
+ input: `() => process.env.FRESH_PUBLIC_FOO`,
+ expected: `() => "a";`,
+ });
+});
+
+Deno.test("env vars - inline Deno.env.get()", () => {
+ using _ = usingEnv("FRESH_PUBLIC_FOO", "b");
+ runTest({
+ input: `() => Deno.env.get("FRESH_PUBLIC_FOO")`,
+ expected: `() => "b";`,
+ });
+});
+
+Deno.test("env vars - inline Deno.env.get(NODE_ENV)", () => {
+ using _ = usingEnv("NODE_ENV", "c");
+ runTest({
+ input: `() => Deno.env.get("NODE_ENV")`,
+ expected: `() => "c";`,
+ });
+});
+
+Deno.test("env vars - inline Deno.env.get(NODE_ENV) mode", () => {
+ runTest({
+ input: `() => Deno.env.get("NODE_ENV")`,
+ expected: `() => "test";`,
+ mode: "test",
+ });
+});
+
+Deno.test("env vars - inline const _ = Deno.env.get()", () => {
+ using _ = usingEnv("FRESH_PUBLIC_FOO", "test");
+ runTest({
+ input: `const deno = Deno.env.get("FRESH_PUBLIC_FOO");`,
+ expected: `const deno = "test";`,
+ });
+});
diff --git a/packages/plugin-vite/tests/dev_server_test.ts b/packages/plugin-vite/tests/dev_server_test.ts
index 64c43b9c772..0f18fe2424d 100644
--- a/packages/plugin-vite/tests/dev_server_test.ts
+++ b/packages/plugin-vite/tests/dev_server_test.ts
@@ -144,3 +144,26 @@ Deno.test({
sanitizeResources: false,
sanitizeOps: false,
});
+
+Deno.test({
+ name: "vite dev - inline env vars",
+ fn: async () => {
+ await withDevServer(DEMO_DIR, async (address) => {
+ await withBrowser(async (page) => {
+ await page.goto(`${address}/tests/env`, {
+ waitUntil: "networkidle2",
+ });
+ await page.locator(".ready").wait();
+
+ const res = await page.locator("pre").evaluate((el) =>
+ // deno-lint-ignore no-explicit-any
+ (el as any).textContent ?? ""
+ );
+
+ expect(JSON.parse(res)).toEqual({ deno: "foobar", nodeEnv: "foobar" });
+ });
+ }, { FRESH_PUBLIC_FOO: "foobar" });
+ },
+ sanitizeResources: false,
+ sanitizeOps: false,
+});
diff --git a/packages/plugin-vite/tests/test_utils.ts b/packages/plugin-vite/tests/test_utils.ts
index 73e1cde9bd3..faa5bbfbe91 100644
--- a/packages/plugin-vite/tests/test_utils.ts
+++ b/packages/plugin-vite/tests/test_utils.ts
@@ -50,6 +50,7 @@ async function copyDir(from: string, to: string) {
export async function withDevServer(
fixtureDir: string,
fn: (address: string, dir: string) => void | Promise,
+ env: Record = {},
) {
await using tmp = await withTmpDir({
dir: path.join(import.meta.dirname!, ".."),
@@ -72,7 +73,7 @@ export default defineConfig({
);
await withChildProcessServer(
- { cwd: tmp.dir, args: ["run", "-A", "npm:vite", "--port", "0"] },
+ { cwd: tmp.dir, args: ["run", "-A", "npm:vite", "--port", "0"], env },
async (address) => await fn(address, tmp.dir),
);
}
@@ -110,3 +111,12 @@ export async function buildVite(fixtureDir: string) {
},
};
}
+
+export function usingEnv(name: string, value: string) {
+ Deno.env.set(name, value);
+ return {
+ [Symbol.dispose]: () => {
+ Deno.env.delete(name);
+ },
+ };
+}