Skip to content

Commit b582e4f

Browse files
RFCreatepcattori
andauthored
fix(vite): loadEnv before reading routes (#14446)
* Signed CLA by adding RFCreate to list * feat: bump vite version to ^6.3.0 needed to resolve VITE_* variables * fix(vite): loadEnv before reading routes * test: write case where VITE_* variable is used in routes.ts * rewrite dotenv test with new fixtures also, restore testing dotenv for Vite v5 * changeset: switch to `patch` since we're now testing that it works with all major versions of Vite that we support --------- Co-authored-by: Pedro Cattori <[email protected]>
1 parent e04ad2b commit b582e4f

File tree

23 files changed

+212
-218
lines changed

23 files changed

+212
-218
lines changed

.changeset/dull-moons-share.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
"@react-router/dev": patch
3+
---
4+
5+
Load environment variables before evaluating `routes.ts`
6+
7+
For example, you can now compute your routes based on [`VITE_`-prefixed environment variables](https://vite.dev/guide/env-and-mode#env-variables):
8+
9+
```txt
10+
# .env
11+
VITE_ENV_ROUTE=my-route
12+
```
13+
14+
```ts
15+
// app/routes.ts
16+
import { type RouteConfig, route } from "@react-router/dev/routes";
17+
18+
const routes: RouteConfig = [];
19+
if (import.meta.env.VITE_ENV_ROUTE === "my-route") {
20+
routes.push(route("my-route", "routes/my-route.tsx"));
21+
}
22+
23+
export default routes;
24+
```

contributors.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@
330330
- remorses
331331
- renyu-io
332332
- reyronald
333+
- RFCreate
333334
- richardscarrott
334335
- rifaidev
335336
- rimian

integration/helpers/cloudflare-dev-proxy-template/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"@types/react": "^18.2.20",
2727
"@types/react-dom": "^18.2.7",
2828
"typescript": "^5.1.6",
29-
"vite": "^6.1.0",
29+
"vite": "^6.3.0",
3030
"wrangler": "^4.23.0"
3131
},
3232
"engines": {

integration/helpers/rsc-vite-framework/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"@vitejs/plugin-rsc": "0.4.30",
2525
"cross-env": "^7.0.3",
2626
"typescript": "^5.1.6",
27-
"vite": "^6.2.0",
27+
"vite": "^6.3.0",
2828
"vite-env-only": "^3.0.1",
2929
"vite-tsconfig-paths": "^4.2.1"
3030
},

integration/helpers/rsc-vite/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"@types/react-dom": "^19.1.6",
1818
"@vitejs/plugin-react": "^4.5.2",
1919
"typescript": "^5.1.6",
20-
"vite": "^6.2.0"
20+
"vite": "^6.3.0"
2121
},
2222
"dependencies": {
2323
"@mjackson/node-fetch-server": "0.6.1",

integration/helpers/vite-6-template/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"@types/react-dom": "^18.2.7",
3232
"eslint": "^8.38.0",
3333
"typescript": "^5.1.6",
34-
"vite": "^6.1.0",
34+
"vite": "^6.3.0",
3535
"vite-env-only": "^3.0.1",
3636
"vite-tsconfig-paths": "^4.2.1"
3737
},

integration/helpers/vite-plugin-cloudflare-template/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"@types/react-dom": "^18.2.7",
2828
"eslint": "^8.38.0",
2929
"typescript": "^5.1.6",
30-
"vite": "^6.1.0",
30+
"vite": "^6.3.0",
3131
"vite-tsconfig-paths": "^4.2.1",
3232
"wrangler": "^4.23.0"
3333
},

integration/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"strip-indent": "^3.0.0",
4545
"type-fest": "^4.0.0",
4646
"typescript": "^5.1.0",
47-
"vite": "^6.1.0",
47+
"vite": "^6.3.0",
4848
"vite-env-only": "^3.0.1",
4949
"vite-tsconfig-paths": "^4.2.2",
5050
"wait-on": "^7.0.1"

integration/vite-dotenv-test.ts

Lines changed: 62 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,20 @@
1-
import { test, expect } from "@playwright/test";
1+
import { expect } from "@playwright/test";
2+
import tsx from "dedent";
23
import getPort from "get-port";
34

