Skip to content

Commit 857f9ac

Browse files
hi-ogawaclaude
andcommitted
refactor(rsc): use emitFile and virtual scan placeholder for env imports
Replace direct rollupOptions.input mutation with: - Virtual scan placeholder for scan builds without configured entries - emitFile in buildStart for emitting discovered entries in real builds This follows the fullstack plugin pattern and provides cleaner separation between scan and real build phases. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent e663662 commit 857f9ac

File tree

2 files changed

+38
-33
lines changed

2 files changed

+38
-33
lines changed

packages/plugin-rsc/src/plugin.ts

Lines changed: 12 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { cjsModuleRunnerPlugin } from './plugins/cjs'
2929
import { vitePluginFindSourceMapURL } from './plugins/find-source-map-url'
3030
import {
3131
ENV_IMPORTS_MANIFEST_NAME,
32+
ENV_IMPORTS_SCAN_VIRTUAL,
3233
vitePluginImportEnvironment,
3334
} from './plugins/import-environment'
3435
import {
@@ -459,44 +460,22 @@ export default function vitePluginRsc(
459460
logStep('[1/5] analyze client references...')
460461
await builder.build(builder.environments.rsc!)
461462

462-
// TODO: let's configuere dummy input e.g. __vite_rsc_xxx: "virtual:..."
463-
// Inject RSC → other environment imports discovered during RSC scan
464-
// This must happen before SSR scan so SSR has input
465-
for (const meta of Object.values(manager.environmentImportMetaMap)) {
466-
const targetEnv = builder.environments[meta.targetEnv]
467-
if (targetEnv) {
468-
const input = (targetEnv.config.build.rollupOptions.input ??=
469-
{}) as Record<string, string>
470-
if (!(meta.entryName in input)) {
471-
input[meta.entryName] = meta.resolvedId
472-
}
473-
}
474-
}
475-
476463
// Check if we need SSR scan (has input or discovered imports targeting SSR)
477-
// TODO: no need of optimization yet. just leave it as future follow TODO.
478-
const ssrInputAfterRsc = builder.environments.ssr!.config.build
479-
.rollupOptions.input as Record<string, string> | undefined
480-
const hasSsrInputAfterRsc =
481-
ssrInputAfterRsc && Object.keys(ssrInputAfterRsc).length > 0
464+
const hasRscToSsrImports = Object.values(
465+
manager.environmentImportMetaMap,
466+
).some((meta) => meta.targetEnv === 'ssr')
482467

483-
if (hasSsrInput || hasSsrInputAfterRsc) {
468+
if (hasSsrInput || hasRscToSsrImports) {
469+
// Use dummy input for scan builds that have no configured input
470+
// Real entries are emitted via emitFile in buildStart
471+
if (!hasSsrInput) {
472+
builder.environments.ssr!.config.build.rollupOptions.input = {
473+
__vite_rsc_env_imports_scan: ENV_IMPORTS_SCAN_VIRTUAL,
474+
}
475+
}
484476
builder.environments.ssr!.config.build.write = false
485477
logStep('[2/5] analyze server references...')
486478
await builder.build(builder.environments.ssr!)
487-
488-
// Inject SSR → other environment imports discovered during SSR scan
489-
// (for bidirectional support)
490-
for (const meta of Object.values(manager.environmentImportMetaMap)) {
491-
const targetEnv = builder.environments[meta.targetEnv]
492-
if (targetEnv) {
493-
const input = (targetEnv.config.build.rollupOptions.input ??=
494-
{}) as Record<string, string>
495-
if (!(meta.entryName in input)) {
496-
input[meta.entryName] = meta.resolvedId
497-
}
498-
}
499-
}
500479
}
501480

502481
manager.isScanBuild = false

packages/plugin-rsc/src/plugins/import-environment.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite'
66
import { evalValue } from './vite-utils'
77

88
export const ENV_IMPORTS_MANIFEST_NAME = '__vite_rsc_env_imports_manifest.js'
9+
export const ENV_IMPORTS_SCAN_VIRTUAL = 'virtual:vite-rsc/env-imports-scan'
910

1011
export type EnvironmentImportMeta = {
1112
resolvedId: string
@@ -18,6 +19,7 @@ export type EnvironmentImportMeta = {
1819
interface PluginManager {
1920
server: ViteDevServer
2021
config: ResolvedConfig
22+
isScanBuild: boolean
2123
environmentImportMetaMap: Record<string, EnvironmentImportMeta>
2224
environmentImportOutputMap: Record<string, string>
2325
}
@@ -35,6 +37,30 @@ export function vitePluginImportEnvironment(manager: PluginManager): Plugin[] {
3537
) {
3638
return { id: './' + ENV_IMPORTS_MANIFEST_NAME, external: true }
3739
}
40+
// Virtual scan placeholder for scan builds without entries
41+
if (source === ENV_IMPORTS_SCAN_VIRTUAL) {
42+
return '\0' + ENV_IMPORTS_SCAN_VIRTUAL
43+
}
44+
},
45+
load(id) {
46+
// Empty module for scan placeholder
47+
if (id === '\0' + ENV_IMPORTS_SCAN_VIRTUAL) {
48+
return 'export {}'
49+
}
50+
},
51+
buildStart() {
52+
// Emit discovered entries in real builds (not scan builds)
53+
if (this.environment.mode !== 'build' || manager.isScanBuild) return
54+
55+
for (const meta of Object.values(manager.environmentImportMetaMap)) {
56+
if (meta.targetEnv === this.environment.name) {
57+
this.emitFile({
58+
type: 'chunk',
59+
id: meta.resolvedId,
60+
name: meta.entryName,
61+
})
62+
}
63+
}
3864
},
3965
transform: {
4066
async handler(code, id) {

0 commit comments

Comments
 (0)