diff --git a/.changeset/afraid-buses-pay.md b/.changeset/afraid-buses-pay.md new file mode 100644 index 0000000000..7571cfbdb2 --- /dev/null +++ b/.changeset/afraid-buses-pay.md @@ -0,0 +1,5 @@ +--- +"@react-router/dev": patch +--- + +Handle custom `envDir` in Vite config diff --git a/integration/helpers/vite.ts b/integration/helpers/vite.ts index 62b6359904..10e04ebd50 100644 --- a/integration/helpers/vite.ts +++ b/integration/helpers/vite.ts @@ -58,8 +58,14 @@ export const reactRouterConfig = ({ `; }; +type ViteConfigArgs = { + port: number; + fsAllow?: string[]; + envDir?: string; +}; + export const viteConfig = { - server: async (args: { port: number; fsAllow?: string[] }) => { + server: async (args: ViteConfigArgs) => { let { port, fsAllow } = args; let hmrPort = await getPort(); let text = dedent` @@ -72,7 +78,7 @@ export const viteConfig = { `; return text; }, - basic: async (args: { port: number; fsAllow?: string[] }) => { + basic: async (args: ViteConfigArgs) => { return dedent` import { reactRouter } from "@react-router/dev/vite"; import { envOnlyMacros } from "vite-env-only"; @@ -80,6 +86,7 @@ export const viteConfig = { export default { ${await viteConfig.server(args)} + envDir: ${args.envDir ? `"${args.envDir}"` : "undefined"}, plugins: [ reactRouter(), envOnlyMacros(), diff --git a/integration/vite-dotenv-test.ts b/integration/vite-dotenv-test.ts index dd27b43520..d96fe1df1a 100644 --- a/integration/vite-dotenv-test.ts +++ b/integration/vite-dotenv-test.ts @@ -8,73 +8,113 @@ import { viteConfig, } from "./helpers/vite.js"; -let files = { - ".env": ` - ENV_VAR_FROM_DOTENV_FILE=Content from .env file - `, - "app/routes/dotenv.tsx": String.raw` - import { useState, useEffect } from "react"; - import { useLoaderData } from "react-router"; - - export const loader = () => { - return { - loaderContent: process.env.ENV_VAR_FROM_DOTENV_FILE, - } - } +let getFiles = async ({ envDir, port }: { envDir?: string; port: number }) => { + let envPath = `${envDir ? `${envDir}/` : ""}.env`; - export default function DotenvRoute() { - const { loaderContent } = useLoaderData(); + return { + "vite.config.js": await viteConfig.basic({ port, envDir }), + "server.mjs": EXPRESS_SERVER({ port }), + [envPath]: ` + ENV_VAR_FROM_DOTENV_FILE=Content from ${envPath} file + `, + "app/routes/dotenv.tsx": String.raw` + import { useState, useEffect } from "react"; + import { useLoaderData } from "react-router"; - const [clientContent, setClientContent] = useState(''); - useEffect(() => { - try { - setClientContent("process.env.ENV_VAR_FROM_DOTENV_FILE shouldn't be available on the client, found: " + process.env.ENV_VAR_FROM_DOTENV_FILE); - } catch (err) { - setClientContent("process.env.ENV_VAR_FROM_DOTENV_FILE not available on the client, which is a good thing"); + export const loader = () => { + return { + loaderContent: process.env.ENV_VAR_FROM_DOTENV_FILE, } - }, []); - - return <> -
{loaderContent}
-
{clientContent}
- - } - `, + } + + export default function DotenvRoute() { + const { loaderContent } = useLoaderData(); + + const [clientContent, setClientContent] = useState(''); + useEffect(() => { + try { + setClientContent("process.env.ENV_VAR_FROM_DOTENV_FILE shouldn't be available on the client, found: " + process.env.ENV_VAR_FROM_DOTENV_FILE); + } catch (err) { + setClientContent("process.env.ENV_VAR_FROM_DOTENV_FILE not available on the client, which is a good thing"); + } + }, []); + + return <> +
{loaderContent}
+
{clientContent}
+ + } + `, + }; }; -test.describe(async () => { - let port: number; - let cwd: string; - let stop: () => void; - - test.beforeAll(async () => { - port = await getPort(); - cwd = await createProject({ - "vite.config.js": await viteConfig.basic({ port }), - "server.mjs": EXPRESS_SERVER({ port }), - ...files, +test.describe("Vite .env", () => { + test.describe("defaults", async () => { + let port: number; + let cwd: string; + let stop: () => void; + + test.beforeAll(async () => { + port = await getPort(); + cwd = await createProject(await getFiles({ port })); + stop = await customDev({ cwd, port }); + }); + test.afterAll(() => stop()); + + test("express", async ({ page }) => { + let pageErrors: unknown[] = []; + page.on("pageerror", (error) => pageErrors.push(error)); + + await page.goto(`http://localhost:${port}/dotenv`, { + waitUntil: "networkidle", + }); + expect(pageErrors).toEqual([]); + + let loaderContent = page.locator("[data-dotenv-route-loader-content]"); + await expect(loaderContent).toHaveText("Content from .env file"); + + let clientContent = page.locator("[data-dotenv-route-client-content]"); + await expect(clientContent).toHaveText( + "process.env.ENV_VAR_FROM_DOTENV_FILE not available on the client, which is a good thing" + ); + + expect(pageErrors).toEqual([]); }); - stop = await customDev({ cwd, port }); }); - test.afterAll(() => stop()); - test("Vite / Load context / express", async ({ page }) => { - let pageErrors: unknown[] = []; - page.on("pageerror", (error) => pageErrors.push(error)); + test.describe("custom env dir", async () => { + let port: number; + let cwd: string; + let stop: () => void; - await page.goto(`http://localhost:${port}/dotenv`, { - waitUntil: "networkidle", + test.beforeAll(async () => { + const envDir = "custom-env-dir"; + port = await getPort(); + cwd = await createProject(await getFiles({ envDir, port })); + stop = await customDev({ cwd, port }); }); - expect(pageErrors).toEqual([]); + test.afterAll(() => stop()); + + test("express", async ({ page }) => { + let pageErrors: unknown[] = []; + page.on("pageerror", (error) => pageErrors.push(error)); - let loaderContent = page.locator("[data-dotenv-route-loader-content]"); - await expect(loaderContent).toHaveText("Content from .env file"); + await page.goto(`http://localhost:${port}/dotenv`, { + waitUntil: "networkidle", + }); + expect(pageErrors).toEqual([]); - let clientContent = page.locator("[data-dotenv-route-client-content]"); - await expect(clientContent).toHaveText( - "process.env.ENV_VAR_FROM_DOTENV_FILE not available on the client, which is a good thing" - ); + let loaderContent = page.locator("[data-dotenv-route-loader-content]"); + await expect(loaderContent).toHaveText( + "Content from custom-env-dir/.env file" + ); - expect(pageErrors).toEqual([]); + let clientContent = page.locator("[data-dotenv-route-client-content]"); + await expect(clientContent).toHaveText( + "process.env.ENV_VAR_FROM_DOTENV_FILE not available on the client, which is a good thing" + ); + + expect(pageErrors).toEqual([]); + }); }); }); diff --git a/packages/react-router-dev/vite/plugin.ts b/packages/react-router-dev/vite/plugin.ts index a936096288..3ca46360d9 100644 --- a/packages/react-router-dev/vite/plugin.ts +++ b/packages/react-router-dev/vite/plugin.ts @@ -955,7 +955,7 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => { process.env, vite.loadEnv( viteConfigEnv.mode, - ctx.rootDirectory, + viteUserConfig.envDir ?? ctx.rootDirectory, // We override default prefix of "VITE_" with a blank string since // we're targeting the server, so we want to load all environment // variables, not just those explicitly marked for the client