Skip to content

Commit 57e19ed

Browse files
hi-ogawaclaude
andcommitted
chore(rsc): add docs and plan for import.meta.viteRsc.import
- Add architecture.md documenting build pipeline and data flow - Add bundler-comparison.md comparing RSC approaches across bundlers - Add implementation plan for new ergonomic import API - Stub import-environment.ts plugin file Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 07adbab commit 57e19ed

File tree

8 files changed

+624
-4
lines changed

8 files changed

+624
-4
lines changed

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ This monorepo contains multiple packages (see [README.md](README.md#packages) fo
1313

1414
- `packages/plugin-react/` - Main React plugin with Babel
1515
- `packages/plugin-react-swc/` - SWC-based React plugin
16-
- `packages/plugin-rsc/` - React Server Components ([AI guidance](packages/plugin-rsc/AGENTS.md))
16+
- `packages/plugin-rsc/` - React Server Components ([AI guidance](packages/plugin-rsc/AGENTS.md), [architecture](packages/plugin-rsc/docs/architecture.md))
1717
- `packages/plugin-react-oxc/` - Deprecated (merged with plugin-react)
1818

1919
### Essential Setup Commands

packages/plugin-rsc/AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ This document provides AI-agent-specific guidance for the React Server Component
44

55
- **[README.md](README.md)** - Plugin overview, concepts, and examples
66
- **[CONTRIBUTING.md](CONTRIBUTING.md)** - Development setup and testing guidelines
7+
- **[docs/architecture.md](docs/architecture.md)** - Build pipeline, data flow, and key components
8+
- **[docs/bundler-comparison.md](docs/bundler-comparison.md)** - How different bundlers approach RSC
79

810
## Quick Reference for AI Agents
911

packages/plugin-rsc/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,13 @@ Note that while there are official npm packages [`server-only`](https://www.npmj
628628
629629
This build-time validation is enabled by default and can be disabled by setting `validateImports: false` in the plugin options.
630630
631+
## Architecture Documentation
632+
633+
For developers interested in the internal architecture:
634+
635+
- **[docs/architecture.md](docs/architecture.md)** - Build pipeline, data flow, and key components
636+
- **[docs/bundler-comparison.md](docs/bundler-comparison.md)** - How different bundlers approach RSC
637+
631638
## Credits
632639
633640
This project builds on fundamental techniques and insights from pioneering Vite RSC implementations.
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
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.
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# RSC Bundler Architecture Comparison
2+
3+
How different bundlers/frameworks handle the architectural complexity of React Server Components.
4+
5+
## The Core Challenge
6+
7+
RSC requires discovering two types of references at build time:
8+
9+
1. **Client references** (`"use client"`) - components that run on the client
10+
2. **Server references** (`"use server"`) - functions callable from client
11+
12+
The challenge: server references are often **imported by client components**, creating a dependency:
13+
14+
```
15+
Server components → discover client boundaries → client components → discover server references
16+
```
17+
18+
## Architectural Approaches
19+
20+
### 1. Multi-Graph / Multi-Pass (Vite RSC Plugin)
21+
22+
**Approach**: Separate module graphs per environment, sequential scan phases.
23+
24+
```
25+
RSC scan → SSR scan → RSC build → Client build → SSR build
26+
```
27+
28+
**How it works**:
29+
30+
- Each environment (rsc, ssr, client) has its own module graph
31+
- RSC scan populates `clientReferenceMetaMap`
32+
- SSR scan reads this via `virtual:vite-rsc/client-references` to discover server references
33+
- Sequential dependency prevents parallelization
34+
35+
**Trade-offs**:
36+
37+
- Clean separation of concerns
38+
- Works with existing Vite environment API
39+
- Multiple Rollup orchestration cycles
40+
- Cannot parallelize scan phases (architectural dependency)
41+
42+
### 2. Unified Graph with Transitions (Turbopack/Next.js)
43+
44+
**Approach**: Single compiler with environment "transitions" at module boundaries.
45+
46+
**How it works**:
47+
48+
> "We can mark an import as a transition from server to browser or from browser to server. This is what allows Turbopack to efficiently bundle Server Components and Client Components, as well as Server Functions imported from Client Components."
49+
50+
- Single unified graph for all environments
51+
- `"use client"` creates a transition point, not a separate graph
52+
- No separate scan phase needed - references discovered during single traversal
53+
54+
**Trade-offs**:
55+
56+
- Single compilation pass
57+
- No coordination between compiler processes
58+
- Better debugging (single source of truth)
59+
- Requires bundler-level architecture changes (Rust rewrite)
60+
61+
**Source**: [Turbopack Documentation](https://nextjs.org/docs/app/api-reference/turbopack)
62+
63+
### 3. Unified Graph with Environments (Parcel)
64+
65+
**Approach**: Single module graph spanning environments, environment property per module.
66+
67+
**How it works**:
68+
69+
> "Unlike most other bundlers, Parcel has a single unified module graph spanning across environments rather than splitting each environment into a separate build. This enables code splitting to span environments too."
70+
71+
- Each module has an associated environment (server, react-client, etc.)
72+
- `"use client"` transforms imports to Client References at boundary
73+
- Single compilation discovers all references
74+
75+
**Trade-offs**:
76+
77+
- Single compilation pass
78+
- Cross-environment code splitting
79+
- Environment-aware from v2 (2021)
80+
- Different mental model than traditional bundlers
81+
82+
**Source**: [How Parcel bundles React Server Components](https://devongovett.me/blog/parcel-rsc.html)
83+
84+
### 4. Plugin-Based Discovery (Webpack)
85+
86+
**Approach**: Webpack plugin generates client manifest during standard compilation.
87+
88+
**How it works**:
89+
90+
- `react-server-dom-webpack/plugin` scans for `"use client"` directives
91+
- Generates `react-client-manifest.json` with module IDs, chunks, exports
92+
- Server uses manifest to create Client References
93+
- Client uses manifest to load chunks on demand
94+
95+
**Trade-offs**:
96+
97+
- Integrates with existing Webpack ecosystem
98+
- Leverages Webpack's chunk loading runtime
99+
- Requires framework-level orchestration (Next.js handles multi-environment)
100+
101+
**Source**: [react-server-dom-webpack](https://www.npmjs.com/package/react-server-dom-webpack)
102+
103+
### 5. Layers (Rspack)
104+
105+
**Approach**: Using "layers" feature to implement RSC in a Webpack-compatible way.
106+
107+
**How it works**:
108+
109+
- Rspack 1.0.0-beta.1 introduced "layers" support
110+
- Layers allow frameworks to implement RSC environment separation
111+
- Built-in RSC support on roadmap, inspired by Parcel
112+
113+
**Status**: In development, not yet fully built-in.
114+
115+
**Source**: [Rspack Roadmap](https://rspack.rs/misc/planning/roadmap)
116+
117+
## Key Architectural Insight
118+
119+
### Why Unified Graph Avoids Multi-Pass
120+
121+
In a **multi-graph** approach (Vite):
122+
123+
```
124+
Graph 1 (RSC): server.tsx → client.tsx (stops at boundary)
125+
Graph 2 (SSR): needs to know about client.tsx → action.tsx
126+
127+
Problem: Graph 2 can't start until Graph 1 identifies boundaries
128+
Solution: Sequential scan phases
129+
```
130+
131+
In a **unified graph** approach (Parcel/Turbopack):
132+
133+
```
134+
Single Graph: server.tsx → client.tsx[transition] → action.tsx
135+
136+
All modules in one graph with environment transitions at boundaries
137+
No sequential dependency - discovered in single traversal
138+
```
139+
140+
The unified approach treats `"use client"` as a **transition annotation** rather than a **graph boundary**.
141+
142+
## Comparison Table
143+
144+
| Bundler | Graph Model | Passes | Parallelizable | Complexity |
145+
| --------- | ---------------------- | ------------------- | ---------------------- | ------------ |
146+
| Vite RSC | Multi-graph | 5 (with SSR) | No (architectural dep) | Medium |
147+
| Turbopack | Unified + transitions | 1 | N/A (single pass) | High (Rust) |
148+
| Parcel | Unified + environments | 1 | N/A (single pass) | Medium |
149+
| Webpack | Plugin-based | Framework-dependent | Framework-dependent | Low (plugin) |
150+
| Rspack | Layers (WIP) | TBD | TBD | Medium |
151+
152+
## Implications for Vite RSC Plugin
153+
154+
The multi-pass approach is a consequence of Vite's environment API design, where each environment has its own module graph. This is fundamentally different from Parcel/Turbopack's unified graph.
155+
156+
**Potential future optimizations**:
157+
158+
1. **Cache scan results** - Skip scan phases on incremental builds if references unchanged
159+
2. **Skip SSR scan** - For apps without `"use server"` (rare, ~12% of apps)
160+
3. **Vite architecture evolution** - If Vite adopts unified graph concepts, could enable single-pass
161+
162+
**Not possible without architectural changes**:
163+
164+
- Parallel scan phases (SSR scan depends on RSC scan output)
165+
- Single-pass compilation (requires unified graph)
166+
167+
## References
168+
169+
- [Why Does RSC Integrate with a Bundler?](https://overreacted.io/why-does-rsc-integrate-with-a-bundler/) - Dan Abramov
170+
- [How Parcel bundles React Server Components](https://devongovett.me/blog/parcel-rsc.html) - Devon Govett
171+
- [Turbopack Documentation](https://nextjs.org/docs/app/api-reference/turbopack) - Next.js
172+
- [Parcel RSC Recipe](https://parceljs.org/recipes/rsc/) - Parcel
173+
- [react-server-dom-webpack](https://www.npmjs.com/package/react-server-dom-webpack) - React
174+
- [Waku Migration to @vitejs/plugin-rsc](https://waku.gg/blog/migration-to-vite-plugin-rsc) - Waku

0 commit comments

Comments
 (0)