Skip to content

Commit 83a5741

Browse files
authored
feat(rsc): add useBuildAppHook option to switch plugin.buildApp or builder.buildApp (#653)
1 parent ac0cac7 commit 83a5741

File tree

2 files changed

+166
-47
lines changed

2 files changed

+166
-47
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { test, expect } from '@playwright/test'
2+
import { setupInlineFixture, useFixture } from './fixture'
3+
import { x } from 'tinyexec'
4+
import { waitForHydration } from './helper'
5+
6+
test.describe('buildApp hook', () => {
7+
const root = 'examples/e2e/temp/buildApp'
8+
test.beforeAll(async () => {
9+
await setupInlineFixture({
10+
src: 'examples/starter',
11+
dest: root,
12+
files: {
13+
'vite.config.base.ts': { cp: 'vite.config.ts' },
14+
'vite.config.ts': /* js */ `
15+
import rsc from '@vitejs/plugin-rsc'
16+
import react from '@vitejs/plugin-react'
17+
import { defineConfig, mergeConfig } from 'vite'
18+
import baseConfig from './vite.config.base.ts'
19+
20+
delete baseConfig.plugins
21+
22+
const overrideConfig = defineConfig({
23+
plugins: [
24+
{
25+
name: 'buildApp-prafter',
26+
buildApp: async () => {
27+
console.log('++++ buildApp:before ++++')
28+
},
29+
},
30+
rsc({
31+
useBuildAppHook: process.env.TEST_USE_BUILD_APP_HOOK === 'true',
32+
}),
33+
{
34+
name: 'buildApp-after',
35+
buildApp: async () => {
36+
console.log('++++ buildApp:after ++++')
37+
},
38+
},
39+
react(),
40+
],
41+
})
42+
43+
export default mergeConfig(baseConfig, overrideConfig)
44+
`,
45+
},
46+
})
47+
})
48+
49+
function verifyMatchOrder(s: string, matches: string[]) {
50+
const found = matches
51+
.map((match) => ({ match, index: s.indexOf(match) }))
52+
.filter((item) => item.index !== -1)
53+
.sort((a, b) => a.index - b.index)
54+
.map((item) => item.match)
55+
expect(found).toEqual(matches)
56+
}
57+
58+
test('useBuildAppHook: true', async () => {
59+
const result = await x('pnpm', ['build'], {
60+
nodeOptions: {
61+
cwd: root,
62+
env: {
63+
TEST_USE_BUILD_APP_HOOK: 'true',
64+
},
65+
},
66+
throwOnError: true,
67+
})
68+
verifyMatchOrder(result.stdout, [
69+
'++++ buildApp:before ++++',
70+
'building for production...',
71+
'++++ buildApp:after ++++',
72+
])
73+
expect(result.exitCode).toBe(0)
74+
})
75+
76+
test('useBuildAppHook: false', async () => {
77+
const result = await x('pnpm', ['build'], {
78+
nodeOptions: {
79+
cwd: root,
80+
env: {
81+
TEST_USE_BUILD_APP_HOOK: 'false',
82+
},
83+
},
84+
throwOnError: true,
85+
})
86+
verifyMatchOrder(result.stdout, [
87+
'++++ buildApp:before ++++',
88+
'++++ buildApp:after ++++',
89+
'building for production...',
90+
])
91+
expect(result.exitCode).toBe(0)
92+
})
93+
94+
test.describe('build', () => {
95+
const f = useFixture({
96+
root,
97+
mode: 'build',
98+
cliOptions: {
99+
env: {
100+
TEST_USE_BUILD_APP_HOOK: 'true',
101+
},
102+
},
103+
})
104+
105+
test('basic', async ({ page }) => {
106+
await page.goto(f.url())
107+
await waitForHydration(page)
108+
})
109+
})
110+
})

packages/plugin-rsc/src/plugin.ts

Lines changed: 56 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { createRequestListener } from '@mjackson/node-fetch-server'
88
import * as esModuleLexer from 'es-module-lexer'
99
import MagicString from 'magic-string'
1010
import {
11+
type BuilderOptions,
1112
type DevEnvironment,
1213
type EnvironmentModuleNode,
1314
type Plugin,
@@ -123,11 +124,64 @@ export type RscPluginOptions = {
123124
* @default true
124125
*/
125126
validateImports?: boolean
127+
128+
/**
129+
* use `Plugin.buildApp` hook (introduced on Vite 7) instead of `builder.buildApp` configuration
130+
* for better composability with other plugins.
131+
* @default false
132+
*/
133+
useBuildAppHook?: boolean
126134
}
127135

128136
export default function vitePluginRsc(
129137
rscPluginOptions: RscPluginOptions = {},
130138
): Plugin[] {
139+
const buildApp: NonNullable<BuilderOptions['buildApp']> = async (builder) => {
140+
// no-ssr case
141+
// rsc -> client -> rsc -> client
142+
if (!builder.environments.ssr?.config.build.rollupOptions.input) {
143+
isScanBuild = true
144+
builder.environments.rsc!.config.build.write = false
145+
builder.environments.client!.config.build.write = false
146+
await builder.build(builder.environments.rsc!)
147+
await builder.build(builder.environments.client!)
148+
isScanBuild = false
149+
builder.environments.rsc!.config.build.write = true
150+
builder.environments.client!.config.build.write = true
151+
await builder.build(builder.environments.rsc!)
152+
// sort for stable build
153+
clientReferenceMetaMap = sortObject(clientReferenceMetaMap)
154+
serverResourcesMetaMap = sortObject(serverResourcesMetaMap)
155+
await builder.build(builder.environments.client!)
156+
157+
const assetsManifestCode = `export default ${serializeValueWithRuntime(
158+
buildAssetsManifest,
159+
)}`
160+
const manifestPath = path.join(
161+
builder.environments!.rsc!.config.build!.outDir!,
162+
BUILD_ASSETS_MANIFEST_NAME,
163+
)
164+
fs.writeFileSync(manifestPath, assetsManifestCode)
165+
return
166+
}
167+
168+
// rsc -> ssr -> rsc -> client -> ssr
169+
isScanBuild = true
170+
builder.environments.rsc!.config.build.write = false
171+
builder.environments.ssr!.config.build.write = false
172+
await builder.build(builder.environments.rsc!)
173+
await builder.build(builder.environments.ssr!)
174+
isScanBuild = false
175+
builder.environments.rsc!.config.build.write = true
176+
builder.environments.ssr!.config.build.write = true
177+
await builder.build(builder.environments.rsc!)
178+
// sort for stable build
179+
clientReferenceMetaMap = sortObject(clientReferenceMetaMap)
180+
serverResourcesMetaMap = sortObject(serverResourcesMetaMap)
181+
await builder.build(builder.environments.client!)
182+
await builder.build(builder.environments.ssr!)
183+
}
184+
131185
return [
132186
{
133187
name: 'rsc',
@@ -233,58 +287,14 @@ export default function vitePluginRsc(
233287
},
234288
},
235289
},
236-
// TODO: use buildApp hook on v7?
237290
builder: {
238291
sharedPlugins: true,
239292
sharedConfigBuild: true,
240-
async buildApp(builder) {
241-
// no-ssr case
242-
// rsc -> client -> rsc -> client
243-
if (!builder.environments.ssr?.config.build.rollupOptions.input) {
244-
isScanBuild = true
245-
builder.environments.rsc!.config.build.write = false
246-
builder.environments.client!.config.build.write = false
247-
await builder.build(builder.environments.rsc!)
248-
await builder.build(builder.environments.client!)
249-
isScanBuild = false
250-
builder.environments.rsc!.config.build.write = true
251-
builder.environments.client!.config.build.write = true
252-
await builder.build(builder.environments.rsc!)
253-
// sort for stable build
254-
clientReferenceMetaMap = sortObject(clientReferenceMetaMap)
255-
serverResourcesMetaMap = sortObject(serverResourcesMetaMap)
256-
await builder.build(builder.environments.client!)
257-
258-
const assetsManifestCode = `export default ${serializeValueWithRuntime(
259-
buildAssetsManifest,
260-
)}`
261-
const manifestPath = path.join(
262-
builder.environments!.rsc!.config.build!.outDir!,
263-
BUILD_ASSETS_MANIFEST_NAME,
264-
)
265-
fs.writeFileSync(manifestPath, assetsManifestCode)
266-
return
267-
}
268-
269-
// rsc -> ssr -> rsc -> client -> ssr
270-
isScanBuild = true
271-
builder.environments.rsc!.config.build.write = false
272-
builder.environments.ssr!.config.build.write = false
273-
await builder.build(builder.environments.rsc!)
274-
await builder.build(builder.environments.ssr!)
275-
isScanBuild = false
276-
builder.environments.rsc!.config.build.write = true
277-
builder.environments.ssr!.config.build.write = true
278-
await builder.build(builder.environments.rsc!)
279-
// sort for stable build
280-
clientReferenceMetaMap = sortObject(clientReferenceMetaMap)
281-
serverResourcesMetaMap = sortObject(serverResourcesMetaMap)
282-
await builder.build(builder.environments.client!)
283-
await builder.build(builder.environments.ssr!)
284-
},
293+
buildApp: rscPluginOptions.useBuildAppHook ? undefined : buildApp,
285294
},
286295
}
287296
},
297+
buildApp: rscPluginOptions.useBuildAppHook ? buildApp : undefined,
288298
configResolved(config_) {
289299
config = config_
290300
},
@@ -841,7 +851,6 @@ globalThis.AsyncLocalStorage = __viteRscAyncHooks.AsyncLocalStorage;
841851
detectNonOptimizedCjsPlugin(),
842852
]
843853
}
844-
845854
function detectNonOptimizedCjsPlugin(): Plugin {
846855
return {
847856
name: 'rsc:detect-non-optimized-cjs',

0 commit comments

Comments
 (0)