4-
import {
5-
type TemplateName,
6-
createProject,
7-
customDev,
8-
EXPRESS_SERVER,
9-
viteConfig,
10-
} from "./helpers/vite.js";
11-
12-
const templateNames = [
13-
"vite-5-template",
14-
"rsc-vite-framework",
15-
] as const satisfies TemplateName[];
16-
17-
let getFiles = async ({
18-
templateName,
19-
envDir,
20-
port,
21-
}: {
22-
templateName: TemplateName;
23-
envDir?: string;
24-
port: number;
25-
}) => {
26-
let envPath = `${envDir ? `${envDir}/` : ""}.env`;
27-
28-
return {
29-
"vite.config.js": await viteConfig.basic({ templateName, port, envDir }),
30-
"server.mjs": EXPRESS_SERVER({ port, templateName }),
31-
[envPath]: `
32-
ENV_VAR_FROM_DOTENV_FILE=Content from ${envPath} file
33-
`,
34-
"app/routes/dotenv.tsx": String.raw`
5+
import * as Express from "./helpers/express";
6+
import { test } from "./helpers/fixtures";
7+
import * as Stream from "./helpers/stream";
8+
import { viteMajorTemplates, getTemplates } from "./helpers/templates";
9+
10+
const templates = [
11+
...viteMajorTemplates,
12+
...getTemplates(["rsc-vite-framework"]),
13+
];
14+
15+
test.use({
16+
files: {
17+
"app/routes/dotenv.tsx": tsx`
3518
import { useState, useEffect } from "react";
3619
import { useLoaderData } from "react-router";
3720
@@ -59,82 +42,62 @@ let getFiles = async ({
5942
</>
6043
}
6144
`,
62-
};
63-
};
64-
65-
test.describe("Vite .env", () => {
66-
for (const templateName of templateNames) {
67-
test.describe(`template: ${templateName}`, () => {
68-
test.describe("defaults", async () => {
69-
let port: number;
70-
let cwd: string;
71-
let stop: () => void;
72-
73-
test.beforeAll(async () => {
74-
port = await getPort();
75-
cwd = await createProject(
76-
await getFiles({ port, templateName }),
77-
templateName,
78-
);
79-
stop = await customDev({ cwd, port });
80-
});
81-
test.afterAll(() => stop());
45+
},
46+
});
8247

83-
test("express", async ({ page }) => {
84-
let pageErrors: unknown[] = [];
85-
page.on("pageerror", (error) => pageErrors.push(error));
48+
const envs = [
49+
{ name: "default", path: ".env" },
50+
{ name: "custom env dir", path: "custom-env-dir/.env" },
51+
];
8652

87-
await page.goto(`http://localhost:${port}/dotenv`, {
88-
waitUntil: "networkidle",
53+
test.describe("Vite .env", () => {
54+
templates.forEach((template) => {
55+
test.describe(`template: ${template.displayName}`, () => {
56+
const isRsc = template.name.startsWith("rsc-");
57+
test.use({ template: template.name });
58+
envs.forEach((env) => {
59+
test(env.name, async ({ edit, $, page }) => {
60+
await edit({
61+
"server.mjs": isRsc ? Express.rsc() : Express.server(),
62+
".env": `
63+
VITE_ENV_ROUTE=dotenv
64+
ENV_VAR_FROM_DOTENV_FILE=Content from ${env.path} file
65+
`,
66+
"app/routes.ts": (contents) => {
67+
if (template.name === "vite-5-template") return contents;
68+
return tsx`
69+
import { type RouteConfig, route } from "@react-router/dev/routes";
70+
71+
const routes: RouteConfig = [];
72+
if (import.meta.env.VITE_ENV_ROUTE === "dotenv") {
73+
routes.push(route("dotenv", "routes/dotenv.tsx"));
74+
}
75+
76+
export default routes
77+
`;
78+
},
8979
});
90-
expect(pageErrors).toEqual([]);
80+
await $("pnpm build");
9181

92-
let loaderContent = page.locator(
93-
"[data-dotenv-route-loader-content]",
94-
);
95-
await expect(loaderContent).toHaveText("Content from .env file");
82+
const port = await getPort();
83+
const url = `http://localhost:${port}`;
9684

97-
let clientContent = page.locator(
98-
"[data-dotenv-route-client-content]",
99-
);
100-
await expect(clientContent).toHaveText(
101-
"process.env.ENV_VAR_FROM_DOTENV_FILE not available on the client, which is a good thing",
102-
);
103-
104-
expect(pageErrors).toEqual([]);
105-
});
106-
});
107-
108-
test.describe("custom env dir", async () => {
109-
let port: number;
110-
let cwd: string;
111-
let stop: () => void;
112-
113-
test.beforeAll(async () => {
114-
const envDir = "custom-env-dir";
115-
port = await getPort();
116-
cwd = await createProject(
117-
await getFiles({ envDir, port, templateName }),
118-
templateName,
119-
);
120-
stop = await customDev({ cwd, port });
121-
});
122-
test.afterAll(() => stop());
123-
124-
test("express", async ({ page }) => {
125-
let pageErrors: unknown[] = [];
126-
page.on("pageerror", (error) => pageErrors.push(error));
127-
128-
await page.goto(`http://localhost:${port}/dotenv`, {
129-
waitUntil: "networkidle",
85+
const server = $("node server.mjs", {
86+
env: {
87+
PORT: String(port),
88+
HMR_PORT: String(await getPort()),
89+
},
13090
});
131-
expect(pageErrors).toEqual([]);
91+
await Stream.match(server.stdout, url);
92+
93+
await page.goto(`${url}/dotenv`, { waitUntil: "networkidle" });
94+
expect(page.errors).toEqual([]);
13295

13396
let loaderContent = page.locator(
13497
"[data-dotenv-route-loader-content]",
13598
);
13699
await expect(loaderContent).toHaveText(
137-
"Content from custom-env-dir/.env file",
100+
`Content from ${env.path} file`,
138101
);
139102

140103
let clientContent = page.locator(
@@ -144,9 +107,9 @@ test.describe("Vite .env", () => {
144107
"process.env.ENV_VAR_FROM_DOTENV_FILE not available on the client, which is a good thing",
145108
);
146109

147-
expect(pageErrors).toEqual([]);
110+
expect(page.errors).toEqual([]);
148111
});
149112
});
150113
});
151-
}
114+
});
152115
});

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
"typescript": "^5.4.5",
9393
"unified": "^10.1.2",
9494
"unist-util-remove": "^3.1.0",
95-
"vite": "^6.1.0"
95+
"vite": "^6.3.0"
9696
},
9797
"engines": {
9898
"node": ">=20.0.0"

0 commit comments

Comments
 (0)