vite-plugin-react-server
Version:
Vite plugin for React Server Components (RSC)
208 lines (205 loc) • 7.92 kB
JavaScript
/**
* vite-plugin-react-server
* Copyright (c) Nico Brinkkemper
* MIT License
*/
import { join, dirname } from 'node:path';
import 'node:worker_threads';
import { createLogger } from 'vite';
import { checkFilesExist } from '../checkFilesExist.js';
import { resolveOptions } from '../config/resolveOptions.js';
import { resolvePages } from '../config/resolvePages.js';
import { resolveUserConfig } from '../config/resolveUserConfig.js';
import { tryManifest } from '../helpers/tryManifest.js';
import { createBuildLoader } from '../loader/createBuildLoader.js';
import { createWorker } from '../worker/createWorker.js';
import { renderPages } from '../worker/html/renderPages.js';
import { mkdir } from 'node:fs/promises';
import { collectManifestClientFiles } from '../collect-manifest-client-files.js';
import { mkdirSync, copyFileSync } from 'node:fs';
import { copyDir } from '../copy-dir.js';
let loader = null;
let worker;
let htmlTransform = null;
let clientAssets = /* @__PURE__ */ new Set();
function reactStaticPlugin(options) {
const timing = {
start: Date.now()
};
let files;
let root = process.cwd();
let userConfig;
let userOptions;
let pages;
let serverManifest = {};
let clientManifest = {};
const resolvedOptions = resolveOptions(options, false);
if (resolvedOptions.type === "error") {
throw resolvedOptions.error;
}
userOptions = resolvedOptions.userOptions;
if (userOptions.projectRoot != root && typeof userOptions.projectRoot === "string" && userOptions.projectRoot !== process.cwd() && userOptions.projectRoot !== "") {
root = userOptions.projectRoot;
console.log(
"[vite:plugin-react-server] Root dir changed in plugin",
userOptions.projectRoot,
root
);
}
return {
name: "vite:plugin-react-server/static",
enforce: "post",
api: {
meta: { timing }
},
async config(config, configEnv) {
if (typeof config.root === "string" && config.root !== root && config.root !== process.cwd() && config.root !== "") {
root = config.root;
}
const resolvePagesResult = await resolvePages(userOptions.build.pages);
if (resolvePagesResult.type === "error") {
throw resolvePagesResult.error;
}
pages = resolvePagesResult.pages;
files = await checkFilesExist(pages, userOptions, root);
const resolvedConfig2 = resolveUserConfig({
isStatic: true,
config,
configEnv,
userOptions,
files
});
if (resolvedConfig2.type === "error") {
throw resolvedConfig2.error;
}
userConfig = resolvedConfig2.userConfig;
timing.configResolved = Date.now();
return {};
},
async buildStart() {
timing.buildStart = Date.now();
},
async closeBundle() {
timing.renderStart = Date.now();
const serverManifestResult = tryManifest({
root,
outDir: join(userOptions.build.outDir, userOptions.build.server),
ssrManifest: false
});
if (serverManifestResult.type === "error") {
throw serverManifestResult.error;
}
serverManifest = serverManifestResult.manifest;
const clientManifestResult = tryManifest({
root,
outDir: join(userOptions.build.outDir, userOptions.build.client),
ssrManifest: false
});
if (clientManifestResult.type === "error") {
throw clientManifestResult.error;
}
clientManifest = clientManifestResult.manifest;
const staticDir = join(root, userOptions.build.outDir, userOptions.build.static);
await mkdir(staticDir, { recursive: true });
worker = await createWorker({
projectRoot: root,
workerPath: userOptions.htmlWorkerPath,
condition: "react-server",
reverseCondition: true,
mode: "production"
});
if (typeof loader !== "function") {
loader = createBuildLoader({
root,
userConfig,
userOptions,
pluginContext: this,
serverManifest,
clientManifest
});
}
const routeCssMap = /* @__PURE__ */ new Map();
const globalCss = /* @__PURE__ */ new Set();
await mkdir(staticDir, { recursive: true });
await copyDir(join(root, userOptions.build.outDir, userOptions.build.client), join(root, userOptions.build.outDir, userOptions.build.static));
const { cssFiles: indexCss } = collectManifestClientFiles({
manifest: clientManifest,
root,
pagePath: "index.html",
moduleBase: userOptions.moduleBase,
preserveModulesRoot: userOptions.build.preserveModulesRoot,
testClient: () => true
});
indexCss.forEach((css) => globalCss.add(css));
for (const route of pages) {
const routeFiles = files.urlMap.get(route);
if (routeFiles) {
const pageCss = collectManifestClientFiles({
manifest: serverManifest,
root,
pagePath: routeFiles.page,
moduleBase: userOptions.moduleBase,
preserveModulesRoot: userOptions.build.preserveModulesRoot,
onClientModule(path) {
const targetPath = join(root, userOptions.build.outDir, userOptions.build.server, path);
const destinationPath = join(root, userOptions.build.outDir, userOptions.build.static, path);
mkdirSync(dirname(destinationPath), { recursive: true });
copyFileSync(targetPath, destinationPath);
},
testClient: userOptions.autoDiscover.cssPattern,
testJson: userOptions.autoDiscover.jsonPattern
});
routeCssMap.set(route, /* @__PURE__ */ new Set([...globalCss, ...pageCss.cssFiles.keys()]));
}
}
const bootstrapModules = clientManifest["index.html"]?.file ? [clientManifest["index.html"].file.startsWith("/") ? clientManifest["index.html"].file.slice(1) : clientManifest["index.html"].file] : [];
const { failedRoutes, completedRoutes } = await renderPages(
pages,
files,
{
root,
outDir: userOptions.build.outDir,
htmlOutputPath: join(userOptions.build.outDir, userOptions.build.static, "index.html"),
pipableStreamOptions: {
bootstrapModules
},
moduleRootPath: join(root, userOptions.build.outDir, userOptions.build.static, userOptions.moduleBasePath),
moduleBasePath: userOptions.moduleBasePath,
moduleBaseURL: userOptions.moduleBaseURL,
inlineCss: userOptions.inlineCss,
pageExportName: userOptions.pageExportName,
propsExportName: userOptions.propsExportName,
Html: userOptions.Html,
CssCollector: userOptions.CssCollector,
cssFiles: [],
logger: createLogger(),
moduleBase: userOptions.moduleBase,
worker,
clientManifest,
serverManifest,
loader,
transformIndexHtml: htmlTransform,
onClientJSFile: (url) => {
if (!clientAssets.has(url)) {
const clientPath = join(root, userOptions.build.outDir, userOptions.build.client, url);
const targetPath = join(root, userOptions.build.outDir, userOptions.build.static, url);
mkdirSync(dirname(targetPath), { recursive: true });
copyFileSync(clientPath, targetPath);
clientAssets.add(url);
}
}
}
);
if (failedRoutes.size > 0) {
console.error(
"[vite-plugin-react-server] Failed to render routes:",
failedRoutes
);
}
console.log(`Rendered ${completedRoutes.size} unique routes to ${join(userOptions.build.outDir, userOptions.build.static)}`);
await worker.terminate();
}
};
}
export { reactStaticPlugin };
//# sourceMappingURL=plugin.js.map