Skip to content

Commit 19766f0

Browse files
Merge remote-tracking branch 'upstream/main' into chore-rsc-nightly
2 parents 2492e38 + a7d32a0 commit 19766f0

File tree

25 files changed

+248
-233
lines changed

25 files changed

+248
-233
lines changed

.github/workflows/ci-rsc.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
name: ci-rsc
2+
3+
permissions: {}
4+
25
on:
36
push:
47
branches:

.npmrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ hoist-pattern[]=eslint-import-resolver-*
55
strict-peer-dependencies=false
66
shell-emulator=true
77
auto-install-peers=false
8+
link-workspace-packages=true
9+
prefer-workspace-packages=true

package.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,6 @@
7272
]
7373
},
7474
"pnpm": {
75-
"overrides": {
76-
"@vitejs/plugin-rsc": "workspace:*"
77-
},
7875
"packageExtensions": {
7976
"generouted": {
8077
"peerDependencies": {

packages/plugin-rsc/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ export default defineConfig({
9898
// this environment is responsible for:
9999
// - RSC stream deserialization (RSC stream -> React VDOM)
100100
// - traditional SSR (React VDOM -> HTML string/stream)
101-
// (NOTE: as it can be seen in the above diagram. SSR is technically an optional mechanism.)
102101
ssr: {
103102
build: {
104103
rollupOptions: {

packages/plugin-rsc/e2e/basic.test.ts

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,33 @@ test.describe('dev-default', () => {
1212
defineTest(f)
1313
})
1414

15+
test.describe('dev-initial', () => {
16+
const f = useFixture({ root: 'examples/basic', mode: 'dev' })
17+
18+
// verify css is collected properly on server startup (i.e. empty module graph)
19+
testNoJs('style', async ({ page }) => {
20+
await page.goto(f.url('./'))
21+
await expect(page.locator('.test-style-client')).toHaveCSS(
22+
'color',
23+
'rgb(255, 165, 0)',
24+
)
25+
await expect(page.locator('.test-style-server')).toHaveCSS(
26+
'color',
27+
'rgb(255, 165, 0)',
28+
)
29+
await expect(page.locator('.test-tw-client')).toHaveCSS(
30+
'color',
31+
// blue-500
32+
'rgb(0, 0, 255)',
33+
)
34+
await expect(page.locator('.test-tw-server')).toHaveCSS(
35+
'color',
36+
// red-500
37+
'rgb(255, 0, 0)',
38+
)
39+
})
40+
})
41+
1542
test.describe('build-default', () => {
1643
const f = useFixture({ root: 'examples/basic', mode: 'build' })
1744
defineTest(f)
@@ -43,6 +70,39 @@ test.describe('build-base', () => {
4370
defineTest(f)
4471
})
4572

73+
test.describe('dev-react-compiler', () => {
74+
const f = useFixture({
75+
root: 'examples/basic',
76+
mode: 'dev',
77+
cliOptions: {
78+
env: {
79+
TEST_REACT_COMPILER: 'true',
80+
},
81+
},
82+
})
83+
defineTest(f)
84+
85+
test('verify react compiler', async ({ page }) => {
86+
await page.goto(f.url())
87+
await waitForHydration(page)
88+
const res = await page.request.get(f.url('src/routes/client.tsx'))
89+
expect(await res.text()).toContain('react.memo_cache_sentinel')
90+
})
91+
})
92+
93+
test.describe('build-react-compiler', () => {
94+
const f = useFixture({
95+
root: 'examples/basic',
96+
mode: 'build',
97+
cliOptions: {
98+
env: {
99+
TEST_REACT_COMPILER: 'true',
100+
},
101+
},
102+
})
103+
defineTest(f)
104+
})
105+
46106
test.describe(() => {
47107
// disabled by default
48108
if (!process.env.TEST_ISOLATED) return
@@ -623,15 +683,6 @@ function defineTest(f: Fixture) {
623683
'rgb(255, 0, 0)',
624684
)
625685
})
626-
627-
testNoJs('no FOUC after server restart @nojs', async ({ page }) => {
628-
const res = await page.request.get(f.url('/__test_restart'))
629-
expect(await res.text()).toBe('ok')
630-
await new Promise((r) => setTimeout(r, 100))
631-
await page.goto(f.url('./'))
632-
await testCss(page)
633-
await testTailwind(page)
634-
})
635686
})
636687

637688
test('temporary references @js', async ({ page }) => {
@@ -952,4 +1003,17 @@ function defineTest(f: Fixture) {
9521003
await waitForHydration(page)
9531004
expect(errors).toEqual([])
9541005
})
1006+
1007+
test('browser only', async ({ page, browser }) => {
1008+
await page.goto(f.url())
1009+
await expect(page.getByTestId('test-browser-only')).toHaveText(
1010+
'test-browser-only: true',
1011+
)
1012+
1013+
const pageNoJs = await browser.newPage({ javaScriptEnabled: false })
1014+
await pageNoJs.goto(f.url())
1015+
await expect(pageNoJs.getByTestId('test-browser-only')).toHaveText(
1016+
'test-browser-only: loading...',
1017+
)
1018+
})
9551019
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# rsc basic
22

3-
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/hi-ogawa/vite-plugins/tree/main/packages/rsc/examples/basic)
3+
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vitejs/vite-plugin-react/tree/main/packages/plugin-rsc/examples/basic)
44

55
https://vite-rsc-basic.hiro18181.workers.dev
66

77
```sh
8-
npx giget gh:hi-ogawa/vite-plugins/packages/rsc/examples/basic my-app
8+
npx giget gh:vitejs/vite-plugin-react/packages/plugin-rsc/examples/basic my-app
99
```

packages/plugin-rsc/examples/basic/package.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,22 @@
1313
},
1414
"dependencies": {
1515
"@vitejs/plugin-rsc": "latest",
16-
"react": "latest",
17-
"react-dom": "latest"
16+
"react": "^19.1.0",
17+
"react-dom": "^19.1.0"
1818
},
1919
"devDependencies": {
2020
"@tailwindcss/vite": "^4.1.4",
21-
"@types/react": "latest",
22-
"@types/react-dom": "latest",
21+
"@types/react": "^19.1.8",
22+
"@types/react-dom": "^19.1.6",
2323
"@vitejs/plugin-react": "latest",
2424
"@vitejs/test-dep-client-in-server": "file:./test-dep/client-in-server",
2525
"@vitejs/test-dep-client-in-server2": "file:./test-dep/client-in-server2",
2626
"@vitejs/test-dep-server-in-client": "file:./test-dep/server-in-client",
2727
"@vitejs/test-dep-server-in-server": "file:./test-dep/server-in-server",
28+
"babel-plugin-react-compiler": "19.1.0-rc.2",
2829
"tailwindcss": "^4.1.4",
2930
"vite": "^7.0.2",
30-
"vite-plugin-inspect": "^11.2.0"
31+
"vite-plugin-inspect": "^11.2.0",
32+
"wrangler": "^4.22.0"
3133
}
3234
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function BrowserDep() {
2+
return <>{String(!!window)}</>
3+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
'use client'
2+
3+
import * as React from 'react'
4+
5+
const BrowserDep = (
6+
import.meta.env.SSR ? undefined : React.lazy(() => import('./browser-dep'))
7+
)!
8+
9+
export function TestBrowserOnly() {
10+
return (
11+
<div data-testid="test-browser-only">
12+
test-browser-only:{' '}
13+
<BrowserOnly fallback={<>loading...</>}>
14+
<BrowserDep />
15+
</BrowserOnly>
16+
</div>
17+
)
18+
}
19+
20+
function BrowserOnly(props: React.SuspenseProps) {
21+
const hydrated = useHydrated()
22+
if (!hydrated) {
23+
return props.fallback
24+
}
25+
return <React.Suspense {...props} />
26+
}
27+
28+
const noopStore = () => () => {}
29+
30+
const useHydrated = () =>
31+
React.useSyncExternalStore(
32+
noopStore,
33+
() => true,
34+
() => false,
35+
)
36+
37+
/*
38+
If we were to implement this whole logic via hypothetical `browserOnly` helper with transform:
39+
40+
======= input ======
41+
42+
const SomeDep = browserOnly(() => import('./some-dep'))
43+
44+
======= output ======
45+
46+
const __TmpLazy = import.meta.env.SSR ? undefined : React.lazy(() => import('./some-dep'}));
47+
48+
const SomeDep = ({ browserOnlyFallback, ...props }) => {
49+
const hydrated = useHydrated()
50+
if (!hydrated) {
51+
return browserOnlyFallback
52+
}
53+
return (
54+
<React.Suspense fallback={browserOnlyFallback}>
55+
<__TmpLazy {...props} />
56+
</React.Suspense>
57+
)
58+
}
59+
60+
=== helper types ===
61+
62+
declare function browserOnly<T>(fn: () => Promise<{ default: React.ComponentType<T> }>):
63+
React.ComponentType<T & { browserOnlyFallback?: React.ReactNode }>
64+
65+
*/

packages/plugin-rsc/examples/basic/src/routes/root.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { TestTailwindServer } from './tailwind/server'
2828
import { TestTemporaryReference } from './temporary-reference/client'
2929
import { TestUseCache } from './use-cache/server'
3030
import { TestHydrationMismatch } from './hydration-mismatch/server'
31+
import { TestBrowserOnly } from './browser-only/client'
3132

3233
export function Root(props: { url: URL }) {
3334
return (
@@ -67,6 +68,7 @@ export function Root(props: { url: URL }) {
6768
<TestServerInClient />
6869
<TestActionStateServer />
6970
<TestModuleInvalidationServer />
71+
<TestBrowserOnly />
7072
<TestUseCache />
7173
</body>
7274
</html>

0 commit comments

Comments
 (0)