Skip to content

Commit d432264

Browse files
nihgwuhi-ogawaOpenCode
authored
fix(rsc): ignore root resolution errors in rsc:virtual-client-package (#1309)
Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Co-authored-by: OpenCode <noreply@opencode.ai> Co-authored-by: Hiroshi Ogawa <hi.ogawa.zz@gmail.com>
1 parent dfc36c4 commit d432264

2 files changed

Lines changed: 137 additions & 5 deletions

File tree

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { expect, test } from '@playwright/test'
2+
import { setupInlineFixture, useFixture } from './fixture'
3+
import { waitForHydration } from './helper'
4+
5+
test.describe(() => {
6+
const root = 'examples/e2e/temp/client-package-resolution'
7+
8+
test.beforeAll(async () => {
9+
// Dependency and import graph:
10+
//
11+
// app
12+
// |-- @vitejs/test-client-dep@1 (exports only its root)
13+
// `-- @vitejs/test-server-dep
14+
// `-- @vitejs/test-client-dep@2 (exports "./client")
15+
//
16+
// root.tsx -> test-server-dep -> test-client-dep@2/client
17+
// ^ resolves from the actual importer
18+
//
19+
// The plugin also probes v2's "./client" specifier from the app root. That
20+
// finds v1 and throws because v1 does not export "./client", so the plugin
21+
// must keep the fully resolved nested v2 module ID instead.
22+
const json = (value: unknown) => JSON.stringify(value, null, 2)
23+
await setupInlineFixture({
24+
src: 'examples/starter-extra',
25+
dest: root,
26+
files: {
27+
'package.json': {
28+
edit: (source) => {
29+
const packageJson = JSON.parse(source)
30+
packageJson.dependencies = {
31+
...packageJson.dependencies,
32+
'@vitejs/test-client-dep': '1.0.0',
33+
'@vitejs/test-server-dep': '1.0.0',
34+
}
35+
return JSON.stringify(packageJson, null, 2) + '\n'
36+
},
37+
},
38+
'src/root.tsx': /* tsx */ `
39+
import { TestServer } from '@vitejs/test-server-dep/server'
40+
41+
export function Root() {
42+
return (
43+
<html lang="en">
44+
<body>
45+
<TestServer />
46+
</body>
47+
</html>
48+
)
49+
}
50+
`,
51+
'node_modules/@vitejs/test-server-dep/package.json': json({
52+
name: '@vitejs/test-server-dep',
53+
version: '1.0.0',
54+
type: 'module',
55+
exports: {
56+
'./server': './server.js',
57+
},
58+
dependencies: {
59+
'@vitejs/test-client-dep': '2.0.0',
60+
},
61+
peerDependencies: {
62+
react: '*',
63+
},
64+
}),
65+
'node_modules/@vitejs/test-server-dep/server.js': /* js */ `
66+
import { TestClient } from '@vitejs/test-client-dep/client'
67+
import React from 'react'
68+
69+
export function TestServer() {
70+
return React.createElement(TestClient)
71+
}
72+
`,
73+
'node_modules/@vitejs/test-client-dep/package.json': json({
74+
name: '@vitejs/test-client-dep',
75+
version: '1.0.0',
76+
type: 'module',
77+
exports: './index.js',
78+
}),
79+
'node_modules/@vitejs/test-client-dep/index.js': /* js */ ``,
80+
'node_modules/@vitejs/test-server-dep/node_modules/@vitejs/test-client-dep/package.json':
81+
json({
82+
name: '@vitejs/test-client-dep',
83+
version: '2.0.0',
84+
type: 'module',
85+
exports: {
86+
'./client': './client.js',
87+
},
88+
peerDependencies: {
89+
react: '*',
90+
},
91+
}),
92+
'node_modules/@vitejs/test-server-dep/node_modules/@vitejs/test-client-dep/client.js': /* js */ `
93+
'use client'
94+
95+
import React from 'react'
96+
97+
export function TestClient() {
98+
const [count, setCount] = React.useState(0)
99+
return React.createElement(
100+
'button',
101+
{ onClick: () => setCount((value) => value + 1) },
102+
'Nested client: ' + count,
103+
)
104+
}
105+
`,
106+
},
107+
})
108+
})
109+
110+
for (const mode of ['dev', 'build'] as const) {
111+
test.describe(mode, () => {
112+
const f = useFixture({ root, mode })
113+
114+
test('uses the client package resolved from its importer', async ({
115+
page,
116+
}) => {
117+
await page.goto(f.url())
118+
await waitForHydration(page)
119+
await page.getByRole('button', { name: 'Nested client: 0' }).click()
120+
await expect(
121+
page.getByRole('button', { name: 'Nested client: 1' }),
122+
).toBeVisible()
123+
})
124+
})
125+
}
126+
})

packages/plugin-rsc/src/plugin.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1731,11 +1731,17 @@ function vitePluginUseClient(
17311731
this.environment.config.root,
17321732
'index.html',
17331733
)
1734-
const resolvedAtRoot = await this.resolve(
1735-
source,
1736-
rootImporter,
1737-
options,
1738-
)
1734+
let resolvedAtRoot
1735+
try {
1736+
resolvedAtRoot = await this.resolve(
1737+
source,
1738+
rootImporter,
1739+
options,
1740+
)
1741+
} catch {
1742+
// A different version at the root may not export this subpath.
1743+
return
1744+
}
17391745
if (!resolvedAtRoot || resolvedAtRoot.id !== resolved.id) {
17401746
return
17411747
}

0 commit comments

Comments
 (0)