From 04f780204aceef981e7da83a4dddb3e82cea6ada Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:41:29 +0900 Subject: [PATCH 1/2] fix(react): HMR did not work for components imported with queries --- packages/plugin-react/src/index.ts | 8 +++---- playground/react-classic/App.jsx | 3 +++ .../react-classic/__tests__/react.spec.ts | 22 +++++++++++++++++++ .../react-classic/components/WithQuery.jsx | 16 ++++++++++++++ playground/react/App.jsx | 4 ++-- playground/react/__tests__/react.spec.ts | 22 +++++++++++++++++++ playground/react/components/Dummy.jsx | 3 --- playground/react/components/WithQuery.jsx | 16 ++++++++++++++ 8 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 playground/react-classic/components/WithQuery.jsx delete mode 100644 playground/react/components/Dummy.jsx create mode 100644 playground/react/components/WithQuery.jsx 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
+ + + ) +} From 80b879de9e37b2b823039e5528638d84ae29a559 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:42:34 +0900 Subject: [PATCH 2/2] chore: add changelog --- packages/plugin-react/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) 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)