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)