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