diff --git a/packages/plugin-rsc/e2e/basic.test.ts b/packages/plugin-rsc/e2e/basic.test.ts index 71c2679a..2955afce 100644 --- a/packages/plugin-rsc/e2e/basic.test.ts +++ b/packages/plugin-rsc/e2e/basic.test.ts @@ -52,7 +52,7 @@ test.describe('dev-non-optimized-cjs', () => { const editor = f.createEditor('vite.config.ts') editor.edit((s) => s.replace( - `'@vitejs/test-dep-transitive-cjs > use-sync-external-store/shim/index.js',`, + `include: ['@vitejs/test-dep-transitive-cjs > @vitejs/test-dep-cjs'],`, ``, ), ) @@ -941,9 +941,10 @@ function defineTest(f: Fixture) { test('transitive cjs dep', async ({ page }) => { await page.goto(f.url()) await waitForHydration(page) - await expect(page.getByTestId('transitive-cjs-client')).toHaveText( - 'ok:browser', - ) + await expect(page.getByTestId('transitive-cjs-client')).toHaveText('ok') + await expect( + page.getByTestId('transitive-use-sync-external-store-client'), + ).toHaveText('ok:browser') }) test('use cache function', async ({ page }) => { diff --git a/packages/plugin-rsc/examples/basic/package.json b/packages/plugin-rsc/examples/basic/package.json index e0b2de77..d80757fe 100644 --- a/packages/plugin-rsc/examples/basic/package.json +++ b/packages/plugin-rsc/examples/basic/package.json @@ -22,6 +22,7 @@ "@types/react-dom": "^19.1.7", "@vitejs/plugin-react": "latest", "@vitejs/test-dep-transitive-cjs": "file:./test-dep/transitive-cjs", + "@vitejs/test-dep-transitive-use-sync-external-store": "file:./test-dep/transitive-use-sync-external-store", "@vitejs/test-dep-client-in-server": "file:./test-dep/client-in-server", "@vitejs/test-dep-client-in-server2": "file:./test-dep/client-in-server2", "@vitejs/test-dep-server-in-client": "file:./test-dep/server-in-client", diff --git a/packages/plugin-rsc/examples/basic/src/routes/deps/transitive-cjs/client.tsx b/packages/plugin-rsc/examples/basic/src/routes/deps/transitive-cjs/client.tsx index 11045ee3..10c2a1e4 100644 --- a/packages/plugin-rsc/examples/basic/src/routes/deps/transitive-cjs/client.tsx +++ b/packages/plugin-rsc/examples/basic/src/routes/deps/transitive-cjs/client.tsx @@ -3,10 +3,18 @@ // @ts-ignore import { TestClient } from '@vitejs/test-dep-transitive-cjs/client' +// @ts-ignore +import { TestClient as TestClient2 } from '@vitejs/test-dep-transitive-use-sync-external-store/client' + export function TestTransitiveCjsClient() { return ( -
- [test-dep-transitive-cjs-client: ] -
+ <> +
+ [test-dep-transitive-cjs-client: ] +
+
+ [test-dep-transitive-use-sync-external-store-client: ] +
+ ) } diff --git a/packages/plugin-rsc/examples/basic/test-dep/cjs/index.js b/packages/plugin-rsc/examples/basic/test-dep/cjs/index.js new file mode 100644 index 00000000..94dc9b3a --- /dev/null +++ b/packages/plugin-rsc/examples/basic/test-dep/cjs/index.js @@ -0,0 +1 @@ +exports.ok = 'ok' diff --git a/packages/plugin-rsc/examples/basic/test-dep/cjs/package.json b/packages/plugin-rsc/examples/basic/test-dep/cjs/package.json new file mode 100644 index 00000000..dc270dca --- /dev/null +++ b/packages/plugin-rsc/examples/basic/test-dep/cjs/package.json @@ -0,0 +1,9 @@ +{ + "name": "@vitejs/test-dep-cjs", + "private": true, + "type": "commonjs", + "exports": "./index.js", + "peerDependencies": { + "react": "*" + } +} diff --git a/packages/plugin-rsc/examples/basic/test-dep/transitive-cjs/client.js b/packages/plugin-rsc/examples/basic/test-dep/transitive-cjs/client.js index db2fd4b6..87aa08e6 100644 --- a/packages/plugin-rsc/examples/basic/test-dep/transitive-cjs/client.js +++ b/packages/plugin-rsc/examples/basic/test-dep/transitive-cjs/client.js @@ -2,26 +2,16 @@ import React from 'react' -// similar to swr -// https://github.com/vercel/swr/blob/063fe55dddb95f0b6c3f1637a935c43d732ded78/src/index/use-swr.ts#L3 -import { useSyncExternalStore } from 'use-sync-external-store/shim/index.js' +import { ok } from '@vitejs/test-dep-cjs' const h = React.createElement -const noopStore = () => () => {} - export function TestClient() { - const value = useSyncExternalStore( - noopStore, - () => 'ok:browser', - () => 'ok:ssr', - ) - return h( 'span', { 'data-testid': 'transitive-cjs-client', }, - value, + ok, ) } diff --git a/packages/plugin-rsc/examples/basic/test-dep/transitive-cjs/package.json b/packages/plugin-rsc/examples/basic/test-dep/transitive-cjs/package.json index 8368742d..e0a0eaea 100644 --- a/packages/plugin-rsc/examples/basic/test-dep/transitive-cjs/package.json +++ b/packages/plugin-rsc/examples/basic/test-dep/transitive-cjs/package.json @@ -6,7 +6,7 @@ "./client": "./client.js" }, "dependencies": { - "use-sync-external-store": "^1.5.0" + "@vitejs/test-dep-cjs": "file:../cjs" }, "peerDependencies": { "react": "*" diff --git a/packages/plugin-rsc/examples/basic/test-dep/transitive-use-sync-external-store/client.js b/packages/plugin-rsc/examples/basic/test-dep/transitive-use-sync-external-store/client.js new file mode 100644 index 00000000..1ac3e0cc --- /dev/null +++ b/packages/plugin-rsc/examples/basic/test-dep/transitive-use-sync-external-store/client.js @@ -0,0 +1,28 @@ +'use client' + +import React from 'react' + +// similar to +// https://github.com/vercel/swr/blob/063fe55dddb95f0b6c3f1637a935c43d732ded78/src/index/use-swr.ts#L3 +// https://github.com/TanStack/store/blob/1d1323283e79059821d6c731eaaee60e4143dbc2/packages/react-store/src/index.ts#L1 +import { useSyncExternalStore } from 'use-sync-external-store/shim/index.js' + +const h = React.createElement + +const noopStore = () => () => {} + +export function TestClient() { + const value = useSyncExternalStore( + noopStore, + () => 'ok:browser', + () => 'ok:ssr', + ) + + return h( + 'span', + { + 'data-testid': 'transitive-use-sync-external-store-client', + }, + value, + ) +} diff --git a/packages/plugin-rsc/examples/basic/test-dep/transitive-use-sync-external-store/package.json b/packages/plugin-rsc/examples/basic/test-dep/transitive-use-sync-external-store/package.json new file mode 100644 index 00000000..c478349f --- /dev/null +++ b/packages/plugin-rsc/examples/basic/test-dep/transitive-use-sync-external-store/package.json @@ -0,0 +1,14 @@ +{ + "name": "@vitejs/test-dep-transitive-use-sync-external-store", + "private": true, + "type": "module", + "exports": { + "./client": "./client.js" + }, + "dependencies": { + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "react": "*" + } +} diff --git a/packages/plugin-rsc/examples/basic/vite.config.ts b/packages/plugin-rsc/examples/basic/vite.config.ts index 94df04e0..98edceac 100644 --- a/packages/plugin-rsc/examples/basic/vite.config.ts +++ b/packages/plugin-rsc/examples/basic/vite.config.ts @@ -153,9 +153,7 @@ export default { fetch: handler }; }, ssr: { optimizeDeps: { - include: [ - '@vitejs/test-dep-transitive-cjs > use-sync-external-store/shim/index.js', - ], + include: ['@vitejs/test-dep-transitive-cjs > @vitejs/test-dep-cjs'], }, }, }, diff --git a/packages/plugin-rsc/package.json b/packages/plugin-rsc/package.json index 5d92ab98..ec4b66e8 100644 --- a/packages/plugin-rsc/package.json +++ b/packages/plugin-rsc/package.json @@ -44,6 +44,7 @@ "magic-string": "^0.30.17", "periscopic": "^4.0.2", "turbo-stream": "^3.1.0", + "use-sync-external-store": "^1.5.0", "vitefu": "^1.1.1" }, "devDependencies": { diff --git a/packages/plugin-rsc/src/plugin.ts b/packages/plugin-rsc/src/plugin.ts index 1ba25551..91b7e811 100644 --- a/packages/plugin-rsc/src/plugin.ts +++ b/packages/plugin-rsc/src/plugin.ts @@ -882,6 +882,7 @@ globalThis.AsyncLocalStorage = __viteRscAyncHooks.AsyncLocalStorage; ...(rscPluginOptions.validateImports !== false ? [validateImportPlugin()] : []), + ...vendorUseSyncExternalStorePlugin(), scanBuildStripPlugin(), detectNonOptimizedCjsPlugin(), ] @@ -2124,6 +2125,41 @@ function validateImportPlugin(): Plugin { } } +function vendorUseSyncExternalStorePlugin(): Plugin[] { + // vendor and optimize use-sync-external-store out of the box + // since this is a common enough cjs, which tends to break + // other packages (e.g. swr, @tanstack/react-store) + + // https://github.com/facebook/react/blob/c499adf8c89bbfd884f4d3a58c4e510001383525/packages/use-sync-external-store/package.json#L5-L20 + const exports = [ + 'use-sync-external-store', + 'use-sync-external-store/with-selector', + 'use-sync-external-store/with-selector.js', + 'use-sync-external-store/shim', + 'use-sync-external-store/shim/index.js', + 'use-sync-external-store/shim/with-selector', + 'use-sync-external-store/shim/with-selector.js', + ] + + return [ + { + name: 'rsc:vendor-use-sync-external-store', + apply: 'serve', + config() { + return { + environments: { + ssr: { + optimizeDeps: { + include: exports.map((e) => `${PKG_NAME} > ${e}`), + }, + }, + }, + } + }, + }, + ] +} + function sortObject(o: T) { return Object.fromEntries( Object.entries(o).sort(([a], [b]) => a.localeCompare(b)), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0867e2a7..ec37ca6f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -456,6 +456,9 @@ importers: turbo-stream: specifier: ^3.1.0 version: 3.1.0 + use-sync-external-store: + specifier: ^1.5.0 + version: 1.5.0(react@19.1.1) vitefu: specifier: ^1.1.1 version: 1.1.1(vite@7.0.6(@types/node@22.17.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)) @@ -548,6 +551,9 @@ importers: '@vitejs/test-dep-transitive-cjs': specifier: file:./test-dep/transitive-cjs version: file:packages/plugin-rsc/examples/basic/test-dep/transitive-cjs(react@19.1.1) + '@vitejs/test-dep-transitive-use-sync-external-store': + specifier: file:./test-dep/transitive-use-sync-external-store + version: file:packages/plugin-rsc/examples/basic/test-dep/transitive-use-sync-external-store(react@19.1.1) rsc-html-stream: specifier: ^0.0.7 version: 0.0.7 @@ -2594,6 +2600,11 @@ packages: '@vitejs/release-scripts@1.6.0': resolution: {integrity: sha512-XV+w22Fvn+wqDtEkz8nQIJzvmRVSh90c2xvOO7cX9fkX8+39ZJpYRiXDIRJG1JRnF8khm1rHjulid+l+khc7TQ==} + '@vitejs/test-dep-cjs@file:packages/plugin-rsc/examples/basic/test-dep/cjs': + resolution: {directory: packages/plugin-rsc/examples/basic/test-dep/cjs, type: directory} + peerDependencies: + react: '*' + '@vitejs/test-dep-client-in-server2@file:packages/plugin-rsc/examples/basic/test-dep/client-in-server2': resolution: {directory: packages/plugin-rsc/examples/basic/test-dep/client-in-server2, type: directory} peerDependencies: @@ -2629,6 +2640,11 @@ packages: peerDependencies: react: '*' + '@vitejs/test-dep-transitive-use-sync-external-store@file:packages/plugin-rsc/examples/basic/test-dep/transitive-use-sync-external-store': + resolution: {directory: packages/plugin-rsc/examples/basic/test-dep/transitive-use-sync-external-store, type: directory} + peerDependencies: + react: '*' + '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -6401,6 +6417,10 @@ snapshots: transitivePeerDependencies: - conventional-commits-filter + '@vitejs/test-dep-cjs@file:packages/plugin-rsc/examples/basic/test-dep/cjs(react@19.1.1)': + dependencies: + react: 19.1.1 + '@vitejs/test-dep-client-in-server2@file:packages/plugin-rsc/examples/basic/test-dep/client-in-server2(react@19.1.1)': dependencies: react: 19.1.1 @@ -6426,6 +6446,11 @@ snapshots: react: 19.1.1 '@vitejs/test-dep-transitive-cjs@file:packages/plugin-rsc/examples/basic/test-dep/transitive-cjs(react@19.1.1)': + dependencies: + '@vitejs/test-dep-cjs': file:packages/plugin-rsc/examples/basic/test-dep/cjs(react@19.1.1) + react: 19.1.1 + + '@vitejs/test-dep-transitive-use-sync-external-store@file:packages/plugin-rsc/examples/basic/test-dep/transitive-use-sync-external-store(react@19.1.1)': dependencies: react: 19.1.1 use-sync-external-store: 1.5.0(react@19.1.1)