@@ -246,12 +246,205 @@ import.meta.viteRsc.importAsset('./entry.browser', { asset: 'client' }) // retur
246246
247247This avoids breaking changes and keeps each method's return type clear.
248248
249+ ## Manifest-Based Approach (Preferred)
250+
251+ The initial implementation hardcoded output filenames (` ${entryName}.js ` ), which breaks with custom ` entryFileNames ` config. A manifest approach solves this.
252+
253+ ### Problem with Hardcoded Filenames
254+
255+ ``` ts
256+ // renderChunk - current implementation
257+ const targetFileName = ` ${entryName }.js ` // ❌ Breaks with custom entryFileNames
258+ ```
259+
260+ Build order means RSC builds before SSR:
261+
262+ ```
263+ rsc (real) → client → ssr (real)
264+ ↑ ↑
265+ RSC renderChunk SSR output filenames known
266+ ```
267+
268+ ### Solution: Manifest with Static Imports
269+
270+ Generate a manifest file with ** static import functions** that bundlers can analyze:
271+
272+ ``` ts
273+ // __vite_rsc_env_imports_manifest.js (generated in buildApp after SSR build)
274+ export default {
275+ ' /abs/path/to/entry.ssr.tsx' : () => import (' ../ssr/entry.ssr.js' ),
276+ }
277+ ```
278+
279+ Transform emits manifest lookup:
280+
281+ ``` ts
282+ // Original:
283+ await import .meta .viteRsc
284+ .import (' ./entry.ssr' , { environment: ' ssr' })
285+ (
286+ // Build transform to:
287+ await import (' ./__vite_rsc_env_imports_manifest.js' ),
288+ )
289+ .default [' /abs/path/to/entry.ssr.tsx' ]()
290+ ```
291+
292+ ### Why Static Import Functions?
293+
294+ Dynamic imports like ` import(manifest['key']) ` break post-bundling and static analysis. By using functions with static import strings, bundlers can:
295+
296+ - Analyze the dependency graph
297+ - Apply optimizations (tree-shaking, code-splitting)
298+ - Work correctly with further bundling
299+
300+ ### Implementation Changes
301+
302+ ** 1. Transform (in RSC):**
303+
304+ - Dev: unchanged (` globalThis.__VITE_ENVIRONMENT_RUNNER_IMPORT__ ` )
305+ - Build: emit manifest lookup instead of markers
306+
307+ ``` ts
308+ // Build mode transform
309+ const manifestImport = ` await import('./__vite_rsc_env_imports_manifest.js') `
310+ replacement = ` (${manifestImport }).default[${JSON .stringify (resolvedId )}]() `
311+ ```
312+
313+ ** 2. Remove renderChunk:**
314+
315+ - No longer needed for this feature (markers eliminated)
316+
317+ ** 3. SSR generateBundle:**
318+
319+ - Track output filenames for discovered entries
320+
321+ ``` ts
322+ generateBundle (options , bundle ) {
323+ for (const [fileName, chunk] of Object .entries (bundle )) {
324+ if (chunk .type === ' chunk' && chunk .isEntry ) {
325+ const resolvedId = chunk .facadeModuleId
326+ if (resolvedId && resolvedId in manager .environmentImportMetaMap ) {
327+ manager .environmentImportOutputMap [resolvedId ] = fileName
328+ }
329+ }
330+ }
331+ }
332+ ```
333+
334+ ** 4. buildApp - writeEnvironmentImportsManifest:**
335+
336+ - Generate manifest after SSR build completes
337+ - Calculate relative paths from manifest location to target chunks
338+
339+ ``` ts
340+ function writeEnvironmentImportsManifest() {
341+ const rscOutDir = config .environments .rsc .build .outDir
342+ const manifestPath = path .join (
343+ rscOutDir ,
344+ ' __vite_rsc_env_imports_manifest.js' ,
345+ )
346+
347+ let code = ' export default {\n '
348+ for (const [resolvedId, meta] of Object .entries (
349+ manager .environmentImportMetaMap ,
350+ )) {
351+ const outputFileName = manager .environmentImportOutputMap [resolvedId ]
352+ const targetOutDir = config .environments [meta .targetEnv ].build .outDir
353+ const relativePath = normalizeRelativePath (
354+ path .relative (rscOutDir , path .join (targetOutDir , outputFileName )),
355+ )
356+ code += ` ${JSON .stringify (resolvedId )}: () => import(${JSON .stringify (relativePath )}),\n `
357+ }
358+ code += ' }\n '
359+
360+ fs .writeFileSync (manifestPath , code )
361+ }
362+ ```
363+
364+ ### Bidirectional Support
365+
366+ Both directions are supported:
367+
368+ - ** RSC → SSR** : ` import('./entry.ssr', { environment: 'ssr' }) ` in RSC code
369+ - ** SSR → RSC** : ` import('./entry.rsc', { environment: 'rsc' }) ` in SSR code
370+
371+ This is similar to "use client" / "use server" discovery - each scan phase can discover entries for other environments.
372+
373+ ** Key insight** : Entry injection must happen AFTER both scan phases but BEFORE real builds.
374+
375+ ### Data Flow
376+
377+ ```
378+ ┌─────────────────────────────────────────────────────────────────────────┐
379+ │ RSC Scan │
380+ │ transform: discover viteRsc.import → populate environmentImportMetaMap│
381+ │ (discovers RSC → SSR imports) │
382+ └─────────────────────────────────────────────────────────────────────────┘
383+ ↓
384+ ┌─────────────────────────────────────────────────────────────────────────┐
385+ │ SSR Scan │
386+ │ transform: discover viteRsc.import → populate environmentImportMetaMap│
387+ │ (discovers SSR → RSC imports) │
388+ └─────────────────────────────────────────────────────────────────────────┘
389+ ↓
390+ ┌─────────────────────────────────────────────────────────────────────────┐
391+ │ Inject Discovered Entries (in buildApp, after both scans) │
392+ │ for each meta in environmentImportMetaMap: │
393+ │ inject meta.resolvedId into target environment's rollupOptions.input│
394+ └─────────────────────────────────────────────────────────────────────────┘
395+ ↓
396+ ┌─────────────────────────────────────────────────────────────────────────┐
397+ │ RSC Real Build │
398+ │ transform: emit manifest lookup code │
399+ │ generateBundle: track resolvedId → outputFileName (for SSR → RSC) │
400+ └─────────────────────────────────────────────────────────────────────────┘
401+ ↓
402+ ┌─────────────────────────────────────────────────────────────────────────┐
403+ │ Client Build │
404+ └─────────────────────────────────────────────────────────────────────────┘
405+ ↓
406+ ┌─────────────────────────────────────────────────────────────────────────┐
407+ │ SSR Real Build │
408+ │ transform: emit manifest lookup code │
409+ │ generateBundle: track resolvedId → outputFileName (for RSC → SSR) │
410+ └─────────────────────────────────────────────────────────────────────────┘
411+ ↓
412+ ┌─────────────────────────────────────────────────────────────────────────┐
413+ │ buildApp Post-Build │
414+ │ writeEnvironmentImportsManifest: │
415+ │ - Write manifest to RSC output (for RSC → SSR imports) │
416+ │ - Write manifest to SSR output (for SSR → RSC imports) │
417+ └─────────────────────────────────────────────────────────────────────────┘
418+ ```
419+
420+ ### Manifest Per Source Environment
421+
422+ Each source environment gets its own manifest with imports pointing to target environments:
423+
424+ ``` ts
425+ // dist/rsc/__vite_rsc_env_imports_manifest.js (for RSC → SSR)
426+ export default {
427+ ' /abs/path/entry.ssr.tsx' : () => import (' ../ssr/entry.ssr.js' ),
428+ }
429+
430+ // dist/ssr/__vite_rsc_env_imports_manifest.js (for SSR → RSC)
431+ export default {
432+ ' /abs/path/entry.rsc.tsx' : () => import (' ../rsc/index.js' ),
433+ }
434+ ```
435+
249436## Implementation Steps
250437
251- 1 . [ ] Add ` environmentImportMetaMap ` to RscPluginManager
252- 2 . [ ] Clean up ` import.ts ` : add imports, parameterize with manager
253- 3 . [ ] Implement transform to discover and track imports
254- 4 . [ ] Add ` buildStart ` hook to emit discovered entries in target environment
255- 5 . [ ] Implement renderChunk to resolve markers
256- 6 . [ ] Test with basic example
257- 7 . [ ] Update documentation
438+ 1 . [x] Add ` environmentImportMetaMap ` to RscPluginManager
439+ 2 . [x] Clean up ` import-environment.ts ` : add imports, parameterize with manager
440+ 3 . [x] Implement transform to discover and track imports
441+ 4 . [x] Inject discovered entries into target environment's input
442+ 5 . [ ] ** Fix** : Move entry injection to AFTER both scans (currently after RSC scan only)
443+ 6 . [ ] Update transform to emit manifest lookup (build mode)
444+ 7 . [ ] Remove renderChunk marker replacement
445+ 8 . [ ] Add ` environmentImportOutputMap ` to track resolvedId → outputFileName
446+ 9 . [ ] Add generateBundle hook to populate output map (in both RSC and SSR)
447+ 10 . [ ] Add ` writeEnvironmentImportsManifest ` in buildApp (per source environment)
448+ 11 . [ ] Test with basic example (RSC → SSR)
449+ 12 . [ ] Test bidirectional (SSR → RSC) if applicable
450+ 13 . [ ] Update documentation
0 commit comments