Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
07adbab
feat(rsc): add `import(.., { environment: ... })`
hi-ogawa Jan 16, 2026
57e19ed
chore(rsc): add docs and plan for `import.meta.viteRsc.import`
hi-ogawa Jan 16, 2026
82d0cb1
test: update example
hi-ogawa Jan 16, 2026
51f8e91
feat(rsc): implement `import.meta.viteRsc.import` transform
hi-ogawa Jan 16, 2026
3591f12
refactor(rsc): use transform handler object form
hi-ogawa Jan 16, 2026
2fe7f44
docs(rsc): add manifest-based approach for viteRsc.import
hi-ogawa Jan 16, 2026
e663662
feat(rsc): implement manifest-based viteRsc.import for custom entryFi…
hi-ogawa Jan 16, 2026
857f9ac
refactor(rsc): use emitFile and virtual scan placeholder for env imports
hi-ogawa Jan 16, 2026
8bc8ab4
refactor: tweak
hi-ogawa Jan 16, 2026
a1332f9
todo
hi-ogawa Jan 16, 2026
fe0e772
refactor: wip
hi-ogawa Jan 16, 2026
f7c422e
refactor(rsc): use renderChunk to fix manifest import relative paths
hi-ogawa Jan 16, 2026
e8ffe96
refactor(rsc): remove entryName from environment import metadata
hi-ogawa Jan 16, 2026
eac76d5
refactor(rsc): merge environmentImportOutputMap into environmentImpor…
hi-ogawa Jan 16, 2026
0251ac1
refactor(rsc): use relative IDs in environment imports manifest
hi-ogawa Jan 16, 2026
b2b877a
refactor(rsc): restructure environmentImportMetaMap to key by sourceEnv
hi-ogawa Jan 16, 2026
1efe394
chore: cleanup
hi-ogawa Jan 16, 2026
29f4440
chore: cleanup
hi-ogawa Jan 16, 2026
a3953ec
docs(rsc): document viteRsc.import API
hi-ogawa Jan 16, 2026
a11be5f
test: add e2e
hi-ogawa Jan 16, 2026
3dcefb8
test: add e2e
hi-ogawa Jan 16, 2026
45d11f4
chore: cleanup
hi-ogawa Jan 16, 2026
acdddf6
chore: cleanup
hi-ogawa Jan 16, 2026
f4892d2
fix: fix ssr -> rsc
hi-ogawa Jan 16, 2026
705c821
chore: todo
hi-ogawa Jan 16, 2026
04debfb
refactor: consolidate rscBundle into bundles map and delay fileName r…
hi-ogawa Jan 16, 2026
6431f8f
refactor: restructure environmentImportMetaMap to [sourceEnv][targetE…
hi-ogawa Jan 16, 2026
f3e68ae
chore: packages/plugin-rsc/docs/notes/2026-01-16-plugin-refactor.md
hi-ogawa Jan 16, 2026
74769a1
refactor: move writeEnvironmentImportsManifest to import-environment.ts
hi-ogawa Jan 16, 2026
eef5fa2
fix: mutate rollupOptions.input later ensureEnvironmentImportsEntryFa…
hi-ogawa Jan 16, 2026
94532a5
test: add e2e
hi-ogawa Jan 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ This monorepo contains multiple packages (see [README.md](README.md#packages) fo

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

### Essential Setup Commands
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin-rsc/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ This document provides AI-agent-specific guidance for the React Server Component

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

## Quick Reference for AI Agents

Expand Down
34 changes: 34 additions & 0 deletions packages/plugin-rsc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,33 @@ ssrModule.renderHTML(...);
export function renderHTML(...) {}
```

#### `import.meta.viteRsc.import`

- Type: `<T>(specifier: string, options: { environment: string }) => Promise<T>`

A more ergonomic alternative to `loadModule`:

1. No manual `rollupOptions.input` config needed - entries are auto-discovered
2. Specifier matches the path in `typeof import(...)` type annotations

**Comparison:**

```ts
// Before (loadModule) - requires vite.config.ts:
// environments.ssr.build.rollupOptions.input = { index: './entry.ssr.tsx' }
import.meta.viteRsc.loadModule<typeof import('./entry.ssr.tsx')>('ssr', 'index')

// After (import) - no config needed, auto-discovered
import.meta.viteRsc.import<typeof import('./entry.ssr.tsx')>(
'./entry.ssr.tsx',
{ environment: 'ssr' },
)
```

During development, this works the same as `loadModule`, using the `__VITE_ENVIRONMENT_RUNNER_IMPORT__` function to import modules in the target environment.

During production build, the plugin auto-discovers these imports and emits them as entries in the target environment. A manifest file (`__vite_rsc_env_imports_manifest.js`) is generated to map module specifiers to their output filenames.

### Available on `rsc` environment

#### `import.meta.viteRsc.loadCss`
Expand Down Expand Up @@ -616,6 +643,13 @@ Note that while there are official npm packages [`server-only`](https://www.npmj

This build-time validation is enabled by default and can be disabled by setting `validateImports: false` in the plugin options.

## Architecture Documentation

For developers interested in the internal architecture:

- **[docs/architecture.md](docs/architecture.md)** - Build pipeline, data flow, and key components
- **[docs/bundler-comparison.md](docs/bundler-comparison.md)** - How different bundlers approach RSC

## Credits

This project builds on fundamental techniques and insights from pioneering Vite RSC implementations.
Expand Down
175 changes: 175 additions & 0 deletions packages/plugin-rsc/docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# RSC Plugin Architecture

## Overview

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.

## Build Pipeline

### With SSR (5-step)

```
rsc (scan) → ssr (scan) → rsc (real) → client → ssr (real)
```

| Step | Phase | Write to Disk | Purpose |
| ---- | -------- | ------------- | ---------------------------------------------------------------------------- |
| 1 | RSC scan | No | Discover `"use client"` boundaries → `clientReferenceMetaMap` |
| 2 | SSR scan | No | Discover `"use server"` boundaries → `serverReferenceMetaMap` |
| 3 | RSC real | Yes | Build server components, populate `renderedExports`, `serverChunk` |
| 4 | Client | Yes | Build client bundle using reference metadata, generate `buildAssetsManifest` |
| 5 | SSR real | Yes | Final SSR build with complete manifests |

### Without SSR (4-step)

```
rsc (scan) → client (scan) → rsc (real) → client (real)
```

## Why This Build Order?

1. **RSC scan first**: Must discover `"use client"` boundaries before client build knows what to bundle
2. **SSR scan second**: Must discover `"use server"` boundaries for proxy generation in both client and SSR
3. **RSC real third**: Generates proxy modules, determines which exports are actually used (`renderedExports`)
4. **Client fourth**: Needs RSC's `renderedExports` to tree-shake unused client components
5. **SSR last**: Needs complete client manifest for SSR hydration

### Critical Dependency: RSC → SSR Scan

The SSR scan **depends on RSC scan output**. This prevents parallelization:

1. SSR entry imports `@vitejs/plugin-rsc/ssr`
2. `ssr.tsx` imports `virtual:vite-rsc/client-references`
3. This virtual module reads `clientReferenceMetaMap` (populated during RSC scan)
4. Client components may import `"use server"` files
5. SSR scan processes those imports, populating `serverReferenceMetaMap`

## Data Flow

```
┌─────────────────────────────────────────────────────────────┐
│ RSC Scan Build │
│ Writes: clientReferenceMetaMap (importId, exportNames) │
│ Writes: serverReferenceMetaMap (for "use server" in RSC) │
└──────────────────────────┬──────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ SSR Scan Build │
│ Writes: serverReferenceMetaMap (for "use server" in SSR) │
└──────────────────────────┬──────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ RSC Real Build │
│ Reads: clientReferenceMetaMap │
│ Mutates: renderedExports, serverChunk on each meta │
│ Outputs: rscBundle │
└──────────────────────────┬──────────────────────────────────┘
manager.stabilize()
(sorts clientReferenceMetaMap)
┌─────────────────────────────────────────────────────────────┐
│ Client Build │
│ Reads: clientReferenceMetaMap (with renderedExports) │
│ Uses: clientReferenceGroups for chunking │
│ Outputs: buildAssetsManifest, copies RSC assets │
└──────────────────────────┬──────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ SSR Real Build │
│ Reads: serverReferenceMetaMap │
│ Final output with assets manifest │
└─────────────────────────────────────────────────────────────┘
```

## Key Components

### RscPluginManager

Central state manager shared across all build phases:

```typescript
class RscPluginManager {
server: ViteDevServer
config: ResolvedConfig
rscBundle: Rollup.OutputBundle
buildAssetsManifest: AssetsManifest | undefined
isScanBuild: boolean = false

// Reference tracking
clientReferenceMetaMap: Record<string, ClientReferenceMeta> = {}
clientReferenceGroups: Record<string, ClientReferenceMeta[]> = {}
serverReferenceMetaMap: Record<string, ServerReferenceMeta> = {}
serverResourcesMetaMap: Record<string, { key: string }> = {}
}
```

### Client Reference Discovery

When RSC transform encounters `"use client"`:

1. Parse exports from the module
2. Generate a unique `referenceKey` (hash of module ID)
3. Store in `clientReferenceMetaMap`:
- `importId`: Module ID for importing
- `referenceKey`: Unique identifier
- `exportNames`: List of exports
- `renderedExports`: Exports actually used (populated during real build)
- `serverChunk`: Which RSC chunk imports this (for grouping)

### Server Reference Discovery

When transform encounters `"use server"`:

1. Parse exported functions
2. Generate reference IDs
3. Store in `serverReferenceMetaMap`
4. Generate proxy module that calls server via RPC

### Virtual Modules

Key virtual modules used in the build:

| Virtual Module | Purpose |
| ------------------------------------------------- | ----------------------------------------------- |
| `virtual:vite-rsc/client-references` | Entry point importing all client components |
| `virtual:vite-rsc/client-references/group/{name}` | Grouped client components for code splitting |
| `virtual:vite-rsc/assets-manifest` | Client asset manifest for SSR |
| `virtual:vite-rsc/rpc-client` | Dev-mode RPC client for cross-environment calls |

### Cross-Environment Module Loading

`import.meta.viteRsc.loadModule(environment, entryName)` enables loading modules from other environments:

**Dev mode:**

```typescript
globalThis.__VITE_ENVIRONMENT_RUNNER_IMPORT__(environmentName, resolvedId)
```

**Build mode:**

- Emits marker during transform
- `renderChunk` resolves to relative import path between output directories

## Key Code Locations

| Component | Location |
| ----------------------------- | -------------------------- |
| Manager definition | `src/plugin.ts:112-148` |
| Build orchestration | `src/plugin.ts:343-429` |
| clientReferenceMetaMap writes | `src/plugin.ts:1386` |
| serverReferenceMetaMap writes | `src/plugin.ts:1817, 1862` |
| Scan strip plugin | `src/plugins/scan.ts` |
| Cross-env module loading | `src/plugin.ts:824-916` |

## Virtual Module Resolution

Virtual modules with `\0` prefix need special handling:

1. Vite convention: `\0` prefix marks virtual modules
2. When used as import specifiers, `\0` must be stripped
3. CSS requests get `?direct` query added by Vite
4. The `resolved-id-proxy` plugin handles query stripping

See `src/plugins/resolved-id-proxy.ts` for implementation.
Loading
Loading