|
| 1 | +# RSC Plugin Architecture |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +The `@vitejs/plugin-rsc` implements React Server Components using Vite's multi-environment architecture. Each environment (rsc, ssr, client) has its own module graph, requiring a multi-pass build strategy. |
| 6 | + |
| 7 | +## Build Pipeline |
| 8 | + |
| 9 | +### With SSR (5-step) |
| 10 | + |
| 11 | +``` |
| 12 | +rsc (scan) → ssr (scan) → rsc (real) → client → ssr (real) |
| 13 | +``` |
| 14 | + |
| 15 | +| Step | Phase | Write to Disk | Purpose | |
| 16 | +| ---- | -------- | ------------- | ---------------------------------------------------------------------------- | |
| 17 | +| 1 | RSC scan | No | Discover `"use client"` boundaries → `clientReferenceMetaMap` | |
| 18 | +| 2 | SSR scan | No | Discover `"use server"` boundaries → `serverReferenceMetaMap` | |
| 19 | +| 3 | RSC real | Yes | Build server components, populate `renderedExports`, `serverChunk` | |
| 20 | +| 4 | Client | Yes | Build client bundle using reference metadata, generate `buildAssetsManifest` | |
| 21 | +| 5 | SSR real | Yes | Final SSR build with complete manifests | |
| 22 | + |
| 23 | +### Without SSR (4-step) |
| 24 | + |
| 25 | +``` |
| 26 | +rsc (scan) → client (scan) → rsc (real) → client (real) |
| 27 | +``` |
| 28 | + |
| 29 | +## Why This Build Order? |
| 30 | + |
| 31 | +1. **RSC scan first**: Must discover `"use client"` boundaries before client build knows what to bundle |
| 32 | +2. **SSR scan second**: Must discover `"use server"` boundaries for proxy generation in both client and SSR |
| 33 | +3. **RSC real third**: Generates proxy modules, determines which exports are actually used (`renderedExports`) |
| 34 | +4. **Client fourth**: Needs RSC's `renderedExports` to tree-shake unused client components |
| 35 | +5. **SSR last**: Needs complete client manifest for SSR hydration |
| 36 | + |
| 37 | +### Critical Dependency: RSC → SSR Scan |
| 38 | + |
| 39 | +The SSR scan **depends on RSC scan output**. This prevents parallelization: |
| 40 | + |
| 41 | +1. SSR entry imports `@vitejs/plugin-rsc/ssr` |
| 42 | +2. `ssr.tsx` imports `virtual:vite-rsc/client-references` |
| 43 | +3. This virtual module reads `clientReferenceMetaMap` (populated during RSC scan) |
| 44 | +4. Client components may import `"use server"` files |
| 45 | +5. SSR scan processes those imports, populating `serverReferenceMetaMap` |
| 46 | + |
| 47 | +## Data Flow |
| 48 | + |
| 49 | +``` |
| 50 | +┌─────────────────────────────────────────────────────────────┐ |
| 51 | +│ RSC Scan Build │ |
| 52 | +│ Writes: clientReferenceMetaMap (importId, exportNames) │ |
| 53 | +│ Writes: serverReferenceMetaMap (for "use server" in RSC) │ |
| 54 | +└──────────────────────────┬──────────────────────────────────┘ |
| 55 | + ▼ |
| 56 | +┌─────────────────────────────────────────────────────────────┐ |
| 57 | +│ SSR Scan Build │ |
| 58 | +│ Writes: serverReferenceMetaMap (for "use server" in SSR) │ |
| 59 | +└──────────────────────────┬──────────────────────────────────┘ |
| 60 | + ▼ |
| 61 | +┌─────────────────────────────────────────────────────────────┐ |
| 62 | +│ RSC Real Build │ |
| 63 | +│ Reads: clientReferenceMetaMap │ |
| 64 | +│ Mutates: renderedExports, serverChunk on each meta │ |
| 65 | +│ Outputs: rscBundle │ |
| 66 | +└──────────────────────────┬──────────────────────────────────┘ |
| 67 | + ▼ |
| 68 | + manager.stabilize() |
| 69 | + (sorts clientReferenceMetaMap) |
| 70 | + ▼ |
| 71 | +┌─────────────────────────────────────────────────────────────┐ |
| 72 | +│ Client Build │ |
| 73 | +│ Reads: clientReferenceMetaMap (with renderedExports) │ |
| 74 | +│ Uses: clientReferenceGroups for chunking │ |
| 75 | +│ Outputs: buildAssetsManifest, copies RSC assets │ |
| 76 | +└──────────────────────────┬──────────────────────────────────┘ |
| 77 | + ▼ |
| 78 | +┌─────────────────────────────────────────────────────────────┐ |
| 79 | +│ SSR Real Build │ |
| 80 | +│ Reads: serverReferenceMetaMap │ |
| 81 | +│ Final output with assets manifest │ |
| 82 | +└─────────────────────────────────────────────────────────────┘ |
| 83 | +``` |
| 84 | + |
| 85 | +## Key Components |
| 86 | + |
| 87 | +### RscPluginManager |
| 88 | + |
| 89 | +Central state manager shared across all build phases: |
| 90 | + |
| 91 | +```typescript |
| 92 | +class RscPluginManager { |
| 93 | + server: ViteDevServer |
| 94 | + config: ResolvedConfig |
| 95 | + rscBundle: Rollup.OutputBundle |
| 96 | + buildAssetsManifest: AssetsManifest | undefined |
| 97 | + isScanBuild: boolean = false |
| 98 | + |
| 99 | + // Reference tracking |
| 100 | + clientReferenceMetaMap: Record<string, ClientReferenceMeta> = {} |
| 101 | + clientReferenceGroups: Record<string, ClientReferenceMeta[]> = {} |
| 102 | + serverReferenceMetaMap: Record<string, ServerReferenceMeta> = {} |
| 103 | + serverResourcesMetaMap: Record<string, { key: string }> = {} |
| 104 | +} |
| 105 | +``` |
| 106 | + |
| 107 | +### Client Reference Discovery |
| 108 | + |
| 109 | +When RSC transform encounters `"use client"`: |
| 110 | + |
| 111 | +1. Parse exports from the module |
| 112 | +2. Generate a unique `referenceKey` (hash of module ID) |
| 113 | +3. Store in `clientReferenceMetaMap`: |
| 114 | + - `importId`: Module ID for importing |
| 115 | + - `referenceKey`: Unique identifier |
| 116 | + - `exportNames`: List of exports |
| 117 | + - `renderedExports`: Exports actually used (populated during real build) |
| 118 | + - `serverChunk`: Which RSC chunk imports this (for grouping) |
| 119 | + |
| 120 | +### Server Reference Discovery |
| 121 | + |
| 122 | +When transform encounters `"use server"`: |
| 123 | + |
| 124 | +1. Parse exported functions |
| 125 | +2. Generate reference IDs |
| 126 | +3. Store in `serverReferenceMetaMap` |
| 127 | +4. Generate proxy module that calls server via RPC |
| 128 | + |
| 129 | +### Virtual Modules |
| 130 | + |
| 131 | +Key virtual modules used in the build: |
| 132 | + |
| 133 | +| Virtual Module | Purpose | |
| 134 | +| ------------------------------------------------- | ----------------------------------------------- | |
| 135 | +| `virtual:vite-rsc/client-references` | Entry point importing all client components | |
| 136 | +| `virtual:vite-rsc/client-references/group/{name}` | Grouped client components for code splitting | |
| 137 | +| `virtual:vite-rsc/assets-manifest` | Client asset manifest for SSR | |
| 138 | +| `virtual:vite-rsc/rpc-client` | Dev-mode RPC client for cross-environment calls | |
| 139 | + |
| 140 | +### Cross-Environment Module Loading |
| 141 | + |
| 142 | +`import.meta.viteRsc.loadModule(environment, entryName)` enables loading modules from other environments: |
| 143 | + |
| 144 | +**Dev mode:** |
| 145 | + |
| 146 | +```typescript |
| 147 | +globalThis.__VITE_ENVIRONMENT_RUNNER_IMPORT__(environmentName, resolvedId) |
| 148 | +``` |
| 149 | + |
| 150 | +**Build mode:** |
| 151 | + |
| 152 | +- Emits marker during transform |
| 153 | +- `renderChunk` resolves to relative import path between output directories |
| 154 | + |
| 155 | +## Key Code Locations |
| 156 | + |
| 157 | +| Component | Location | |
| 158 | +| ----------------------------- | -------------------------- | |
| 159 | +| Manager definition | `src/plugin.ts:112-148` | |
| 160 | +| Build orchestration | `src/plugin.ts:343-429` | |
| 161 | +| clientReferenceMetaMap writes | `src/plugin.ts:1386` | |
| 162 | +| serverReferenceMetaMap writes | `src/plugin.ts:1817, 1862` | |
| 163 | +| Scan strip plugin | `src/plugins/scan.ts` | |
| 164 | +| Cross-env module loading | `src/plugin.ts:824-916` | |
| 165 | + |
| 166 | +## Virtual Module Resolution |
| 167 | + |
| 168 | +Virtual modules with `\0` prefix need special handling: |
| 169 | + |
| 170 | +1. Vite convention: `\0` prefix marks virtual modules |
| 171 | +2. When used as import specifiers, `\0` must be stripped |
| 172 | +3. CSS requests get `?direct` query added by Vite |
| 173 | +4. The `resolved-id-proxy` plugin handles query stripping |
| 174 | + |
| 175 | +See `src/plugins/resolved-id-proxy.ts` for implementation. |
0 commit comments