From 2c091fae5237baf7bab37ced9bf816a98aa028ee Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Date: Mon, 19 Oct 2026 14:36:11 +0900 Subject: [PATCH 1/4] docs(rsc): document barrel package workaround for validate-imports Add e2e coverage with a synthetic `sideEffects: false` barrel package whose unused leaf imports `client-only`, which fails dev and build, and passes both with `@rolldown/plugin-transform-imports` rewriting to subpaths. Co-Authored-By: Claude Opus 5.5 --- packages/plugin-rsc/README.md | 47 ++++++ .../plugin-rsc/e2e/validate-imports.test.ts | 156 ++++++++++++++++++ packages/plugin-rsc/examples/e2e/package.json | 1 + pnpm-lock.yaml | 35 ++++ 4 files changed, 239 insertions(+) diff --git a/packages/plugin-rsc/README.md b/packages/plugin-rsc/README.md index be7e8d607..d927658e3 100644 --- a/packages/plugin-rsc/README.md +++ b/packages/plugin-rsc/README.md @@ -747,6 +747,53 @@ Note that while there are official npm packages [`server-only`](https://www.npmj This build-time validation is enabled by default and can be disabled by setting `validateImports: false` in the plugin options. +#### Barrel packages + +A server component that imports one component from a package's barrel entry can fail with `'client-only' cannot be imported in server build`, even though the component it uses is fine on the server: + +```tsx +import { Button } from 'some-ui-library' // ❌ fails because of `Calendar`, which imports 'client-only' +``` + +```js +// node_modules/some-ui-library/index.js +export { Button } from './button.js' +export { Calendar } from './calendar.js' +``` + +The validation runs on the module graph before tree-shaking, so the unused `calendar.js` and its `client-only` import are still part of the graph, even when the package has `"sideEffects": false`. Dev fails in the same way, because Vite evaluates the barrel and every module it re-exports. Other client-only code in an unused module, such as a top-level `createContext` call, can also crash dev, so disabling `validateImports` isn't a reliable fix. + +The fix is to import from the package's subpath entry instead, for example `import { Button } from 'some-ui-library/button'`. To do this without changing each import, rewrite barrel imports with [`@rolldown/plugin-transform-imports`](https://github.com/rolldown/plugins/tree/main/packages/transform-imports), which works in both dev and build on Vite 8: + +```js +import transformImports from '@rolldown/plugin-transform-imports' + +export default defineConfig({ + plugins: [ + transformImports({ + 'some-ui-library': { + transform: [ + // `Button` -> `some-ui-library/button` + ['[A-Z].*', 'some-ui-library/{{kebabCase member}}'], + // other exports, such as hooks, stay on the barrel entry + ['*', 'some-ui-library'], + ], + skipDefaultConversion: true, + }, + }), + rsc(), + react(), + ], +}) +``` + +Some things to watch out for: + +- Check that the member-to-subpath mapping matches the package's `exports`. A member that matches no pattern is rewritten to the member name itself, so keep the `'*'` fallback. +- `skipDefaultConversion: true` keeps `import { Button }` as a named import. Without it, the plugin assumes each subpath has a default export. +- The plugin only rewrites code that goes through Vite's plugin pipeline. In server environments, packages with a `react` peer dependency are not pre-bundled, so this covers typical UI libraries, but a pre-bundled dependency that imports the barrel itself isn't rewritten. +- If the subpath entry itself still reaches client-only code, rewriting imports doesn't help. Move that usage into a `"use client"` component instead. + ## Credits This project builds on fundamental techniques and insights from pioneering Vite RSC implementations. diff --git a/packages/plugin-rsc/e2e/validate-imports.test.ts b/packages/plugin-rsc/e2e/validate-imports.test.ts index edce8c868..d9255c818 100644 --- a/packages/plugin-rsc/e2e/validate-imports.test.ts +++ b/packages/plugin-rsc/e2e/validate-imports.test.ts @@ -1,5 +1,6 @@ import { test, expect } from '@playwright/test' import { x } from 'tinyexec' +import * as vite from 'vite' import { setupInlineFixture, useFixture, type Fixture } from './fixture' import { expectNoPageError, waitForHydration } from './helper' @@ -161,3 +162,158 @@ test.describe('validate imports', () => { }) }) }) + +// A server component importing one export from a `sideEffects: false` barrel +// package still pulls in every leaf the barrel re-exports, including one that +// imports `client-only`. Rewriting barrel imports to subpath imports with +// `@rolldown/plugin-transform-imports` keeps unused leaves out of the module +// graph in both dev and build. +test.describe('client-only in barrel package', () => { + function setupBarrelFixture(options: { + root: string + transformImports: boolean + }) { + return setupInlineFixture({ + src: 'examples/starter-extra', + dest: options.root, + files: { + 'package.json': { + edit: (s) => { + const pkg = JSON.parse(s) + pkg.dependencies['@vitejs/test-barrel'] = '0.0.0' + return JSON.stringify(pkg, null, 2) + }, + }, + 'node_modules/@vitejs/test-barrel/package.json': JSON.stringify({ + name: '@vitejs/test-barrel', + version: '0.0.0', + type: 'module', + sideEffects: false, + exports: { + '.': './index.js', + './button': './button.js', + './calendar': './calendar.js', + }, + peerDependencies: { react: '*' }, + }), + 'node_modules/@vitejs/test-barrel/index.js': /* js */ ` + export { Button } from './button.js' + export { Calendar } from './calendar.js' + `, + 'node_modules/@vitejs/test-barrel/button.js': /* js */ ` + import React from 'react' + export function Button(props) { + return React.createElement('button', null, props.children) + } + `, + 'node_modules/@vitejs/test-barrel/calendar.js': /* js */ ` + import 'client-only' + import React from 'react' + export function Calendar() { + return React.createElement('div', null, '[test-calendar]') + } + `, + 'src/root.tsx': /* tsx */ ` + import { Button } from '@vitejs/test-barrel' + + export function Root() { + return ( + + + + + + + + + ) + } + `, + ...(options.transformImports && { + 'vite.config.ts': { + edit: (s) => + s + .replace( + `import { defineConfig } from 'vite'`, + `import { defineConfig } from 'vite'\nimport transformImports from '@rolldown/plugin-transform-imports'`, + ) + .replace( + `plugins: [`, + `plugins: [ + transformImports({ + '@vitejs/test-barrel': { + transform: [ + ['[A-Z].*', '@vitejs/test-barrel/{{kebabCase member}}'], + ['*', '@vitejs/test-barrel'], + ], + skipDefaultConversion: true, + }, + }), + `, + ), + }, + }), + }, + }) + } + + test.describe('without transform', () => { + const root = 'examples/e2e/temp/validate-client-only-barrel' + test.beforeAll(async () => { + await setupBarrelFixture({ root, transformImports: false }) + }) + + test.describe('dev', () => { + const f = useFixture({ root, mode: 'dev' }) + + test('basic', async ({ request }) => { + const res = await request.get(f.url()) + expect(res.status()).toBe(500) + expect(await res.text()).toContain( + `'client-only' cannot be imported in server build`, + ) + }) + }) + + test('build', async () => { + const result = await x('pnpm', ['build'], { + throwOnError: false, + nodeOptions: { cwd: root }, + }) + expect(result.stderr).toContain(`rsc:validate-imports`) + expect(result.stderr).toContain( + `'client-only' cannot be imported in server build`, + ) + expect(result.exitCode).not.toBe(0) + }) + }) + + test.describe('with transform', () => { + test.skip(!('rolldownVersion' in vite), 'rolldown only') + + const root = 'examples/e2e/temp/validate-client-only-barrel-transform' + test.beforeAll(async () => { + await setupBarrelFixture({ root, transformImports: true }) + }) + + test.describe('dev', () => { + const f = useFixture({ root, mode: 'dev' }) + defineTest(f) + }) + + test.describe('build', () => { + const f = useFixture({ root, mode: 'build' }) + defineTest(f) + }) + + function defineTest(f: Fixture) { + test('basic', async ({ page }) => { + using _ = expectNoPageError(page) + await page.goto(f.url()) + await expect(page.getByRole('button')).toHaveText( + '[test-barrel-button]', + ) + }) + } + }) +}) diff --git a/packages/plugin-rsc/examples/e2e/package.json b/packages/plugin-rsc/examples/e2e/package.json index 9bd59da85..c4143b784 100644 --- a/packages/plugin-rsc/examples/e2e/package.json +++ b/packages/plugin-rsc/examples/e2e/package.json @@ -5,6 +5,7 @@ "devDependencies": { "@cloudflare/vite-plugin": "^1.62.5", "@rolldown/plugin-babel": "^0.2.4", + "@rolldown/plugin-transform-imports": "^0.1.2", "@vitejs/plugin-react": "latest", "@vitejs/plugin-rsc": "latest", "babel-plugin-react-compiler": "^1.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ee1e39690..b2239b86f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -729,6 +729,9 @@ importers: '@rolldown/plugin-babel': specifier: ^0.2.4 version: 0.2.4(@babel/core@8.0.6)(rolldown@1.2.12)(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.1)) + '@rolldown/plugin-transform-imports': + specifier: ^0.1.2 + version: 0.1.2(rolldown@1.2.12)(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.1)) '@vitejs/plugin-react': specifier: latest version: link:../../../plugin-react @@ -2642,6 +2645,16 @@ packages: vite: optional: true + '@rolldown/plugin-transform-imports@0.1.2': + resolution: {integrity: sha512-C6I8jO2Eg2LfBkzwR9ZEKv6I2rtSETL/psCnGHNHqOENJwBHeEAYPyq9RhDqEO8sF6RtLwlpDsbg6lu76YxT3w==} + engines: {node: '>=22.12.0 || ^24.0.0'} + peerDependencies: + rolldown: ^1.0.0-rc.13 + vite: ^8.0.0 + peerDependenciesMeta: + vite: + optional: true + '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} @@ -4531,6 +4544,15 @@ packages: vue-tsc: optional: true + rolldown-string@0.3.1: + resolution: {integrity: sha512-dv8GOXkYqUQdI0rsXB8hmzO95pKWSuX2eg5/JSJa9czfEVIFuKNR7ZJDfat8LlmD35RAhWY+evS7/wXXqFDfSg==} + engines: {node: '>=20.19.0'} + peerDependencies: + rolldown: '*' + peerDependenciesMeta: + rolldown: + optional: true + rolldown@1.2.12: resolution: {integrity: sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5926,6 +5948,13 @@ snapshots: optionalDependencies: vite: 8.3.2(@types/node@24.19.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.1) + '@rolldown/plugin-transform-imports@0.1.2(rolldown@1.2.12)(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.1))': + dependencies: + rolldown: 1.2.12 + rolldown-string: 0.3.1(rolldown@1.2.12) + optionalDependencies: + vite: 8.3.2(@types/node@24.19.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.1) + '@rolldown/pluginutils@1.0.1': {} '@rollup/pluginutils@5.1.4': @@ -7775,6 +7804,12 @@ snapshots: transitivePeerDependencies: - oxc-resolver + rolldown-string@0.3.1(rolldown@1.2.12): + dependencies: + magic-string: 1.4.3 + optionalDependencies: + rolldown: 1.2.12 + rolldown@1.2.12: dependencies: '@oxc-project/types': 0.152.0 From 8bae713c4c6db57d16285ecf56c19f9698fd135e Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Date: Mon, 19 Oct 2026 14:41:31 +0900 Subject: [PATCH 2/4] test(rsc): move synthetic barrel package to test-dep Co-Authored-By: Claude Opus 5.5 --- .../plugin-rsc/e2e/validate-imports.test.ts | 41 ++++--------------- packages/plugin-rsc/examples/e2e/package.json | 1 + .../examples/e2e/test-dep/barrel/button.js | 5 +++ .../examples/e2e/test-dep/barrel/calendar.js | 6 +++ .../examples/e2e/test-dep/barrel/index.js | 2 + .../examples/e2e/test-dep/barrel/package.json | 14 +++++++ pnpm-lock.yaml | 12 ++++++ 7 files changed, 47 insertions(+), 34 deletions(-) create mode 100644 packages/plugin-rsc/examples/e2e/test-dep/barrel/button.js create mode 100644 packages/plugin-rsc/examples/e2e/test-dep/barrel/calendar.js create mode 100644 packages/plugin-rsc/examples/e2e/test-dep/barrel/index.js create mode 100644 packages/plugin-rsc/examples/e2e/test-dep/barrel/package.json diff --git a/packages/plugin-rsc/e2e/validate-imports.test.ts b/packages/plugin-rsc/e2e/validate-imports.test.ts index d9255c818..a715b90ce 100644 --- a/packages/plugin-rsc/e2e/validate-imports.test.ts +++ b/packages/plugin-rsc/e2e/validate-imports.test.ts @@ -177,44 +177,17 @@ test.describe('client-only in barrel package', () => { src: 'examples/starter-extra', dest: options.root, files: { + // listed so that the plugin treats it as a React package and bundles + // it in server environments, as it would for a real UI library 'package.json': { edit: (s) => { const pkg = JSON.parse(s) - pkg.dependencies['@vitejs/test-barrel'] = '0.0.0' + pkg.dependencies['@vitejs/test-dep-barrel'] = '*' return JSON.stringify(pkg, null, 2) }, }, - 'node_modules/@vitejs/test-barrel/package.json': JSON.stringify({ - name: '@vitejs/test-barrel', - version: '0.0.0', - type: 'module', - sideEffects: false, - exports: { - '.': './index.js', - './button': './button.js', - './calendar': './calendar.js', - }, - peerDependencies: { react: '*' }, - }), - 'node_modules/@vitejs/test-barrel/index.js': /* js */ ` - export { Button } from './button.js' - export { Calendar } from './calendar.js' - `, - 'node_modules/@vitejs/test-barrel/button.js': /* js */ ` - import React from 'react' - export function Button(props) { - return React.createElement('button', null, props.children) - } - `, - 'node_modules/@vitejs/test-barrel/calendar.js': /* js */ ` - import 'client-only' - import React from 'react' - export function Calendar() { - return React.createElement('div', null, '[test-calendar]') - } - `, 'src/root.tsx': /* tsx */ ` - import { Button } from '@vitejs/test-barrel' + import { Button } from '@vitejs/test-dep-barrel' export function Root() { return ( @@ -241,10 +214,10 @@ test.describe('client-only in barrel package', () => { `plugins: [`, `plugins: [ transformImports({ - '@vitejs/test-barrel': { + '@vitejs/test-dep-barrel': { transform: [ - ['[A-Z].*', '@vitejs/test-barrel/{{kebabCase member}}'], - ['*', '@vitejs/test-barrel'], + ['[A-Z].*', '@vitejs/test-dep-barrel/{{kebabCase member}}'], + ['*', '@vitejs/test-dep-barrel'], ], skipDefaultConversion: true, }, diff --git a/packages/plugin-rsc/examples/e2e/package.json b/packages/plugin-rsc/examples/e2e/package.json index c4143b784..b8954a6eb 100644 --- a/packages/plugin-rsc/examples/e2e/package.json +++ b/packages/plugin-rsc/examples/e2e/package.json @@ -8,6 +8,7 @@ "@rolldown/plugin-transform-imports": "^0.1.2", "@vitejs/plugin-react": "latest", "@vitejs/plugin-rsc": "latest", + "@vitejs/test-dep-barrel": "file:./test-dep/barrel", "babel-plugin-react-compiler": "^1.0.0", "connect": "^3.7.0", "rsc-html-stream": "^0.0.8", diff --git a/packages/plugin-rsc/examples/e2e/test-dep/barrel/button.js b/packages/plugin-rsc/examples/e2e/test-dep/barrel/button.js new file mode 100644 index 000000000..a288254a9 --- /dev/null +++ b/packages/plugin-rsc/examples/e2e/test-dep/barrel/button.js @@ -0,0 +1,5 @@ +import React from 'react' + +export function Button(props) { + return React.createElement('button', null, props.children) +} diff --git a/packages/plugin-rsc/examples/e2e/test-dep/barrel/calendar.js b/packages/plugin-rsc/examples/e2e/test-dep/barrel/calendar.js new file mode 100644 index 000000000..4e174dcc5 --- /dev/null +++ b/packages/plugin-rsc/examples/e2e/test-dep/barrel/calendar.js @@ -0,0 +1,6 @@ +import 'client-only' +import React from 'react' + +export function Calendar() { + return React.createElement('div', null, '[test-calendar]') +} diff --git a/packages/plugin-rsc/examples/e2e/test-dep/barrel/index.js b/packages/plugin-rsc/examples/e2e/test-dep/barrel/index.js new file mode 100644 index 000000000..ea38f579e --- /dev/null +++ b/packages/plugin-rsc/examples/e2e/test-dep/barrel/index.js @@ -0,0 +1,2 @@ +export { Button } from './button.js' +export { Calendar } from './calendar.js' diff --git a/packages/plugin-rsc/examples/e2e/test-dep/barrel/package.json b/packages/plugin-rsc/examples/e2e/test-dep/barrel/package.json new file mode 100644 index 000000000..7b45b796e --- /dev/null +++ b/packages/plugin-rsc/examples/e2e/test-dep/barrel/package.json @@ -0,0 +1,14 @@ +{ + "name": "@vitejs/test-dep-barrel", + "private": true, + "type": "module", + "sideEffects": false, + "exports": { + ".": "./index.js", + "./button": "./button.js", + "./calendar": "./calendar.js" + }, + "peerDependencies": { + "react": "*" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2239b86f..6dd77a8e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -738,6 +738,9 @@ importers: '@vitejs/plugin-rsc': specifier: latest version: link:../.. + '@vitejs/test-dep-barrel': + specifier: file:./test-dep/barrel + version: file:packages/plugin-rsc/examples/e2e/test-dep/barrel(react@19.3.1) babel-plugin-react-compiler: specifier: ^1.0.0 version: 1.0.0 @@ -3112,6 +3115,11 @@ packages: '@vitejs/release-scripts@1.10.0': resolution: {integrity: sha512-1vPdK4Jm2vxCFpaf4D7Nwam677OUoXqUU0WqhLjYec38qiK/XXkzuWK6QnFkn9ty63F+slN13pNUCwEHPOLRuQ==} + '@vitejs/test-dep-barrel@file:packages/plugin-rsc/examples/e2e/test-dep/barrel': + resolution: {directory: packages/plugin-rsc/examples/e2e/test-dep/barrel, type: directory} + peerDependencies: + react: '*' + '@vitejs/test-dep-cjs-events-extend@file:packages/plugin-rsc/examples/basic/test-dep/cjs-events-extend': resolution: {directory: packages/plugin-rsc/examples/basic/test-dep/cjs-events-extend, type: directory} peerDependencies: @@ -6309,6 +6317,10 @@ snapshots: semver: 7.8.5 tinyexec: 1.3.1 + '@vitejs/test-dep-barrel@file:packages/plugin-rsc/examples/e2e/test-dep/barrel(react@19.3.1)': + dependencies: + react: 19.3.1 + '@vitejs/test-dep-cjs-events-extend@file:packages/plugin-rsc/examples/basic/test-dep/cjs-events-extend(react@19.3.1)': dependencies: react: 19.3.1 From e80b5544de1d97ad833cdf96057992f96e9f7b41 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Date: Mon, 19 Oct 2026 14:43:51 +0900 Subject: [PATCH 3/4] test(rsc): clarify why the fixture lists the barrel test-dep Co-Authored-By: Claude Opus 5.5 --- packages/plugin-rsc/e2e/validate-imports.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/plugin-rsc/e2e/validate-imports.test.ts b/packages/plugin-rsc/e2e/validate-imports.test.ts index a715b90ce..bc82f9984 100644 --- a/packages/plugin-rsc/e2e/validate-imports.test.ts +++ b/packages/plugin-rsc/e2e/validate-imports.test.ts @@ -177,8 +177,9 @@ test.describe('client-only in barrel package', () => { src: 'examples/starter-extra', dest: options.root, files: { - // listed so that the plugin treats it as a React package and bundles - // it in server environments, as it would for a real UI library + // the package is installed through examples/e2e/package.json, but the plugin + // only crawls the app's own package.json to find React packages to bundle in + // server environments, so list it here as well 'package.json': { edit: (s) => { const pkg = JSON.parse(s) From f419141fcbdfc1921e58a25e1c09c044129a30aa Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Date: Mon, 19 Oct 2026 14:45:09 +0900 Subject: [PATCH 4/4] docs(rsc): tighten barrel packages section Co-Authored-By: Claude Opus 5.5 --- packages/plugin-rsc/README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/plugin-rsc/README.md b/packages/plugin-rsc/README.md index d927658e3..92879dd25 100644 --- a/packages/plugin-rsc/README.md +++ b/packages/plugin-rsc/README.md @@ -761,9 +761,9 @@ export { Button } from './button.js' export { Calendar } from './calendar.js' ``` -The validation runs on the module graph before tree-shaking, so the unused `calendar.js` and its `client-only` import are still part of the graph, even when the package has `"sideEffects": false`. Dev fails in the same way, because Vite evaluates the barrel and every module it re-exports. Other client-only code in an unused module, such as a top-level `createContext` call, can also crash dev, so disabling `validateImports` isn't a reliable fix. +The validation runs on the module graph before tree-shaking, so the unused `calendar.js` and its `client-only` import are still part of the graph, even when the package has `"sideEffects": false`. Dev fails in the same way, because Vite evaluates the barrel and every module it re-exports. Disabling `validateImports` doesn't fix dev reliably either, because other client-only code in an unused module, such as a top-level `createContext` call, can still crash the server. -The fix is to import from the package's subpath entry instead, for example `import { Button } from 'some-ui-library/button'`. To do this without changing each import, rewrite barrel imports with [`@rolldown/plugin-transform-imports`](https://github.com/rolldown/plugins/tree/main/packages/transform-imports), which works in both dev and build on Vite 8: +To fix this, import from the package's subpath entry instead, for example `import { Button } from 'some-ui-library/button'`. To do this without changing each import, rewrite barrel imports with [`@rolldown/plugin-transform-imports`](https://github.com/rolldown/plugins/tree/main/packages/transform-imports), which applies in both dev and build and requires Vite 8: ```js import transformImports from '@rolldown/plugin-transform-imports' @@ -787,12 +787,13 @@ export default defineConfig({ }) ``` -Some things to watch out for: +When configuring the plugin: - Check that the member-to-subpath mapping matches the package's `exports`. A member that matches no pattern is rewritten to the member name itself, so keep the `'*'` fallback. -- `skipDefaultConversion: true` keeps `import { Button }` as a named import. Without it, the plugin assumes each subpath has a default export. -- The plugin only rewrites code that goes through Vite's plugin pipeline. In server environments, packages with a `react` peer dependency are not pre-bundled, so this covers typical UI libraries, but a pre-bundled dependency that imports the barrel itself isn't rewritten. -- If the subpath entry itself still reaches client-only code, rewriting imports doesn't help. Move that usage into a `"use client"` component instead. +- Set `skipDefaultConversion: true` when the subpath entries use named exports. Otherwise the plugin rewrites `import { Button }` to a default import. +- The plugin only rewrites code that Vite transforms, which excludes pre-bundled dependencies. Packages with a `react` peer dependency are not pre-bundled in server environments, so imports of a typical UI library are covered, but a pre-bundled dependency that imports the barrel itself isn't rewritten. + +If the subpath entry itself still reaches client-only code, rewriting imports doesn't help. Move that usage into a `"use client"` component instead. ## Credits