From 550264f44ffe3b9313b12a74e1260255ca926ecd Mon Sep 17 00:00:00 2001 From: Alexander Lichter Date: Sun, 21 Jun 2026 21:17:47 +0200 Subject: [PATCH] feat(rsc): expose module runner CJS transform --- packages/plugin-rsc/README.md | 37 +++++++++++++++++++ packages/plugin-rsc/src/plugins/cjs.ts | 2 +- .../plugin-rsc/src/transforms/cjs.test.ts | 2 +- packages/plugin-rsc/src/transforms/index.ts | 1 + 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/plugin-rsc/README.md b/packages/plugin-rsc/README.md index a8780f9fc..2480e2487 100644 --- a/packages/plugin-rsc/README.md +++ b/packages/plugin-rsc/README.md @@ -442,6 +442,43 @@ export default defineConfig({ }) ``` +### `@vitejs/plugin-rsc/transforms` + +The CommonJS transform used by the RSC module runner is available as a +low-level framework integration API: + +```js +import { transformCjsToEsm } from '@vitejs/plugin-rsc/transforms' +import { parseAstAsync } from 'vite' + +const commonJsApplicationPlugin = { + name: 'framework:commonjs-application', + apply: 'serve', + applyToEnvironment: (env) => env.config.dev.moduleRunnerTransform, + transform: { + filter: { id: /\.cjs$/ }, + async handler(code, id) { + const ast = await parseAstAsync(code) + const { output } = transformCjsToEsm(code, ast, { id }) + return { + code: output.toString(), + map: output.generateMap({ hires: 'boundary' }), + } + }, + }, +} +``` + +This transform targets Vite's server module runner. The generated module uses +the module runner's `__vite_ssr_exportAll__` helper and top-level `await`, so it +is not a general production CommonJS bundler transform. + +Nested `require()` calls are hoisted to asynchronous imports to keep the +surrounding function synchronous. This does not preserve Node.js semantics +when a nested require is conditional or depends on runtime state. Frameworks +should detect or implement their own policy for dynamic `require()` calls +before invoking this transform. + ## RSC runtime (react-server-dom) API ### `@vitejs/plugin-rsc/rsc` diff --git a/packages/plugin-rsc/src/plugins/cjs.ts b/packages/plugin-rsc/src/plugins/cjs.ts index 40a6ba0f3..56b0eb8d0 100644 --- a/packages/plugin-rsc/src/plugins/cjs.ts +++ b/packages/plugin-rsc/src/plugins/cjs.ts @@ -4,7 +4,7 @@ import { createDebug } from '@hiogawa/utils' import * as esModuleLexer from 'es-module-lexer' import { parseAstAsync, type Plugin } from 'vite' import { findClosestPkgJsonPath } from 'vitefu' -import { transformCjsToEsm } from '../transforms/cjs' +import { transformCjsToEsm } from '../transforms' import { parseIdQuery } from './shared' const debug = createDebug('vite-rsc:cjs') diff --git a/packages/plugin-rsc/src/transforms/cjs.test.ts b/packages/plugin-rsc/src/transforms/cjs.test.ts index 8a71f2c53..c029c4004 100644 --- a/packages/plugin-rsc/src/transforms/cjs.test.ts +++ b/packages/plugin-rsc/src/transforms/cjs.test.ts @@ -1,7 +1,7 @@ import path from 'node:path' import { createServer, createServerModuleRunner, parseAstAsync } from 'vite' import { describe, expect, it } from 'vitest' -import { transformCjsToEsm } from './cjs' +import { transformCjsToEsm } from './index' import { debugSourceMap } from './test-utils' describe(transformCjsToEsm, () => { diff --git a/packages/plugin-rsc/src/transforms/index.ts b/packages/plugin-rsc/src/transforms/index.ts index b37480dae..c7c70802a 100644 --- a/packages/plugin-rsc/src/transforms/index.ts +++ b/packages/plugin-rsc/src/transforms/index.ts @@ -4,3 +4,4 @@ export * from './proxy-export' export * from './utils' export * from './server-action' export * from './expand-export-all' +export * from './cjs'