diff --git a/packages/plugin-react/CHANGELOG.md b/packages/plugin-react/CHANGELOG.md
index 466bcca61..94e247cb8 100644
--- a/packages/plugin-react/CHANGELOG.md
+++ b/packages/plugin-react/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+### HMR did not work for components imported with queries with rolldown-vite ([#872](https://github.com/vitejs/vite-plugin-react/pull/872))
+
### Perf: simplify refresh wrapper generation ([#835](https://github.com/vitejs/vite-plugin-react/pull/835))
## 5.0.2 (2025-08-28)
diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts
index d57708a43..a7138e239 100644
--- a/packages/plugin-react/src/index.ts
+++ b/packages/plugin-react/src/index.ts
@@ -144,8 +144,8 @@ export default function viteReact(opts: Options = {}): Plugin[] {
// if development is enabled and those properties are already present
development: false,
},
- jsxRefreshInclude: include,
- jsxRefreshExclude: exclude,
+ jsxRefreshInclude: makeIdFiltersToMatchWithQuery(include),
+ jsxRefreshExclude: makeIdFiltersToMatchWithQuery(exclude),
},
}
} else {
@@ -156,8 +156,8 @@ export default function viteReact(opts: Options = {}): Plugin[] {
importSource: opts.jsxImportSource,
refresh: command === 'serve',
},
- jsxRefreshInclude: include,
- jsxRefreshExclude: exclude,
+ jsxRefreshInclude: makeIdFiltersToMatchWithQuery(include),
+ jsxRefreshExclude: makeIdFiltersToMatchWithQuery(exclude),
},
optimizeDeps: {
rollupOptions: { transform: { jsx: { runtime: 'automatic' } } },
diff --git a/playground/react-classic/App.jsx b/playground/react-classic/App.jsx
index 94326dc5f..bb59d3864 100644
--- a/playground/react-classic/App.jsx
+++ b/playground/react-classic/App.jsx
@@ -1,4 +1,5 @@
import React, { useState } from 'react'
+import WithQuery from './components/WithQuery?qs-should-not-break-plugin-react'
function App() {
const [count, setCount] = useState(0)
@@ -23,6 +24,8 @@ function App() {
Learn React
+
+
)
}
diff --git a/playground/react-classic/__tests__/react.spec.ts b/playground/react-classic/__tests__/react.spec.ts
index c2c55c4ba..a172997a5 100644
--- a/playground/react-classic/__tests__/react.spec.ts
+++ b/playground/react-classic/__tests__/react.spec.ts
@@ -18,6 +18,28 @@ test.runIf(isServe)('should hmr', async () => {
expect(await page.textContent('button')).toMatch('count is: 1')
})
+test.runIf(isServe)('should hmr files with queries', async () => {
+ expect(await page.textContent('#WithQuery')).toBe('With Query')
+
+ expect(await page.textContent('#WithQuery-button')).toMatch('count is: 0')
+ await page.click('#WithQuery-button')
+ expect(await page.textContent('#WithQuery-button')).toMatch('count is: 1')
+
+ editFile('components/WithQuery.jsx', (code) =>
+ code.replace('With Query', 'With Query Updated'),
+ )
+ await expect
+ .poll(() => page.textContent('#WithQuery'))
+ .toBe('With Query Updated')
+ // preserve state
+ expect(await page.textContent('#WithQuery-button')).toMatch('count is: 1')
+
+ editFile('components/WithQuery.jsx', (code) =>
+ code.replace('With Query Updated', 'With Query'),
+ )
+ await expect.poll(() => page.textContent('#WithQuery')).toBe('With Query')
+})
+
test.runIf(isServe)(
'should have annotated jsx with file location metadata',
async () => {
diff --git a/playground/react-classic/components/WithQuery.jsx b/playground/react-classic/components/WithQuery.jsx
new file mode 100644
index 000000000..7187dd074
--- /dev/null
+++ b/playground/react-classic/components/WithQuery.jsx
@@ -0,0 +1,16 @@
+import React, { useState } from 'react'
+
+export default function WithQuery() {
+ const [count, setCount] = useState(0)
+ return (
+ <>
+
With Query
+
+ >
+ )
+}
diff --git a/playground/react/App.jsx b/playground/react/App.jsx
index ac393c306..3b494d656 100644
--- a/playground/react/App.jsx
+++ b/playground/react/App.jsx
@@ -1,6 +1,6 @@
import { useState } from 'react'
import Button from 'jsx-entry'
-import Dummy from './components/Dummy?qs-should-not-break-plugin-react'
+import WithQuery from './components/WithQuery?qs-should-not-break-plugin-react'
import { Accordion } from './components/Accordion'
import Parent from './hmr/parent'
import { JsxImportRuntime } from './hmr/jsx-import-runtime'
@@ -39,7 +39,7 @@ function App() {
-
+
First Item
Second Item
diff --git a/playground/react/__tests__/react.spec.ts b/playground/react/__tests__/react.spec.ts
index 6495194cf..fa3e28f30 100644
--- a/playground/react/__tests__/react.spec.ts
+++ b/playground/react/__tests__/react.spec.ts
@@ -35,6 +35,28 @@ test.runIf(isServe)('should hmr', async () => {
await expect.poll(() => page.textContent('h1')).toMatch('Hello Vite + React')
})
+test.runIf(isServe)('should hmr files with queries', async () => {
+ expect(await page.textContent('#WithQuery')).toBe('With Query')
+
+ expect(await page.textContent('#WithQuery-button')).toMatch('count is: 0')
+ await page.click('#WithQuery-button')
+ expect(await page.textContent('#WithQuery-button')).toMatch('count is: 1')
+
+ editFile('components/WithQuery.jsx', (code) =>
+ code.replace('With Query', 'With Query Updated'),
+ )
+ await expect
+ .poll(() => page.textContent('#WithQuery'))
+ .toBe('With Query Updated')
+ // preserve state
+ expect(await page.textContent('#WithQuery-button')).toMatch('count is: 1')
+
+ editFile('components/WithQuery.jsx', (code) =>
+ code.replace('With Query Updated', 'With Query'),
+ )
+ await expect.poll(() => page.textContent('#WithQuery')).toBe('With Query')
+})
+
test.runIf(isServe)('should not invalidate when code is invalid', async () => {
editFile('App.jsx', (code) =>
code.replace('', '
'),
diff --git a/playground/react/components/Dummy.jsx b/playground/react/components/Dummy.jsx
deleted file mode 100644
index 27ec3c21d..000000000
--- a/playground/react/components/Dummy.jsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function Dummy() {
- return <>>
-}
diff --git a/playground/react/components/WithQuery.jsx b/playground/react/components/WithQuery.jsx
new file mode 100644
index 000000000..a11f0ddeb
--- /dev/null
+++ b/playground/react/components/WithQuery.jsx
@@ -0,0 +1,16 @@
+import { useState } from 'react'
+
+export default function WithQuery() {
+ const [count, setCount] = useState(0)
+ return (
+ <>
+
With Query
+
+ >
+ )
+}