From 1c5a528e8e84176ad3f62ccfc11d71b3f5b9e4ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Barr=C3=A9?= Date: Sun, 18 May 2025 17:15:28 +0200 Subject: [PATCH 1/3] fix(hmr): skip HMR for JSX files with hooks --- packages/common/refresh-utils.ts | 2 +- packages/plugin-react-oxc/CHANGELOG.md | 6 +++++ packages/plugin-react-swc/CHANGELOG.md | 6 +++++ packages/plugin-react/CHANGELOG.md | 6 +++++ .../__tests__/hook-with-jsx.spec.ts | 27 +++++++++++++++++++ .../__tests__/oxc/class-components.spec.ts | 1 + playground/hook-with-jsx/index.html | 13 +++++++++ playground/hook-with-jsx/package.json | 19 +++++++++++++ playground/hook-with-jsx/public/vite.svg | 1 + playground/hook-with-jsx/src/App.tsx | 6 +++++ playground/hook-with-jsx/src/index.tsx | 9 +++++++ .../hook-with-jsx/src/useButtonHook.tsx | 10 +++++++ playground/hook-with-jsx/tsconfig.json | 23 ++++++++++++++++ playground/hook-with-jsx/vite.config.ts | 7 +++++ pnpm-lock.yaml | 19 +++++++++++++ 15 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 playground/hook-with-jsx/__tests__/hook-with-jsx.spec.ts create mode 100644 playground/hook-with-jsx/__tests__/oxc/class-components.spec.ts create mode 100644 playground/hook-with-jsx/index.html create mode 100644 playground/hook-with-jsx/package.json create mode 100644 playground/hook-with-jsx/public/vite.svg create mode 100644 playground/hook-with-jsx/src/App.tsx create mode 100644 playground/hook-with-jsx/src/index.tsx create mode 100644 playground/hook-with-jsx/src/useButtonHook.tsx create mode 100644 playground/hook-with-jsx/tsconfig.json create mode 100644 playground/hook-with-jsx/vite.config.ts diff --git a/packages/common/refresh-utils.ts b/packages/common/refresh-utils.ts index 559cbaa04..8c0cadc12 100644 --- a/packages/common/refresh-utils.ts +++ b/packages/common/refresh-utils.ts @@ -1,7 +1,7 @@ export const runtimePublicPath = '/@react-refresh' const reactCompRE = /extends\s+(?:React\.)?(?:Pure)?Component/ -const refreshContentRE = /\$Refresh(?:Reg|Sig)\$\(/ +const refreshContentRE = /\$RefreshReg\$\(/ // NOTE: this is exposed publicly via plugin-react export const preambleCode = `import { injectIntoGlobalHook } from "__BASE__${runtimePublicPath.slice( diff --git a/packages/plugin-react-oxc/CHANGELOG.md b/packages/plugin-react-oxc/CHANGELOG.md index 7648dec4d..64ea91c38 100644 --- a/packages/plugin-react-oxc/CHANGELOG.md +++ b/packages/plugin-react-oxc/CHANGELOG.md @@ -6,6 +6,12 @@ Added `filter` so that it is more performant when running this plugin with rolldown-powered version of Vite. +### Skip HMR for JSX files with hooks + +The current HMR implementation was trying to all HMR files that contains either hooks or components, but this was working only for components and lead to HMR invalidation for JSX files containing hooks. + +The best solution would have been to support HMR for hooks, but in my testing it was sometimes leading to stale updates. So this simple and reliable solution is to skip HMR for these files and have the components handle the updates, like any other hooks file. + ## 0.1.1 (2025-04-10) ## 0.1.0 (2025-04-09) diff --git a/packages/plugin-react-swc/CHANGELOG.md b/packages/plugin-react-swc/CHANGELOG.md index 79ed6f409..cc0700dcc 100644 --- a/packages/plugin-react-swc/CHANGELOG.md +++ b/packages/plugin-react-swc/CHANGELOG.md @@ -10,6 +10,12 @@ Added `filter` so that it is more performant when running this plugin with rolld This was causing annoying `Sourcemap for "/@react-refresh" points to missing source files` and is unnecessary in test mode. +### Skip HMR for JSX files with hooks + +The current HMR implementation was trying to all HMR files that contains either hooks or components, but this was working only for components and lead to HMR invalidation for JSX files containing hooks. + +The best solution would have been to support HMR for hooks, but in my testing it was sometimes leading to stale updates. So this simple and reliable solution is to skip HMR for these files and have the components handle the updates, like any other hooks file. + ## 3.9.0 (2025-04-15) ### Make compatible with rolldown-vite diff --git a/packages/plugin-react/CHANGELOG.md b/packages/plugin-react/CHANGELOG.md index ccb6b02c9..d84b79e50 100644 --- a/packages/plugin-react/CHANGELOG.md +++ b/packages/plugin-react/CHANGELOG.md @@ -6,6 +6,12 @@ Added `filter` so that it is more performant when running this plugin with rolldown-powered version of Vite. +### Skip HMR for JSX files with hooks + +The current HMR implementation was trying to all HMR files that contains either hooks or components, but this was working only for components and lead to HMR invalidation for JSX files containing hooks. + +The best solution would have been to support HMR for hooks, but in my testing it was sometimes leading to stale updates. So this simple and reliable solution is to skip HMR for these files and have the components handle the updates, like any other hooks file. + ## 4.4.1 (2025-04-19) Fix type issue when using `moduleResolution: "node"` in tsconfig [#462](https://github.com/vitejs/vite-plugin-react/pull/462) diff --git a/playground/hook-with-jsx/__tests__/hook-with-jsx.spec.ts b/playground/hook-with-jsx/__tests__/hook-with-jsx.spec.ts new file mode 100644 index 000000000..570ca8174 --- /dev/null +++ b/playground/hook-with-jsx/__tests__/hook-with-jsx.spec.ts @@ -0,0 +1,27 @@ +import { expect, test } from 'vitest' +import { + editFile, + isServe, + page, + untilBrowserLogAfter, + untilUpdated, +} from '~utils' + +test('should render', async () => { + expect(await page.textContent('button')).toMatch('count is 0') + expect(await page.click('button')) + expect(await page.textContent('button')).toMatch('count is 1') +}) + +if (isServe) { + test('Hook with JSX HMR', async () => { + editFile('src/useButtonHook.tsx', (code) => + code.replace('count is {count}', 'count is {count}!'), + ) + await untilBrowserLogAfter( + () => page.textContent('button'), + '[vite] hot updated: /src/App.tsx', + ) + await untilUpdated(() => page.textContent('button'), 'count is 1!') + }) +} diff --git a/playground/hook-with-jsx/__tests__/oxc/class-components.spec.ts b/playground/hook-with-jsx/__tests__/oxc/class-components.spec.ts new file mode 100644 index 000000000..4696fae50 --- /dev/null +++ b/playground/hook-with-jsx/__tests__/oxc/class-components.spec.ts @@ -0,0 +1 @@ +import '../hook-with-jsx.spec' diff --git a/playground/hook-with-jsx/index.html b/playground/hook-with-jsx/index.html new file mode 100644 index 000000000..96ad88a1c --- /dev/null +++ b/playground/hook-with-jsx/index.html @@ -0,0 +1,13 @@ + + + + + + + React hook with JSX + + +
+ + + diff --git a/playground/hook-with-jsx/package.json b/playground/hook-with-jsx/package.json new file mode 100644 index 000000000..6a7bbfd7a --- /dev/null +++ b/playground/hook-with-jsx/package.json @@ -0,0 +1,19 @@ +{ + "name": "@vitejs/test-hook-with-jsx", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "@vitejs/plugin-react": "workspace:*" + } +} diff --git a/playground/hook-with-jsx/public/vite.svg b/playground/hook-with-jsx/public/vite.svg new file mode 100644 index 000000000..4dcd77ad0 --- /dev/null +++ b/playground/hook-with-jsx/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/playground/hook-with-jsx/src/App.tsx b/playground/hook-with-jsx/src/App.tsx new file mode 100644 index 000000000..9e0ae242e --- /dev/null +++ b/playground/hook-with-jsx/src/App.tsx @@ -0,0 +1,6 @@ +import { useButtonHook } from './useButtonHook.tsx' + +export function App() { + const button = useButtonHook() + return
{button}
+} diff --git a/playground/hook-with-jsx/src/index.tsx b/playground/hook-with-jsx/src/index.tsx new file mode 100644 index 000000000..5e7046ce2 --- /dev/null +++ b/playground/hook-with-jsx/src/index.tsx @@ -0,0 +1,9 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import { App } from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/playground/hook-with-jsx/src/useButtonHook.tsx b/playground/hook-with-jsx/src/useButtonHook.tsx new file mode 100644 index 000000000..cae586690 --- /dev/null +++ b/playground/hook-with-jsx/src/useButtonHook.tsx @@ -0,0 +1,10 @@ +import { useState } from 'react' + +export function useButtonHook() { + const [count, setCount] = useState(0) + return ( + + ) +} diff --git a/playground/hook-with-jsx/tsconfig.json b/playground/hook-with-jsx/tsconfig.json new file mode 100644 index 000000000..c9e7d3d89 --- /dev/null +++ b/playground/hook-with-jsx/tsconfig.json @@ -0,0 +1,23 @@ +{ + "include": ["src"], + "compilerOptions": { + "module": "ESNext", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "target": "ESNext", + "jsx": "react-jsx", + "types": ["vite/client"], + "noEmit": true, + "isolatedModules": true, + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "useUnknownInCatchVariables": true + } +} diff --git a/playground/hook-with-jsx/vite.config.ts b/playground/hook-with-jsx/vite.config.ts new file mode 100644 index 000000000..6e5b8e40a --- /dev/null +++ b/playground/hook-with-jsx/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + server: { port: 8909 /* Should be unique */ }, + plugins: [react()], +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb0724961..d0bcf61ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -521,6 +521,25 @@ importers: specifier: ^5.8.3 version: 5.8.3 + playground/hook-with-jsx: + dependencies: + react: + specifier: ^19.1.0 + version: 19.1.0 + react-dom: + specifier: ^19.1.0 + version: 19.1.0(react@19.1.0) + devDependencies: + '@types/react': + specifier: ^19.1.2 + version: 19.1.2 + '@types/react-dom': + specifier: ^19.1.2 + version: 19.1.2(@types/react@19.1.2) + '@vitejs/plugin-react': + specifier: workspace:* + version: link:../../packages/plugin-react + playground/mdx: dependencies: react: From 01f1e684fd70e10f97dca2dd387c965c8dee9e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Barr=C3=A9?= Date: Wed, 21 May 2025 09:37:20 +0200 Subject: [PATCH 2/3] Simplify changelog --- packages/plugin-react-oxc/CHANGELOG.md | 8 +++----- packages/plugin-react-swc/CHANGELOG.md | 10 ++++------ packages/plugin-react/CHANGELOG.md | 8 +++----- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/plugin-react-oxc/CHANGELOG.md b/packages/plugin-react-oxc/CHANGELOG.md index 64ea91c38..2f93c2517 100644 --- a/packages/plugin-react-oxc/CHANGELOG.md +++ b/packages/plugin-react-oxc/CHANGELOG.md @@ -2,15 +2,13 @@ ## Unreleased -### Add `filter` for rolldown-vite +### Add `filter` for rolldown-vite [#470](https://github.com/vitejs/vite-plugin-react/pull/470) Added `filter` so that it is more performant when running this plugin with rolldown-powered version of Vite. -### Skip HMR for JSX files with hooks +### Skip HMR for JSX files with hooks [#480](https://github.com/vitejs/vite-plugin-react/pull/480) -The current HMR implementation was trying to all HMR files that contains either hooks or components, but this was working only for components and lead to HMR invalidation for JSX files containing hooks. - -The best solution would have been to support HMR for hooks, but in my testing it was sometimes leading to stale updates. So this simple and reliable solution is to skip HMR for these files and have the components handle the updates, like any other hooks file. +This removes the HMR warning for hooks with JSX. ## 0.1.1 (2025-04-10) diff --git a/packages/plugin-react-swc/CHANGELOG.md b/packages/plugin-react-swc/CHANGELOG.md index cc0700dcc..aed49abc7 100644 --- a/packages/plugin-react-swc/CHANGELOG.md +++ b/packages/plugin-react-swc/CHANGELOG.md @@ -2,19 +2,17 @@ ## Unreleased -### Add `filter` for rolldown-vite +### Add `filter` for rolldown-vite [#470](https://github.com/vitejs/vite-plugin-react/pull/470) Added `filter` so that it is more performant when running this plugin with rolldown-powered version of Vite. -### Skip HMR preamble in Vitest browser mode +### Skip HMR preamble in Vitest browser mode [#478](https://github.com/vitejs/vite-plugin-react/pull/478) This was causing annoying `Sourcemap for "/@react-refresh" points to missing source files` and is unnecessary in test mode. -### Skip HMR for JSX files with hooks +### Skip HMR for JSX files with hooks [#480](https://github.com/vitejs/vite-plugin-react/pull/480) -The current HMR implementation was trying to all HMR files that contains either hooks or components, but this was working only for components and lead to HMR invalidation for JSX files containing hooks. - -The best solution would have been to support HMR for hooks, but in my testing it was sometimes leading to stale updates. So this simple and reliable solution is to skip HMR for these files and have the components handle the updates, like any other hooks file. +This removes the HMR warning for hooks with JSX. ## 3.9.0 (2025-04-15) diff --git a/packages/plugin-react/CHANGELOG.md b/packages/plugin-react/CHANGELOG.md index d84b79e50..52105e6d6 100644 --- a/packages/plugin-react/CHANGELOG.md +++ b/packages/plugin-react/CHANGELOG.md @@ -2,15 +2,13 @@ ## Unreleased -### Add `filter` for rolldown-vite +### Add `filter` for rolldown-vite [#470](https://github.com/vitejs/vite-plugin-react/pull/470) Added `filter` so that it is more performant when running this plugin with rolldown-powered version of Vite. -### Skip HMR for JSX files with hooks +### Skip HMR for JSX files with hooks [#480](https://github.com/vitejs/vite-plugin-react/pull/480) -The current HMR implementation was trying to all HMR files that contains either hooks or components, but this was working only for components and lead to HMR invalidation for JSX files containing hooks. - -The best solution would have been to support HMR for hooks, but in my testing it was sometimes leading to stale updates. So this simple and reliable solution is to skip HMR for these files and have the components handle the updates, like any other hooks file. +This removes the HMR warning for hooks with JSX. ## 4.4.1 (2025-04-19) From 5906c6f0f3028f619d33e10ab8fe64f74dfa8efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Barr=C3=A9?= Date: Wed, 21 May 2025 11:13:14 +0200 Subject: [PATCH 3/3] Rename --- .../oxc/{class-components.spec.ts => hook-with-jsx.spec.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename playground/hook-with-jsx/__tests__/oxc/{class-components.spec.ts => hook-with-jsx.spec.ts} (100%) diff --git a/playground/hook-with-jsx/__tests__/oxc/class-components.spec.ts b/playground/hook-with-jsx/__tests__/oxc/hook-with-jsx.spec.ts similarity index 100% rename from playground/hook-with-jsx/__tests__/oxc/class-components.spec.ts rename to playground/hook-with-jsx/__tests__/oxc/hook-with-jsx.spec.ts