Skip to content

Commit b81bf6a

Browse files
authored
feat(rsc): support browser mode build (#801)
1 parent 7d7fab8 commit b81bf6a

File tree

10 files changed

+289
-85
lines changed

10 files changed

+289
-85
lines changed

packages/plugin-rsc/e2e/browser-mode.test.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,23 @@ import { expect, test, type Page } from '@playwright/test'
22
import { useFixture } from './fixture'
33
import { defineStarterTest } from './starter'
44

5-
test.describe('dev-browser-mode', () => {
6-
// Webkit fails by
7-
// > TypeError: ReadableByteStreamController is not implemented
8-
test.skip(({ browserName }) => browserName === 'webkit')
5+
// Webkit fails by
6+
// > TypeError: ReadableByteStreamController is not implemented
7+
test.skip(({ browserName }) => browserName === 'webkit')
98

9+
test.describe('dev-browser-mode', () => {
1010
const f = useFixture({ root: 'examples/browser-mode', mode: 'dev' })
1111
defineStarterTest(f, 'browser-mode')
12+
defineBrowserModeTest(f)
13+
})
1214

15+
test.describe('build-browser-mode', () => {
16+
const f = useFixture({ root: 'examples/browser-mode', mode: 'build' })
17+
defineStarterTest(f, 'browser-mode')
18+
defineBrowserModeTest(f)
19+
})
20+
21+
function defineBrowserModeTest(f: ReturnType<typeof useFixture>) {
1322
// action-bind tests copied from basic.test.ts
1423

1524
test('action bind simple', async ({ page }) => {
@@ -71,4 +80,4 @@ test.describe('dev-browser-mode', () => {
7180
.getByRole('button', { name: 'test-server-action-bind-reset' })
7281
.click()
7382
}
74-
})
83+
}

packages/plugin-rsc/examples/browser-mode/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
"type": "module",
77
"scripts": {
88
"dev": "vite",
9-
"build": "false && vite build",
10-
"preview": "false && vite preview"
9+
"build": "vite build",
10+
"preview": "vite preview"
1111
},
1212
"dependencies": {
1313
"react": "^19.1.1",

packages/plugin-rsc/examples/browser-mode/src/framework/entry.browser.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,24 @@ import {
88
encodeReply,
99
} from '@vitejs/plugin-rsc/react/browser'
1010
import type { RscPayload } from './entry.rsc'
11+
import buildClientReferences from 'virtual:vite-rsc-browser-mode/build-client-references'
1112

1213
let fetchServer: typeof import('./entry.rsc').fetchServer
1314

1415
export function initialize(options: { fetchServer: typeof fetchServer }) {
1516
fetchServer = options.fetchServer
1617
setRequireModule({
17-
load: (id) => import(/* @vite-ignore */ id),
18+
load: (id) => {
19+
if (import.meta.env.__vite_rsc_build__) {
20+
const import_ = buildClientReferences[id]
21+
if (!import_) {
22+
throw new Error(`invalid client reference: ${id}`)
23+
}
24+
return import_()
25+
} else {
26+
return import(/* @vite-ignore */ id)
27+
}
28+
},
1829
})
1930
}
2031

packages/plugin-rsc/examples/browser-mode/src/framework/entry.rsc.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import type React from 'react'
1111
import { Root } from '../root'
1212
import type { ReactFormState } from 'react-dom/client'
13+
import buildServerReferences from 'virtual:vite-rsc-browser-mode/build-server-references'
1314

1415
export type RscPayload = {
1516
root: React.ReactNode
@@ -20,7 +21,19 @@ export type RscPayload = {
2021
declare let __vite_rsc_raw_import__: (id: string) => Promise<unknown>
2122

2223
export function initialize() {
23-
setRequireModule({ load: (id) => __vite_rsc_raw_import__(id) })
24+
setRequireModule({
25+
load: (id) => {
26+
if (import.meta.env.__vite_rsc_build__) {
27+
const import_ = buildServerReferences[id]
28+
if (!import_) {
29+
throw new Error(`invalid server reference: ${id}`)
30+
}
31+
return import_()
32+
} else {
33+
return __vite_rsc_raw_import__(/* @vite-ignore */ id)
34+
}
35+
},
36+
})
2437
}
2538

2639
export async function fetchServer(request: Request): Promise<Response> {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { ESModulesEvaluator, ModuleRunner } from 'vite/module-runner'
2+
3+
export default async function loadClient() {
4+
const runner = new ModuleRunner(
5+
{
6+
sourcemapInterceptor: false,
7+
transport: {
8+
invoke: async (payload) => {
9+
const response = await fetch(
10+
'/@vite/invoke-react-client?' +
11+
new URLSearchParams({
12+
data: JSON.stringify(payload),
13+
}),
14+
)
15+
return response.json()
16+
},
17+
},
18+
hmr: false,
19+
},
20+
new ESModulesEvaluator(),
21+
)
22+
return await runner.import<typeof import('./entry.browser')>(
23+
'/src/framework/entry.browser.tsx',
24+
)
25+
}
Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,11 @@
1-
import { ESModulesEvaluator, ModuleRunner } from 'vite/module-runner'
21
import * as server from './entry.rsc'
2+
import loadClient from 'virtual:vite-rsc-browser-mode/load-client'
33

44
async function main() {
5-
const client = await importClient()
5+
const client = await loadClient()
66
server.initialize()
77
client.initialize({ fetchServer: server.fetchServer })
88
await client.main()
99
}
1010

11-
async function importClient() {
12-
const runner = new ModuleRunner(
13-
{
14-
sourcemapInterceptor: false,
15-
transport: {
16-
invoke: async (payload) => {
17-
const response = await fetch(
18-
'/@vite/invoke-react-client?' +
19-
new URLSearchParams({
20-
data: JSON.stringify(payload),
21-
}),
22-
)
23-
return response.json()
24-
},
25-
},
26-
hmr: false,
27-
},
28-
new ESModulesEvaluator(),
29-
)
30-
return await runner.import<typeof import('./entry.browser')>(
31-
'/src/framework/entry.browser.tsx',
32-
)
33-
}
34-
3511
main()
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
declare module 'virtual:vite-rsc-browser-mode/build-client-references' {
2+
const default_: Record<string, () => Promise<any>>
3+
export default default_
4+
}
5+
6+
declare module 'virtual:vite-rsc-browser-mode/build-server-references' {
7+
const default_: Record<string, () => Promise<any>>
8+
export default default_
9+
}
10+
11+
declare module 'virtual:vite-rsc-browser-mode/load-client' {
12+
const default_: () => Promise<typeof import('./entry.browser')>
13+
export default default_
14+
}

0 commit comments

Comments
 (0)