diff --git a/.changeset/clever-months-swim.md b/.changeset/clever-months-swim.md
new file mode 100644
index 00000000000..b2891178dde
--- /dev/null
+++ b/.changeset/clever-months-swim.md
@@ -0,0 +1,5 @@
+---
+"@remix-run/dev": patch
+---
+
+Support JSX usage in `.jsx` files without manual `React` import in Vite
diff --git a/contributors.yml b/contributors.yml
index 0eded58d49e..e563fb427d7 100644
--- a/contributors.yml
+++ b/contributors.yml
@@ -195,6 +195,7 @@
- harmony7
- helderburato
- HenryVogt
+- hi-ogawa
- hicksy
- himorishige
- Hirochon
diff --git a/integration/vite-dev-test.ts b/integration/vite-dev-test.ts
index 2a847db5d35..c948106e949 100644
--- a/integration/vite-dev-test.ts
+++ b/integration/vite-dev-test.ts
@@ -93,7 +93,7 @@ test.describe("Vite dev", () => {
export const loader: LoaderFunction = () => {
const headers = new Headers();
-
+
headers.append(
"Set-Cookie",
"first=one; Domain=localhost; Path=/; SameSite=Lax"
@@ -110,12 +110,12 @@ test.describe("Vite dev", () => {
);
headers.set("location", "http://localhost:${devPort}/get-cookies");
-
+
const response = new Response(null, {
headers,
status: 302,
});
-
+
return response;
};
`,
@@ -136,6 +136,15 @@ test.describe("Vite dev", () => {
);
}
`,
+ "app/routes/jsx.jsx": js`
+ export default function JsxRoute() {
+ return (
+
+ );
+ }
+ `,
},
});
@@ -228,6 +237,33 @@ test.describe("Vite dev", () => {
"first=one; second=two; third=three"
);
});
+
+ test("handles JSX in .jsx file without React import", async ({ page }) => {
+ let pageErrors: unknown[] = [];
+ page.on("pageerror", (error) => pageErrors.push(error));
+
+ await page.goto(`http://localhost:${devPort}/jsx`, {
+ waitUntil: "networkidle",
+ });
+ expect(pageErrors).toEqual([]);
+
+ let hmrStatus = page.locator("#jsx [data-hmr]");
+ await expect(hmrStatus).toHaveText("HMR updated: no");
+
+ let indexRouteContents = await fs.readFile(
+ path.join(projectDir, "app/routes/jsx.jsx"),
+ "utf8"
+ );
+ await fs.writeFile(
+ path.join(projectDir, "app/routes/jsx.jsx"),
+ indexRouteContents.replace("HMR updated: no", "HMR updated: yes"),
+ "utf8"
+ );
+ await page.waitForLoadState("networkidle");
+ await expect(hmrStatus).toHaveText("HMR updated: yes");
+
+ expect(pageErrors).toEqual([]);
+ });
});
let bufferize = (stream: Readable): (() => string) => {
diff --git a/packages/remix-dev/vite/plugin.ts b/packages/remix-dev/vite/plugin.ts
index 51224a2f4a0..e580c2660d9 100644
--- a/packages/remix-dev/vite/plugin.ts
+++ b/packages/remix-dev/vite/plugin.ts
@@ -432,6 +432,10 @@ export const remixVitePlugin: RemixVitePlugin = (options = {}) => {
"react-dom/client",
],
},
+ esbuild: {
+ jsx: "automatic",
+ jsxDev: viteCommand !== "build",
+ },
resolve: {
// https://react.dev/warnings/invalid-hook-call-warning#duplicate-react
dedupe: ["react", "react-dom"],