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
18 changes: 18 additions & 0 deletions packages/plugin-vite/demo/islands/tests/EnvIsland.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div class={ready ? "ready" : ""}>
<pre>{JSON.stringify({ deno,nodeEnv},null,2)}</pre>
</div>
);
}
5 changes: 5 additions & 0 deletions packages/plugin-vite/demo/routes/tests/env.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { EnvIsland } from "../../islands/tests/EnvIsland.tsx";

export default function Page() {
return <EnvIsland />;
}
15 changes: 9 additions & 6 deletions packages/plugin-vite/src/plugins/patches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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",
Expand All @@ -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,
});

Expand Down
66 changes: 66 additions & 0 deletions packages/plugin-vite/src/plugins/patches/inline_env_vars.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { NodePath, types } from "@babel/core";

export function inlineEnvVarsPlugin(mode: string) {
const allowed = new Map<string, string>();
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("<string>")
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);
}
},
},
};
};
}
71 changes: 71 additions & 0 deletions packages/plugin-vite/src/plugins/patches/inline_env_vars_test.ts
Original file line number Diff line number Diff line change
@@ -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";`,
});
});
23 changes: 23 additions & 0 deletions packages/plugin-vite/tests/dev_server_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
12 changes: 11 additions & 1 deletion packages/plugin-vite/tests/test_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ async function copyDir(from: string, to: string) {
export async function withDevServer(
fixtureDir: string,
fn: (address: string, dir: string) => void | Promise<void>,
env: Record<string, string> = {},
) {
await using tmp = await withTmpDir({
dir: path.join(import.meta.dirname!, ".."),
Expand All @@ -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),
);
}
Expand Down Expand Up @@ -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);
},
};
}
Loading