From 2ed4961b746370f43ea9f46c5fd025db32121af9 Mon Sep 17 00:00:00 2001 From: Eliya Cohen Date: Wed, 25 Jun 2025 17:00:06 +0300 Subject: [PATCH 01/13] feat: add hmr support for compound components --- packages/common/refresh-runtime.js | 35 ++++++++++++++++++ .../__tests__/compound-components.spec.ts | 23 ++++++++++++ playground/compound-components/index.html | 13 +++++++ playground/compound-components/package.json | 22 ++++++++++++ .../compound-components/public/vite.svg | 1 + .../compound-components/src/Accordion.tsx | 36 +++++++++++++++++++ playground/compound-components/src/main.tsx | 21 +++++++++++ playground/compound-components/tsconfig.json | 25 +++++++++++++ playground/compound-components/vite.config.ts | 14 ++++++++ pnpm-lock.yaml | 25 +++++++++++++ 10 files changed, 215 insertions(+) create mode 100644 playground/compound-components/__tests__/compound-components.spec.ts create mode 100644 playground/compound-components/index.html create mode 100644 playground/compound-components/package.json create mode 100644 playground/compound-components/public/vite.svg create mode 100644 playground/compound-components/src/Accordion.tsx create mode 100644 playground/compound-components/src/main.tsx create mode 100644 playground/compound-components/tsconfig.json create mode 100644 playground/compound-components/vite.config.ts diff --git a/packages/common/refresh-runtime.js b/packages/common/refresh-runtime.js index 08d1df630..087abe374 100644 --- a/packages/common/refresh-runtime.js +++ b/packages/common/refresh-runtime.js @@ -534,6 +534,16 @@ function isLikelyComponentType(type) { // Definitely React components. return true default: + // Check if this is a compound component (object with all properties being React components) + if (isPlainObject(type)) { + const keys = Object.keys(type) + if ( + keys.length > 0 && + keys.every((key) => isLikelyComponentType(type[key])) + ) { + return true + } + } return false } } @@ -545,6 +555,13 @@ function isLikelyComponentType(type) { } } +function isPlainObject(obj) { + return ( + Object.prototype.toString.call(obj) === '[object Object]' && + (obj.constructor === Object || obj.constructor === undefined) + ) +} + /** * Plugin utils */ @@ -565,6 +582,24 @@ export function registerExportsForReactRefresh(filename, moduleExports) { // The register function has an identity check to not register twice the same component, // so this is safe to not used the same key here. register(exportValue, filename + ' export ' + key) + + // If it's a compound component (plain object with component properties), + // also register the individual components + if ( + typeof exportValue === 'object' && + exportValue != null && + isPlainObject(exportValue) && + Object.keys(exportValue).every((subKey) => + isLikelyComponentType(exportValue[subKey]), + ) + ) { + for (const subKey in exportValue) { + register( + exportValue[subKey], + filename + ' export ' + key + '$' + subKey, + ) + } + } } } } diff --git a/playground/compound-components/__tests__/compound-components.spec.ts b/playground/compound-components/__tests__/compound-components.spec.ts new file mode 100644 index 000000000..5e572227b --- /dev/null +++ b/playground/compound-components/__tests__/compound-components.spec.ts @@ -0,0 +1,23 @@ +import { expect, test } from 'vitest' +import { editFile, page, untilBrowserLogAfter } from '~utils' + +test('should render compound components', async () => { + expect(await page.textContent('h1')).toMatch('Compound Components HMR Test') + expect(await page.textContent('h3')).toMatch('Accordion Root') +}) + +test('compound components should use HMR instead of full reload', async () => { + const logs = await untilBrowserLogAfter(() => { + editFile('src/Accordion.tsx', (code) => + code.replace('Accordion Root', 'Accordion Root Updated'), + ) + }, /\[vite\]/) + + expect(logs).toContain('[vite] hot updated: /src/Accordion.tsx') + expect(logs).not.toContain('[vite] invalidate') + + // revert changes + editFile('src/Accordion.tsx', (code) => + code.replace('Accordion Root Updated', 'Accordion Root'), + ) +}) diff --git a/playground/compound-components/index.html b/playground/compound-components/index.html new file mode 100644 index 000000000..9eb87c2e5 --- /dev/null +++ b/playground/compound-components/index.html @@ -0,0 +1,13 @@ + + + + + + + Compound Components Test + + +
+ + + diff --git a/playground/compound-components/package.json b/playground/compound-components/package.json new file mode 100644 index 000000000..1be8ef088 --- /dev/null +++ b/playground/compound-components/package.json @@ -0,0 +1,22 @@ +{ + "name": "@vitejs/test-compound-components", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "workspace:*", + "typescript": "^5.5.3", + "vite": "^6.0.5" + } +} diff --git a/playground/compound-components/public/vite.svg b/playground/compound-components/public/vite.svg new file mode 100644 index 000000000..ee9fadaf9 --- /dev/null +++ b/playground/compound-components/public/vite.svg @@ -0,0 +1 @@ + diff --git a/playground/compound-components/src/Accordion.tsx b/playground/compound-components/src/Accordion.tsx new file mode 100644 index 000000000..6c35467a7 --- /dev/null +++ b/playground/compound-components/src/Accordion.tsx @@ -0,0 +1,36 @@ +import React from 'react' + +const Root: React.FC<{ children: React.ReactNode }> = ({ children }) => { + return ( +
+

+ Accordion Root +

+
{children}
+
+ ) +} + +const Item: React.FC<{ children: React.ReactNode }> = ({ children }) => { + return ( +
+ {children} +
+ ) +} + +export const Accordion = { Root, Item } diff --git a/playground/compound-components/src/main.tsx b/playground/compound-components/src/main.tsx new file mode 100644 index 000000000..6a5bbec5f --- /dev/null +++ b/playground/compound-components/src/main.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import { Accordion } from './Accordion' + +const root = ReactDOM.createRoot(document.getElementById('root')!) + +root.render( + +
+

Compound Components HMR Test

+

+ This demonstrates the compound component pattern that causes full reload + instead of HMR. +

+ + First Item + Second Item + +
+
, +) diff --git a/playground/compound-components/tsconfig.json b/playground/compound-components/tsconfig.json new file mode 100644 index 000000000..b8574de7b --- /dev/null +++ b/playground/compound-components/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "types": ["vite/client"], + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/playground/compound-components/vite.config.ts b/playground/compound-components/vite.config.ts new file mode 100644 index 000000000..dfdfc6398 --- /dev/null +++ b/playground/compound-components/vite.config.ts @@ -0,0 +1,14 @@ +import react from '@vitejs/plugin-react' +import type { UserConfig } from 'vite' + +const config: UserConfig = { + server: { port: 8907 /* Should be unique */ }, + mode: 'development', + plugins: [react()], + build: { + // to make tests faster + minify: false, + }, +} + +export default config diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b20e62d3a..7b4f18657 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -536,6 +536,31 @@ importers: specifier: ^5.8.3 version: 5.8.3 + playground/compound-components: + dependencies: + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + '@types/react': + specifier: ^18.3.5 + version: 18.3.20 + '@types/react-dom': + specifier: ^18.3.1 + version: 18.3.6(@types/react@18.3.20) + '@vitejs/plugin-react': + specifier: workspace:* + version: link:../../packages/plugin-react + typescript: + specifier: ^5.5.3 + version: 5.8.3 + vite: + specifier: ^6.0.5 + version: 6.3.3(@types/node@22.15.32)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) + playground/hmr-false: dependencies: react: From 8093da09affb275b7248e4ed59aa7a62b2a5cd2c Mon Sep 17 00:00:00 2001 From: Eliya Cohen Date: Tue, 1 Jul 2025 10:57:10 +0300 Subject: [PATCH 02/13] remove unused code --- packages/common/refresh-runtime.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/common/refresh-runtime.js b/packages/common/refresh-runtime.js index 087abe374..2ebaac6b8 100644 --- a/packages/common/refresh-runtime.js +++ b/packages/common/refresh-runtime.js @@ -534,16 +534,6 @@ function isLikelyComponentType(type) { // Definitely React components. return true default: - // Check if this is a compound component (object with all properties being React components) - if (isPlainObject(type)) { - const keys = Object.keys(type) - if ( - keys.length > 0 && - keys.every((key) => isLikelyComponentType(type[key])) - ) { - return true - } - } return false } } From 1479a1d23535e1d4306cc9363b3a3d3426a2ea1b Mon Sep 17 00:00:00 2001 From: Eliya Cohen Date: Tue, 1 Jul 2025 10:58:14 +0300 Subject: [PATCH 03/13] cleanup --- packages/common/refresh-runtime.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/common/refresh-runtime.js b/packages/common/refresh-runtime.js index 2ebaac6b8..5a2a4bc2d 100644 --- a/packages/common/refresh-runtime.js +++ b/packages/common/refresh-runtime.js @@ -546,6 +546,10 @@ function isLikelyComponentType(type) { } function isPlainObject(obj) { + if (typeof obj !== 'object' || obj === null) { + return false + } + return ( Object.prototype.toString.call(obj) === '[object Object]' && (obj.constructor === Object || obj.constructor === undefined) @@ -576,8 +580,6 @@ export function registerExportsForReactRefresh(filename, moduleExports) { // If it's a compound component (plain object with component properties), // also register the individual components if ( - typeof exportValue === 'object' && - exportValue != null && isPlainObject(exportValue) && Object.keys(exportValue).every((subKey) => isLikelyComponentType(exportValue[subKey]), From 2600829eb558fd5dfcb52ddd9d83d11bf9c54e90 Mon Sep 17 00:00:00 2001 From: Eliya Cohen Date: Tue, 1 Jul 2025 10:59:35 +0300 Subject: [PATCH 04/13] move outside of if --- packages/common/refresh-runtime.js | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/packages/common/refresh-runtime.js b/packages/common/refresh-runtime.js index 5a2a4bc2d..8e65ef20e 100644 --- a/packages/common/refresh-runtime.js +++ b/packages/common/refresh-runtime.js @@ -576,21 +576,20 @@ export function registerExportsForReactRefresh(filename, moduleExports) { // The register function has an identity check to not register twice the same component, // so this is safe to not used the same key here. register(exportValue, filename + ' export ' + key) - - // If it's a compound component (plain object with component properties), - // also register the individual components - if ( - isPlainObject(exportValue) && - Object.keys(exportValue).every((subKey) => - isLikelyComponentType(exportValue[subKey]), + } + // If it's a compound component (plain object with component properties), + // also register the individual components + else if ( + isPlainObject(exportValue) && + Object.keys(exportValue).every((subKey) => + isLikelyComponentType(exportValue[subKey]), + ) + ) { + for (const subKey in exportValue) { + register( + exportValue[subKey], + filename + ' export ' + key + '$' + subKey, ) - ) { - for (const subKey in exportValue) { - register( - exportValue[subKey], - filename + ' export ' + key + '$' + subKey, - ) - } } } } From ed7d8707f92fb72522c5323a9a588c9a74b4f16f Mon Sep 17 00:00:00 2001 From: Eliya Cohen Date: Tue, 1 Jul 2025 11:01:51 +0300 Subject: [PATCH 05/13] cleanup --- packages/common/refresh-runtime.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/common/refresh-runtime.js b/packages/common/refresh-runtime.js index 8e65ef20e..2bbc303c8 100644 --- a/packages/common/refresh-runtime.js +++ b/packages/common/refresh-runtime.js @@ -556,6 +556,10 @@ function isPlainObject(obj) { ) } +function isLikelyCompoundComponent(type) { + return isPlainObject(type) && Object.keys(type).every(isLikelyComponentType) +} + /** * Plugin utils */ @@ -576,15 +580,7 @@ export function registerExportsForReactRefresh(filename, moduleExports) { // The register function has an identity check to not register twice the same component, // so this is safe to not used the same key here. register(exportValue, filename + ' export ' + key) - } - // If it's a compound component (plain object with component properties), - // also register the individual components - else if ( - isPlainObject(exportValue) && - Object.keys(exportValue).every((subKey) => - isLikelyComponentType(exportValue[subKey]), - ) - ) { + } else if (isLikelyCompoundComponent(exportValue)) { for (const subKey in exportValue) { register( exportValue[subKey], From ea0279d47881f0dec441a60195192bb5005d8a2d Mon Sep 17 00:00:00 2001 From: Eliya Cohen Date: Tue, 1 Jul 2025 11:22:17 +0300 Subject: [PATCH 06/13] revert --- packages/common/refresh-runtime.js | 39 ++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/packages/common/refresh-runtime.js b/packages/common/refresh-runtime.js index 2bbc303c8..087abe374 100644 --- a/packages/common/refresh-runtime.js +++ b/packages/common/refresh-runtime.js @@ -534,6 +534,16 @@ function isLikelyComponentType(type) { // Definitely React components. return true default: + // Check if this is a compound component (object with all properties being React components) + if (isPlainObject(type)) { + const keys = Object.keys(type) + if ( + keys.length > 0 && + keys.every((key) => isLikelyComponentType(type[key])) + ) { + return true + } + } return false } } @@ -546,20 +556,12 @@ function isLikelyComponentType(type) { } function isPlainObject(obj) { - if (typeof obj !== 'object' || obj === null) { - return false - } - return ( Object.prototype.toString.call(obj) === '[object Object]' && (obj.constructor === Object || obj.constructor === undefined) ) } -function isLikelyCompoundComponent(type) { - return isPlainObject(type) && Object.keys(type).every(isLikelyComponentType) -} - /** * Plugin utils */ @@ -580,12 +582,23 @@ export function registerExportsForReactRefresh(filename, moduleExports) { // The register function has an identity check to not register twice the same component, // so this is safe to not used the same key here. register(exportValue, filename + ' export ' + key) - } else if (isLikelyCompoundComponent(exportValue)) { - for (const subKey in exportValue) { - register( - exportValue[subKey], - filename + ' export ' + key + '$' + subKey, + + // If it's a compound component (plain object with component properties), + // also register the individual components + if ( + typeof exportValue === 'object' && + exportValue != null && + isPlainObject(exportValue) && + Object.keys(exportValue).every((subKey) => + isLikelyComponentType(exportValue[subKey]), ) + ) { + for (const subKey in exportValue) { + register( + exportValue[subKey], + filename + ' export ' + key + '$' + subKey, + ) + } } } } From 5403fd4985dc71827aadcb27417af50ed9ebcbbb Mon Sep 17 00:00:00 2001 From: Eliya Cohen Date: Mon, 14 Jul 2025 15:59:08 +0300 Subject: [PATCH 07/13] update lockfile --- pnpm-lock.yaml | 58 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d60866e14..974216187 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -823,7 +823,7 @@ importers: version: 5.8.3 vite: specifier: ^6.0.5 - version: 6.3.3(@types/node@22.15.32)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) + version: 6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) playground/hmr-false: dependencies: @@ -5565,6 +5565,46 @@ packages: '@nuxt/kit': optional: true + vite@6.3.5: + resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vite@7.0.1: resolution: {integrity: sha512-BiKOQoW5HGR30E6JDeNsati6HnSPMVEKbkIWbCiol+xKeu3g5owrjy7kbk/QEMuzCV87dSUTvycYKmlcfGKq3Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -10450,6 +10490,22 @@ snapshots: transitivePeerDependencies: - supports-color + vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1): + dependencies: + esbuild: 0.25.5 + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + postcss: 8.5.6 + rollup: 4.44.1 + tinyglobby: 0.2.14 + optionalDependencies: + '@types/node': 22.16.0 + fsevents: 2.3.3 + jiti: 2.4.2 + lightningcss: 1.30.1 + tsx: 4.20.3 + yaml: 2.7.1 + vite@7.0.1(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1): dependencies: esbuild: 0.25.5 From f526d3d4160b33f9c767e247a5e2c8d0f5652c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Barr=C3=A9?= Date: Wed, 16 Jul 2025 01:17:18 +0200 Subject: [PATCH 08/13] Move isCompoundComponent to its own function --- packages/common/refresh-runtime.js | 41 +++---- .../__tests__/compound-components.spec.ts | 30 ++--- playground/compound-components/package.json | 12 +- playground/compound-components/tsconfig.json | 2 +- pnpm-lock.yaml | 110 ++++++++++++------ 5 files changed, 116 insertions(+), 79 deletions(-) diff --git a/packages/common/refresh-runtime.js b/packages/common/refresh-runtime.js index 087abe374..7749c889f 100644 --- a/packages/common/refresh-runtime.js +++ b/packages/common/refresh-runtime.js @@ -534,16 +534,6 @@ function isLikelyComponentType(type) { // Definitely React components. return true default: - // Check if this is a compound component (object with all properties being React components) - if (isPlainObject(type)) { - const keys = Object.keys(type) - if ( - keys.length > 0 && - keys.every((key) => isLikelyComponentType(type[key])) - ) { - return true - } - } return false } } @@ -555,6 +545,15 @@ function isLikelyComponentType(type) { } } +function isCompoundComponent(type) { + if (!isPlainObject(type)) return false + const keys = Object.keys(type) + for (const key of keys) { + if (!isLikelyComponentType(type[key])) return false + } + return true +} + function isPlainObject(obj) { return ( Object.prototype.toString.call(obj) === '[object Object]' && @@ -582,23 +581,12 @@ export function registerExportsForReactRefresh(filename, moduleExports) { // The register function has an identity check to not register twice the same component, // so this is safe to not used the same key here. register(exportValue, filename + ' export ' + key) - - // If it's a compound component (plain object with component properties), - // also register the individual components - if ( - typeof exportValue === 'object' && - exportValue != null && - isPlainObject(exportValue) && - Object.keys(exportValue).every((subKey) => - isLikelyComponentType(exportValue[subKey]), + } else if (isCompoundComponent(exportValue)) { + for (const subKey in exportValue) { + register( + exportValue[subKey], + filename + ' export ' + key + '$' + subKey, ) - ) { - for (const subKey in exportValue) { - register( - exportValue[subKey], - filename + ' export ' + key + '$' + subKey, - ) - } } } } @@ -653,6 +641,7 @@ export function validateRefreshBoundaryAndEnqueueUpdate( (key, value) => { hasExports = true if (isLikelyComponentType(value)) return true + if (isCompoundComponent(value)) return true return prevExports[key] === nextExports[key] }, ) diff --git a/playground/compound-components/__tests__/compound-components.spec.ts b/playground/compound-components/__tests__/compound-components.spec.ts index 5e572227b..dbd779441 100644 --- a/playground/compound-components/__tests__/compound-components.spec.ts +++ b/playground/compound-components/__tests__/compound-components.spec.ts @@ -1,23 +1,27 @@ import { expect, test } from 'vitest' -import { editFile, page, untilBrowserLogAfter } from '~utils' +import { editFile, isServe, page, untilBrowserLogAfter } from '~utils' test('should render compound components', async () => { expect(await page.textContent('h1')).toMatch('Compound Components HMR Test') expect(await page.textContent('h3')).toMatch('Accordion Root') }) -test('compound components should use HMR instead of full reload', async () => { - const logs = await untilBrowserLogAfter(() => { - editFile('src/Accordion.tsx', (code) => - code.replace('Accordion Root', 'Accordion Root Updated'), +if (isServe) { + test('compound components should use HMR instead of full reload', async () => { + const logs = await untilBrowserLogAfter( + () => + editFile('src/Accordion.tsx', (code) => + code.replace('Accordion Root', 'Accordion Root Updated'), + ), + /\[vite\]/, ) - }, /\[vite\]/) - expect(logs).toContain('[vite] hot updated: /src/Accordion.tsx') - expect(logs).not.toContain('[vite] invalidate') + expect(logs).toContain('[vite] hot updated: /src/Accordion.tsx') + expect(logs).not.toContain('[vite] invalidate') - // revert changes - editFile('src/Accordion.tsx', (code) => - code.replace('Accordion Root Updated', 'Accordion Root'), - ) -}) + // revert changes + editFile('src/Accordion.tsx', (code) => + code.replace('Accordion Root Updated', 'Accordion Root'), + ) + }) +} diff --git a/playground/compound-components/package.json b/playground/compound-components/package.json index 1be8ef088..d4e8cb92e 100644 --- a/playground/compound-components/package.json +++ b/playground/compound-components/package.json @@ -9,14 +9,14 @@ "serve": "vite preview" }, "dependencies": { - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.1.0", + "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^18.3.5", - "@types/react-dom": "^18.3.1", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "workspace:*", - "typescript": "^5.5.3", - "vite": "^6.0.5" + "typescript": "^5.8.3", + "vite": "^7.0.4" } } diff --git a/playground/compound-components/tsconfig.json b/playground/compound-components/tsconfig.json index b8574de7b..f3f26c23e 100644 --- a/playground/compound-components/tsconfig.json +++ b/playground/compound-components/tsconfig.json @@ -21,5 +21,5 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src"] + "include": ["src", "vite.config.ts"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 974216187..f1a85afd4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -461,7 +461,7 @@ importers: version: 3.1.0 vitefu: specifier: ^1.1.1 - version: 1.1.1(vite@7.0.2(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)) + version: 1.1.1(vite@7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)) devDependencies: '@hiogawa/utils': specifier: ^1.7.0 @@ -658,7 +658,7 @@ importers: version: link:../../../plugin-react vite-plugin-inspect: specifier: ^11.3.0 - version: 11.3.0(vite@7.0.2(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)) + version: 11.3.0(vite@7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)) packages/plugin-rsc/examples/starter: dependencies: @@ -702,7 +702,7 @@ importers: devDependencies: '@cloudflare/vite-plugin': specifier: ^1.9.0 - version: 1.9.0(rollup@4.44.1)(vite@7.0.2(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1))(workerd@1.20250617.0)(wrangler@4.23.0) + version: 1.9.0(rollup@4.44.1)(vite@7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1))(workerd@1.20250617.0)(wrangler@4.23.0) '@types/react': specifier: ^19.1.8 version: 19.1.8 @@ -803,27 +803,27 @@ importers: playground/compound-components: dependencies: react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.1.0 + version: 19.1.0 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.1.0 + version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^18.3.5 - version: 18.3.20 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': - specifier: ^18.3.1 - version: 18.3.6(@types/react@18.3.20) + specifier: ^19.1.6 + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: workspace:* version: link:../../packages/plugin-react typescript: - specifier: ^5.5.3 + specifier: ^5.8.3 version: 5.8.3 vite: - specifier: ^6.0.5 - version: 6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) + specifier: ^7.0.4 + version: 7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) playground/hmr-false: dependencies: @@ -5565,19 +5565,19 @@ packages: '@nuxt/kit': optional: true - vite@6.3.5: - resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vite@7.0.1: + resolution: {integrity: sha512-BiKOQoW5HGR30E6JDeNsati6HnSPMVEKbkIWbCiol+xKeu3g5owrjy7kbk/QEMuzCV87dSUTvycYKmlcfGKq3Q==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@types/node': ^20.19.0 || >=22.12.0 jiti: '>=1.21.0' - less: '*' + less: ^4.0.0 lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 terser: ^5.16.0 tsx: ^4.8.1 yaml: ^2.4.2 @@ -5605,8 +5605,8 @@ packages: yaml: optional: true - vite@7.0.1: - resolution: {integrity: sha512-BiKOQoW5HGR30E6JDeNsati6HnSPMVEKbkIWbCiol+xKeu3g5owrjy7kbk/QEMuzCV87dSUTvycYKmlcfGKq3Q==} + vite@7.0.2: + resolution: {integrity: sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -5645,8 +5645,8 @@ packages: yaml: optional: true - vite@7.0.2: - resolution: {integrity: sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw==} + vite@7.0.4: + resolution: {integrity: sha512-SkaSguuS7nnmV7mfJ8l81JGBFV7Gvzp8IzgE8A8t23+AxuNX61Q5H1Tpz5efduSN7NHC8nQXD3sKQKZAu5mNEA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -6151,6 +6151,25 @@ snapshots: - utf-8-validate - workerd + '@cloudflare/vite-plugin@1.9.0(rollup@4.44.1)(vite@7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1))(workerd@1.20250617.0)(wrangler@4.23.0)': + dependencies: + '@cloudflare/unenv-preset': 2.3.3(unenv@2.0.0-rc.17)(workerd@1.20250617.0) + '@mjackson/node-fetch-server': 0.6.1 + '@rollup/plugin-replace': 6.0.2(rollup@4.44.1) + get-port: 7.1.0 + miniflare: 4.20250617.5 + picocolors: 1.1.1 + tinyglobby: 0.2.14 + unenv: 2.0.0-rc.17 + vite: 7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) + wrangler: 4.23.0 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - rollup + - utf-8-validate + - workerd + '@cloudflare/workerd-darwin-64@1.20250617.0': optional: true @@ -10450,17 +10469,27 @@ snapshots: vite: 7.0.2(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) vite-hot-client: 2.1.0(vite@7.0.2(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)) + vite-dev-rpc@1.1.0(vite@7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)): + dependencies: + birpc: 2.4.0 + vite: 7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) + vite-hot-client: 2.1.0(vite@7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)) + vite-hot-client@2.1.0(vite@7.0.2(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)): dependencies: vite: 7.0.2(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) + vite-hot-client@2.1.0(vite@7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)): + dependencies: + vite: 7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) + vite-node@3.2.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1): dependencies: cac: 6.7.14 debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.0.2(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) + vite: 7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) transitivePeerDependencies: - '@types/node' - jiti @@ -10490,7 +10519,22 @@ snapshots: transitivePeerDependencies: - supports-color - vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1): + vite-plugin-inspect@11.3.0(vite@7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)): + dependencies: + ansis: 4.1.0 + debug: 4.4.1 + error-stack-parser-es: 1.0.5 + ohash: 2.0.11 + open: 10.1.2 + perfect-debounce: 1.0.0 + sirv: 3.0.1 + unplugin-utils: 0.2.4 + vite: 7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) + vite-dev-rpc: 1.1.0(vite@7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)) + transitivePeerDependencies: + - supports-color + + vite@7.0.1(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1): dependencies: esbuild: 0.25.5 fdir: 6.4.6(picomatch@4.0.2) @@ -10506,7 +10550,7 @@ snapshots: tsx: 4.20.3 yaml: 2.7.1 - vite@7.0.1(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1): + vite@7.0.2(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1): dependencies: esbuild: 0.25.5 fdir: 6.4.6(picomatch@4.0.2) @@ -10522,7 +10566,7 @@ snapshots: tsx: 4.20.3 yaml: 2.7.1 - vite@7.0.2(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1): + vite@7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1): dependencies: esbuild: 0.25.5 fdir: 6.4.6(picomatch@4.0.2) @@ -10538,9 +10582,9 @@ snapshots: tsx: 4.20.3 yaml: 2.7.1 - vitefu@1.1.1(vite@7.0.2(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)): + vitefu@1.1.1(vite@7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)): optionalDependencies: - vite: 7.0.2(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) + vite: 7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1): dependencies: From cb02fe99b17fb39c3947a3bd7c230af47dfb1521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Barr=C3=A9?= Date: Wed, 16 Jul 2025 01:22:36 +0200 Subject: [PATCH 09/13] fix for-of --- packages/common/refresh-runtime.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/common/refresh-runtime.js b/packages/common/refresh-runtime.js index 7749c889f..3cac297ad 100644 --- a/packages/common/refresh-runtime.js +++ b/packages/common/refresh-runtime.js @@ -547,8 +547,7 @@ function isLikelyComponentType(type) { function isCompoundComponent(type) { if (!isPlainObject(type)) return false - const keys = Object.keys(type) - for (const key of keys) { + for (const key in type) { if (!isLikelyComponentType(type[key])) return false } return true From d18a7f5e4e95ab175e80e0025eedad84c320f189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Barr=C3=A9?= Date: Wed, 16 Jul 2025 01:24:51 +0200 Subject: [PATCH 10/13] Fix lock --- pnpm-lock.yaml | 102 +------------------------------------------------ 1 file changed, 1 insertion(+), 101 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3b1fcce5f..525c6909f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -823,7 +823,7 @@ importers: version: 5.8.3 vite: specifier: ^7.0.4 - version: 7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) + version: 7.0.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) playground/hmr-false: dependencies: @@ -5666,46 +5666,6 @@ packages: yaml: optional: true - vite@7.0.4: - resolution: {integrity: sha512-SkaSguuS7nnmV7mfJ8l81JGBFV7Gvzp8IzgE8A8t23+AxuNX61Q5H1Tpz5efduSN7NHC8nQXD3sKQKZAu5mNEA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - vitefu@1.1.1: resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} peerDependencies: @@ -6175,25 +6135,6 @@ snapshots: - utf-8-validate - workerd - '@cloudflare/vite-plugin@1.9.0(rollup@4.44.1)(vite@7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1))(workerd@1.20250617.0)(wrangler@4.23.0)': - dependencies: - '@cloudflare/unenv-preset': 2.3.3(unenv@2.0.0-rc.17)(workerd@1.20250617.0) - '@mjackson/node-fetch-server': 0.6.1 - '@rollup/plugin-replace': 6.0.2(rollup@4.44.1) - get-port: 7.1.0 - miniflare: 4.20250617.5 - picocolors: 1.1.1 - tinyglobby: 0.2.14 - unenv: 2.0.0-rc.17 - vite: 7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) - wrangler: 4.23.0 - ws: 8.18.0 - transitivePeerDependencies: - - bufferutil - - rollup - - utf-8-validate - - workerd - '@cloudflare/workerd-darwin-64@1.20250709.0': optional: true @@ -10516,20 +10457,10 @@ snapshots: vite: 7.0.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) vite-hot-client: 2.1.0(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)) - vite-dev-rpc@1.1.0(vite@7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)): - dependencies: - birpc: 2.4.0 - vite: 7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) - vite-hot-client: 2.1.0(vite@7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)) - vite-hot-client@2.1.0(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)): dependencies: vite: 7.0.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) - vite-hot-client@2.1.0(vite@7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)): - dependencies: - vite: 7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) - vite-node@3.2.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1): dependencies: cac: 6.7.14 @@ -10566,21 +10497,6 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plugin-inspect@11.3.0(vite@7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)): - dependencies: - ansis: 4.1.0 - debug: 4.4.1 - error-stack-parser-es: 1.0.5 - ohash: 2.0.11 - open: 10.1.2 - perfect-debounce: 1.0.0 - sirv: 3.0.1 - unplugin-utils: 0.2.4 - vite: 7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) - vite-dev-rpc: 1.1.0(vite@7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)) - transitivePeerDependencies: - - supports-color - vite@7.0.1(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1): dependencies: esbuild: 0.25.5 @@ -10613,22 +10529,6 @@ snapshots: tsx: 4.20.3 yaml: 2.7.1 - vite@7.0.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1): - dependencies: - esbuild: 0.25.5 - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 - postcss: 8.5.6 - rollup: 4.44.1 - tinyglobby: 0.2.14 - optionalDependencies: - '@types/node': 22.16.0 - fsevents: 2.3.3 - jiti: 2.4.2 - lightningcss: 1.30.1 - tsx: 4.20.3 - yaml: 2.7.1 - vitefu@1.1.1(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)): optionalDependencies: vite: 7.0.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) From fe106946f5ac3828de85034e4b934753ae7e4aee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Barr=C3=A9?= Date: Wed, 16 Jul 2025 01:32:43 +0200 Subject: [PATCH 11/13] Remove vite dep from playground --- playground/compound-components/package.json | 3 +-- pnpm-lock.yaml | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/playground/compound-components/package.json b/playground/compound-components/package.json index d4e8cb92e..60bfa3426 100644 --- a/playground/compound-components/package.json +++ b/playground/compound-components/package.json @@ -16,7 +16,6 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "workspace:*", - "typescript": "^5.8.3", - "vite": "^7.0.4" + "typescript": "^5.8.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 525c6909f..cae64718c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -821,9 +821,6 @@ importers: typescript: specifier: ^5.8.3 version: 5.8.3 - vite: - specifier: ^7.0.4 - version: 7.0.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1) playground/hmr-false: dependencies: From db32d067b7a224b17f020299db76f7264bb77ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Barr=C3=A9?= Date: Wed, 16 Jul 2025 01:36:42 +0200 Subject: [PATCH 12/13] Add changelog --- packages/plugin-react-oxc/CHANGELOG.md | 11 +++++++++++ packages/plugin-react-swc/CHANGELOG.md | 11 +++++++++++ packages/plugin-react/CHANGELOG.md | 11 +++++++++++ 3 files changed, 33 insertions(+) diff --git a/packages/plugin-react-oxc/CHANGELOG.md b/packages/plugin-react-oxc/CHANGELOG.md index e82a2fa30..64c1a4285 100644 --- a/packages/plugin-react-oxc/CHANGELOG.md +++ b/packages/plugin-react-oxc/CHANGELOG.md @@ -2,6 +2,17 @@ ## Unreleased +### Add support for compound components ([#518](https://github.com/vitejs/vite-plugin-react/pull/518)) + +HMR now works for compound components like this: + +```tsx +const Root = () =>
Accordion Root
+const Item = () =>
Accordion Item
+ +export const Accordion = { Root, Item } +``` + ### Return `Plugin[]` instead of `PluginOption[]` ## 0.2.3 (2025-06-16) diff --git a/packages/plugin-react-swc/CHANGELOG.md b/packages/plugin-react-swc/CHANGELOG.md index 5a2b0dd8e..52f676ca5 100644 --- a/packages/plugin-react-swc/CHANGELOG.md +++ b/packages/plugin-react-swc/CHANGELOG.md @@ -2,6 +2,17 @@ ## Unreleased +### Add support for compound components ([#518](https://github.com/vitejs/vite-plugin-react/pull/518)) + +HMR now works for compound components like this: + +```tsx +const Root = () =>
Accordion Root
+const Item = () =>
Accordion Item
+ +export const Accordion = { Root, Item } +``` + ### Return `Plugin[]` instead of `PluginOption[]` ## 3.10.2 (2025-06-10) diff --git a/packages/plugin-react/CHANGELOG.md b/packages/plugin-react/CHANGELOG.md index 960c754ed..f3eb4bde6 100644 --- a/packages/plugin-react/CHANGELOG.md +++ b/packages/plugin-react/CHANGELOG.md @@ -2,6 +2,17 @@ ## Unreleased +### Add support for compound components ([#518](https://github.com/vitejs/vite-plugin-react/pull/518)) + +HMR now works for compound components like this: + +```tsx +const Root = () =>
Accordion Root
+const Item = () =>
Accordion Item
+ +export const Accordion = { Root, Item } +``` + ### Return `Plugin[]` instead of `PluginOption[]` The return type has changed from `react(): PluginOption[]` to more specialized type `react(): Plugin[]`. This allows for type-safe manipulation of plugins, for example: From 711fa8c75e6cf7c1461082772a739080f23fe63b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Barr=C3=A9?= Date: Wed, 16 Jul 2025 10:51:28 +0200 Subject: [PATCH 13/13] Review --- packages/common/refresh-runtime.js | 2 +- packages/plugin-react-oxc/CHANGELOG.md | 2 +- packages/plugin-react-swc/CHANGELOG.md | 2 +- packages/plugin-react/CHANGELOG.md | 2 +- .../__tests__/compound-components.spec.ts | 27 ------------------- playground/compound-components/index.html | 13 --------- playground/compound-components/package.json | 21 --------------- .../compound-components/public/vite.svg | 1 - playground/compound-components/src/main.tsx | 21 --------------- playground/compound-components/tsconfig.json | 25 ----------------- playground/compound-components/vite.config.ts | 14 ---------- playground/react/App.jsx | 5 ++++ playground/react/__tests__/react.spec.ts | 15 +++++++++++ .../components/Accordion.jsx} | 11 ++++---- pnpm-lock.yaml | 22 --------------- 15 files changed, 30 insertions(+), 153 deletions(-) delete mode 100644 playground/compound-components/__tests__/compound-components.spec.ts delete mode 100644 playground/compound-components/index.html delete mode 100644 playground/compound-components/package.json delete mode 100644 playground/compound-components/public/vite.svg delete mode 100644 playground/compound-components/src/main.tsx delete mode 100644 playground/compound-components/tsconfig.json delete mode 100644 playground/compound-components/vite.config.ts rename playground/{compound-components/src/Accordion.tsx => react/components/Accordion.jsx} (67%) diff --git a/packages/common/refresh-runtime.js b/packages/common/refresh-runtime.js index 3cac297ad..0029017fe 100644 --- a/packages/common/refresh-runtime.js +++ b/packages/common/refresh-runtime.js @@ -584,7 +584,7 @@ export function registerExportsForReactRefresh(filename, moduleExports) { for (const subKey in exportValue) { register( exportValue[subKey], - filename + ' export ' + key + '$' + subKey, + filename + ' export ' + key + '-' + subKey, ) } } diff --git a/packages/plugin-react-oxc/CHANGELOG.md b/packages/plugin-react-oxc/CHANGELOG.md index 64c1a4285..146a51778 100644 --- a/packages/plugin-react-oxc/CHANGELOG.md +++ b/packages/plugin-react-oxc/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -### Add support for compound components ([#518](https://github.com/vitejs/vite-plugin-react/pull/518)) +### Add HMR support for compound components ([#518](https://github.com/vitejs/vite-plugin-react/pull/518)) HMR now works for compound components like this: diff --git a/packages/plugin-react-swc/CHANGELOG.md b/packages/plugin-react-swc/CHANGELOG.md index 52f676ca5..a23f92d0a 100644 --- a/packages/plugin-react-swc/CHANGELOG.md +++ b/packages/plugin-react-swc/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -### Add support for compound components ([#518](https://github.com/vitejs/vite-plugin-react/pull/518)) +### Add HMR support for compound components ([#518](https://github.com/vitejs/vite-plugin-react/pull/518)) HMR now works for compound components like this: diff --git a/packages/plugin-react/CHANGELOG.md b/packages/plugin-react/CHANGELOG.md index f3eb4bde6..22373b8df 100644 --- a/packages/plugin-react/CHANGELOG.md +++ b/packages/plugin-react/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -### Add support for compound components ([#518](https://github.com/vitejs/vite-plugin-react/pull/518)) +### Add HMR support for compound components ([#518](https://github.com/vitejs/vite-plugin-react/pull/518)) HMR now works for compound components like this: diff --git a/playground/compound-components/__tests__/compound-components.spec.ts b/playground/compound-components/__tests__/compound-components.spec.ts deleted file mode 100644 index dbd779441..000000000 --- a/playground/compound-components/__tests__/compound-components.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { expect, test } from 'vitest' -import { editFile, isServe, page, untilBrowserLogAfter } from '~utils' - -test('should render compound components', async () => { - expect(await page.textContent('h1')).toMatch('Compound Components HMR Test') - expect(await page.textContent('h3')).toMatch('Accordion Root') -}) - -if (isServe) { - test('compound components should use HMR instead of full reload', async () => { - const logs = await untilBrowserLogAfter( - () => - editFile('src/Accordion.tsx', (code) => - code.replace('Accordion Root', 'Accordion Root Updated'), - ), - /\[vite\]/, - ) - - expect(logs).toContain('[vite] hot updated: /src/Accordion.tsx') - expect(logs).not.toContain('[vite] invalidate') - - // revert changes - editFile('src/Accordion.tsx', (code) => - code.replace('Accordion Root Updated', 'Accordion Root'), - ) - }) -} diff --git a/playground/compound-components/index.html b/playground/compound-components/index.html deleted file mode 100644 index 9eb87c2e5..000000000 --- a/playground/compound-components/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Compound Components Test - - -
- - - diff --git a/playground/compound-components/package.json b/playground/compound-components/package.json deleted file mode 100644 index 60bfa3426..000000000 --- a/playground/compound-components/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "@vitejs/test-compound-components", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "serve": "vite preview" - }, - "dependencies": { - "react": "^19.1.0", - "react-dom": "^19.1.0" - }, - "devDependencies": { - "@types/react": "^19.1.8", - "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "workspace:*", - "typescript": "^5.8.3" - } -} diff --git a/playground/compound-components/public/vite.svg b/playground/compound-components/public/vite.svg deleted file mode 100644 index ee9fadaf9..000000000 --- a/playground/compound-components/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/playground/compound-components/src/main.tsx b/playground/compound-components/src/main.tsx deleted file mode 100644 index 6a5bbec5f..000000000 --- a/playground/compound-components/src/main.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import { Accordion } from './Accordion' - -const root = ReactDOM.createRoot(document.getElementById('root')!) - -root.render( - -
-

Compound Components HMR Test

-

- This demonstrates the compound component pattern that causes full reload - instead of HMR. -

- - First Item - Second Item - -
-
, -) diff --git a/playground/compound-components/tsconfig.json b/playground/compound-components/tsconfig.json deleted file mode 100644 index f3f26c23e..000000000 --- a/playground/compound-components/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - "types": ["vite/client"], - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src", "vite.config.ts"] -} diff --git a/playground/compound-components/vite.config.ts b/playground/compound-components/vite.config.ts deleted file mode 100644 index dfdfc6398..000000000 --- a/playground/compound-components/vite.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import react from '@vitejs/plugin-react' -import type { UserConfig } from 'vite' - -const config: UserConfig = { - server: { port: 8907 /* Should be unique */ }, - mode: 'development', - plugins: [react()], - build: { - // to make tests faster - minify: false, - }, -} - -export default config diff --git a/playground/react/App.jsx b/playground/react/App.jsx index c7ffb805a..3d1b29988 100644 --- a/playground/react/App.jsx +++ b/playground/react/App.jsx @@ -1,6 +1,7 @@ import { useState } from 'react' import Button from 'jsx-entry' import Dummy from './components/Dummy?qs-should-not-break-plugin-react' +import { Accordion } from './components/Accordion' import Parent from './hmr/parent' import { JsxImportRuntime } from './hmr/jsx-import-runtime' import { CountProvider } from './context/CountProvider' @@ -38,6 +39,10 @@ function App() { + + First Item + Second Item + diff --git a/playground/react/__tests__/react.spec.ts b/playground/react/__tests__/react.spec.ts index f9d4275af..ea78cabfe 100644 --- a/playground/react/__tests__/react.spec.ts +++ b/playground/react/__tests__/react.spec.ts @@ -148,4 +148,19 @@ if (!isBuild) { expect(await page.textContent('#state-button')).toMatch('count is: 1') }) + + // #493 + test('should hmr compound components', async () => { + await untilBrowserLogAfter( + () => + editFile('components/Accordion.jsx', (code) => + code.replace('Accordion Root', 'Accordion Root Updated'), + ), + ['[vite] hot updated: /components/Accordion.jsx'], + ) + + await expect + .poll(() => page.textContent('#accordion-root')) + .toMatch('Accordion Root Updated') + }) } diff --git a/playground/compound-components/src/Accordion.tsx b/playground/react/components/Accordion.jsx similarity index 67% rename from playground/compound-components/src/Accordion.tsx rename to playground/react/components/Accordion.jsx index 6c35467a7..c690a48e0 100644 --- a/playground/compound-components/src/Accordion.tsx +++ b/playground/react/components/Accordion.jsx @@ -1,6 +1,4 @@ -import React from 'react' - -const Root: React.FC<{ children: React.ReactNode }> = ({ children }) => { +function Root({ children }) { return (
= ({ children }) => { margin: '16px 0', }} > -

+

Accordion Root

{children}
@@ -17,7 +18,7 @@ const Root: React.FC<{ children: React.ReactNode }> = ({ children }) => { ) } -const Item: React.FC<{ children: React.ReactNode }> = ({ children }) => { +function Item({ children }) { return (