diff --git a/packages/plugin-rsc/e2e/basic.test.ts b/packages/plugin-rsc/e2e/basic.test.ts
index 99163871..7e9f4880 100644
--- a/packages/plugin-rsc/e2e/basic.test.ts
+++ b/packages/plugin-rsc/e2e/basic.test.ts
@@ -1454,4 +1454,12 @@ function defineTest(f: Fixture) {
'test-chunk1|test-chunk2|test-chunk2b|test-chunk3|test-chunk3',
)
})
+
+ test('tree-shake2', async ({ page }) => {
+ await page.goto(f.url())
+ await waitForHydration(page)
+ await expect(page.getByTestId('test-tree-shake2')).toHaveText(
+ 'test-tree-shake2:lib-client1|lib-server1',
+ )
+ })
}
diff --git a/packages/plugin-rsc/examples/basic/src/routes/root.tsx b/packages/plugin-rsc/examples/basic/src/routes/root.tsx
index aa16feba..42951874 100644
--- a/packages/plugin-rsc/examples/basic/src/routes/root.tsx
+++ b/packages/plugin-rsc/examples/basic/src/routes/root.tsx
@@ -39,6 +39,7 @@ import { TestAssetsServer } from './assets/server'
import { TestHmrSwitchServer } from './hmr-switch/server'
import { TestHmrSwitchClient } from './hmr-switch/client'
import { TestTreeShakeServer } from './tree-shake/server'
+import { TestTreeShake2 } from './tree-shake2/server'
import { TestClientChunkServer } from './chunk/server'
import { TestTailwind } from './tailwind'
import { TestHmrClientDep2 } from './hmr-client-dep2/client'
@@ -100,6 +101,7 @@ export function Root(props: { url: URL }) {
+
diff --git a/packages/plugin-rsc/examples/basic/src/routes/tree-shake2/lib-client1.tsx b/packages/plugin-rsc/examples/basic/src/routes/tree-shake2/lib-client1.tsx
new file mode 100644
index 00000000..df4f7bfd
--- /dev/null
+++ b/packages/plugin-rsc/examples/basic/src/routes/tree-shake2/lib-client1.tsx
@@ -0,0 +1,5 @@
+'use client'
+
+export function LibClient1() {
+ return 'lib-client1'
+}
diff --git a/packages/plugin-rsc/examples/basic/src/routes/tree-shake2/lib-client2.tsx b/packages/plugin-rsc/examples/basic/src/routes/tree-shake2/lib-client2.tsx
new file mode 100644
index 00000000..4bb13bd3
--- /dev/null
+++ b/packages/plugin-rsc/examples/basic/src/routes/tree-shake2/lib-client2.tsx
@@ -0,0 +1,5 @@
+'use client'
+
+export function LibClient2() {
+ return 'lib-client2:__unused_tree_shake2__'
+}
diff --git a/packages/plugin-rsc/examples/basic/src/routes/tree-shake2/lib-server1.tsx b/packages/plugin-rsc/examples/basic/src/routes/tree-shake2/lib-server1.tsx
new file mode 100644
index 00000000..ff764017
--- /dev/null
+++ b/packages/plugin-rsc/examples/basic/src/routes/tree-shake2/lib-server1.tsx
@@ -0,0 +1,3 @@
+export function LibServer1() {
+ return 'lib-server1'
+}
diff --git a/packages/plugin-rsc/examples/basic/src/routes/tree-shake2/lib-server2.tsx b/packages/plugin-rsc/examples/basic/src/routes/tree-shake2/lib-server2.tsx
new file mode 100644
index 00000000..22bb7efc
--- /dev/null
+++ b/packages/plugin-rsc/examples/basic/src/routes/tree-shake2/lib-server2.tsx
@@ -0,0 +1,3 @@
+export function LibServer2() {
+ return 'lib-server2:__unused_tree_shake2__'
+}
diff --git a/packages/plugin-rsc/examples/basic/src/routes/tree-shake2/lib.tsx b/packages/plugin-rsc/examples/basic/src/routes/tree-shake2/lib.tsx
new file mode 100644
index 00000000..76d4c3c2
--- /dev/null
+++ b/packages/plugin-rsc/examples/basic/src/routes/tree-shake2/lib.tsx
@@ -0,0 +1,4 @@
+export * from './lib-client1'
+export * from './lib-client2'
+export * from './lib-server1'
+export * from './lib-server2'
diff --git a/packages/plugin-rsc/examples/basic/src/routes/tree-shake2/server.tsx b/packages/plugin-rsc/examples/basic/src/routes/tree-shake2/server.tsx
new file mode 100644
index 00000000..39e2652d
--- /dev/null
+++ b/packages/plugin-rsc/examples/basic/src/routes/tree-shake2/server.tsx
@@ -0,0 +1,10 @@
+import { LibClient1, LibServer1 } from './lib'
+
+export function TestTreeShake2() {
+ return (
+
+ test-tree-shake2:
+ |
+
+ )
+}
diff --git a/packages/plugin-rsc/examples/basic/vite.config.ts b/packages/plugin-rsc/examples/basic/vite.config.ts
index 144117f2..3da96ca2 100644
--- a/packages/plugin-rsc/examples/basic/vite.config.ts
+++ b/packages/plugin-rsc/examples/basic/vite.config.ts
@@ -47,6 +47,7 @@ export default defineConfig({
if (chunk.type === 'chunk') {
assert(!chunk.code.includes('__unused_client_reference__'))
assert(!chunk.code.includes('__unused_server_export__'))
+ assert(!chunk.code.includes('__unused_tree_shake2__'))
}
}
},
diff --git a/packages/plugin-rsc/src/plugin.ts b/packages/plugin-rsc/src/plugin.ts
index 99f979a8..13b36d15 100644
--- a/packages/plugin-rsc/src/plugin.ts
+++ b/packages/plugin-rsc/src/plugin.ts
@@ -1226,12 +1226,14 @@ function vitePluginUseClient(
// group client reference modules by `clientChunks` option
manager.clientReferenceGroups = {}
for (const meta of Object.values(manager.clientReferenceMetaMap)) {
+ // no server chunk is associated when the entire "use client" module is tree-shaken
+ if (!meta.serverChunk) continue
let name =
useClientPluginOptions.clientChunks?.({
id: meta.importId,
normalizedId: manager.toRelativeId(meta.importId),
- serverChunk: meta.serverChunk!,
- }) ?? meta.serverChunk!
+ serverChunk: meta.serverChunk,
+ }) ?? meta.serverChunk
// ensure clean virtual id to avoid interfering with other plugins
name = cleanUrl(name.replaceAll('..', '__'))
const group = (manager.clientReferenceGroups[name] ??= [])
@@ -1333,6 +1335,7 @@ function vitePluginUseClient(
}
},
generateBundle(_options, bundle) {
+ if (manager.isScanBuild) return
if (this.environment.name !== serverEnvironmentName) return
// analyze rsc build to inform later client reference building.
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 839f1e5e..cb46e35c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -643,7 +643,7 @@ importers:
devDependencies:
'@cloudflare/vite-plugin':
specifier: ^1.12.3
- version: 1.12.3(vite@7.1.4(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.7.1))(workerd@1.20250902.0)(wrangler@4.34.0)
+ version: 1.12.3(vite@7.1.4(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.7.1))(workerd@1.20250902.0)
'@tailwindcss/typography':
specifier: ^0.5.16
version: 0.5.16(tailwindcss@4.1.13)
@@ -736,7 +736,7 @@ importers:
devDependencies:
'@cloudflare/vite-plugin':
specifier: ^1.12.3
- version: 1.12.3(vite@7.1.4(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.7.1))(workerd@1.20250902.0)(wrangler@4.34.0)
+ version: 1.12.3(vite@7.1.4(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.7.1))(workerd@1.20250902.0)
'@types/react':
specifier: ^19.1.12
version: 19.1.12
@@ -1210,7 +1210,6 @@ packages:
resolution: {integrity: sha512-kdXo0/qERVs7xQfv03O1Z5vZ8+nfdISY5dTKf2qWmqPQHEc4QRD5ImFG9SfDFZvcYZwEkX10mqNCZl3HXagdgA==}
peerDependencies:
vite: ^6.1.0 || ^7.0.0
- wrangler: ^4.34.0
'@cloudflare/workerd-darwin-64@1.20250902.0':
resolution: {integrity: sha512-mwC/YEtDUGfnjXdbW5Lya+bgODrpJ5RxxqpaTjtMJycqnjR0RZgVpOqISwGfBHIhseykU3ahPugM5t91XkBKTg==}
@@ -4958,7 +4957,7 @@ snapshots:
optionalDependencies:
workerd: 1.20250902.0
- '@cloudflare/vite-plugin@1.12.3(vite@7.1.4(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.7.1))(workerd@1.20250902.0)(wrangler@4.34.0)':
+ '@cloudflare/vite-plugin@1.12.3(vite@7.1.4(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.7.1))(workerd@1.20250902.0)':
dependencies:
'@cloudflare/unenv-preset': 2.7.2(unenv@2.0.0-rc.20)(workerd@1.20250902.0)
'@remix-run/node-fetch-server': 0.8.0
@@ -4971,6 +4970,7 @@ snapshots:
wrangler: 4.34.0
ws: 8.18.0
transitivePeerDependencies:
+ - '@cloudflare/workers-types'
- bufferutil
- utf-8-validate
- workerd