vite-plugin-react-server
Version:
Vite plugin for React Server Components (RSC)
143 lines (129 loc) • 4.61 kB
text/typescript
import type {
EnvironmentModuleGraph,
EnvironmentModuleNode,
ModuleNode,
ViteDevServer,
Logger,
} from "vite";
const CSS_EXT = /\.(css|scss|sass|less|styl|stylus|pcss|postcss)(\?|$)/;
export type CollectedCss = { id: string; code: string };
/**
* Walks the Vite server environment's module graph from `pagePath` and
* returns the raw CSS code of every CSS module reachable from it.
*
* Replaces the rsc-worker's Node ESM CSS loader for runner mode: the
* runner bypasses Node's import resolver, so the loader never fires.
* The main thread has visibility into the same module graph and can
* collect CSS deterministically once `runner.import(pagePath)` has
* caused Vite to transform the page and its deps.
*/
export async function collectRunnerCss(
server: ViteDevServer,
pagePath: string,
projectRoot: string,
logger: Logger,
verbose = false
): Promise<CollectedCss[]> {
const env = server.environments?.["server"];
if (!env) {
if (verbose) logger.warn(`[collectRunnerCss] no server environment`);
return [];
}
const moduleGraph: EnvironmentModuleGraph = env.moduleGraph;
const candidates = [
pagePath,
pagePath.startsWith("/") ? pagePath : `/${pagePath}`,
pagePath.startsWith(projectRoot) ? pagePath : `${projectRoot}/${pagePath}`,
];
let pageModule: EnvironmentModuleNode | ModuleNode | undefined;
for (const url of candidates) {
pageModule = await moduleGraph.getModuleByUrl(url);
if (pageModule) break;
}
// Fallback: scan idToModuleMap for an entry whose id endsWith the pagePath.
// Vite keys server-env modules by full absolute path with optional query.
if (!pageModule) {
const idMap = (moduleGraph as any).idToModuleMap as
| Map<string, EnvironmentModuleNode>
| undefined;
if (idMap) {
for (const [id, node] of idMap.entries()) {
if (id.endsWith(pagePath) || id.endsWith(`/${pagePath}`)) {
pageModule = node;
break;
}
}
}
}
if (!pageModule) {
if (verbose) {
logger.warn(
`[collectRunnerCss] no module for pagePath: ${pagePath} (tried: ${candidates.join(", ")}; graph size: ${
(moduleGraph as any).idToModuleMap?.size ?? "?"
})`
);
}
return [];
}
if (verbose) {
logger.info(`[collectRunnerCss] resolved page module: ${pageModule.id}`);
}
const seen = new Set<string>();
const out: CollectedCss[] = [];
const walk = async (mod: EnvironmentModuleNode | ModuleNode) => {
if (!mod?.id || seen.has(mod.id)) return;
seen.add(mod.id);
if (CSS_EXT.test(mod.id)) {
try {
const transformed = await env.transformRequest(`${mod.id}?inline`);
if (transformed?.code) {
const code = transformed.code;
// Vite transforms `.css?inline` modules with different framings:
// - SSR runner output: `const __vite_ssr_export_default__ = "...";`
// - plain ESM output: `export default "...";`
// - legacy module API: `__vite__cssModules.default = "...";`
const exportMatch =
code.match(/__vite_ssr_export_default__\s*=\s*("(?:\\.|[^"\\])*")/) ??
code.match(/export\s+default\s+("(?:\\.|[^"\\])*")/) ??
code.match(/__vite__cssModules\.default\s*=\s*("(?:\\.|[^"\\])*")/);
if (exportMatch) {
const raw = JSON.parse(exportMatch[1]);
if (typeof raw === "string" && raw.length > 0) {
out.push({ id: mod.id, code: raw });
} else if (verbose) {
logger.warn(`[collectRunnerCss] inline default empty for ${mod.id}`);
}
} else if (verbose) {
logger.warn(
`[collectRunnerCss] could not find inline export in transform for ${mod.id} (codeLen=${code.length})`
);
}
} else if (verbose) {
logger.warn(`[collectRunnerCss] no transformed code for ${mod.id}?inline`);
}
} catch (err) {
if (verbose) {
logger.warn(
`[collectRunnerCss] failed to inline ${mod.id}: ${String(err)}`
);
}
}
}
if (mod.importedModules) {
for (const imported of mod.importedModules as Iterable<
EnvironmentModuleNode | ModuleNode
>) {
if (imported && typeof imported === "object" && "id" in imported) {
await walk(imported);
}
}
}
};
await walk(pageModule);
if (verbose) {
logger.info(
`[collectRunnerCss] collected ${out.length} CSS file(s) for ${pagePath}`
);
}
return out;
}