UNPKG

vite-plugin-react-server

Version:
242 lines (239 loc) 8.39 kB
/** * vite-plugin-react-server * Copyright (c) Nico Brinkkemper * MIT License */ import { mkdir, writeFile } from 'node:fs/promises'; import { join, dirname } from 'node:path'; import { Transform } from 'node:stream'; import { createHandler } from '../../helpers/createHandler.js'; import { createLogger } from 'vite'; import React__default from 'react'; import { collectManifestClientFiles } from '../../collect-manifest-client-files.js'; async function renderPages(routes, files, options) { const failedRoutes = /* @__PURE__ */ new Map(); const completedRoutes = /* @__PURE__ */ new Set(); const clientCss = options.clientCss ?? []; const partialPageData = /* @__PURE__ */ new Map(); const moduleRootPath = options.moduleBasePath !== "" && !options.moduleRootPath.endsWith(options.moduleBasePath) ? join(options.moduleRootPath, options.moduleBasePath) : options.moduleRootPath; const mergeAndSendPageData = async (route, resolve) => { const partial = partialPageData.get(route); if (!partial?.html || !partial.rsc) { return; } const pageData = { route, html: partial.html, rsc: partial.rsc }; let routeHtmlPath = route === "/" ? options.htmlOutputPath : options.htmlOutputPath.replace( "index.html", join(route, "index.html") ); if (routeHtmlPath.startsWith("/")) { routeHtmlPath = routeHtmlPath.slice(1); } const routeRscPath = routeHtmlPath.slice(0, -5) + ".rsc"; await mkdir(dirname(routeHtmlPath), { recursive: true }); await writeFile(routeRscPath, partial.rsc.content); await writeFile(routeHtmlPath, partial.html.raw); await options.onPage?.(pageData); completedRoutes.add(route); if (completedRoutes.size === routes.length) { resolve(); } }; try { const allRoutesComplete = new Promise((resolve, reject) => { options.worker.on("message", async (msg) => { switch (msg.type) { case "ALL_READY": { const { id, html } = msg; try { const partial = partialPageData.get(id) || { route: id }; partial.html = { raw: html, transformed: typeof options.transformIndexHtml === "function" ? String(await options.transformIndexHtml(id, { path: id, filename: join(id, "index.html") }) || "") : "", // Will be set by main thread transform assets: [] }; partialPageData.set(id, partial); await mergeAndSendPageData(id, resolve); } catch (error) { failedRoutes.set(id, error); reject(error); } break; } case "ERROR": { console.error("Worker error for route:", msg.id, msg.error); failedRoutes.set(msg.id, new Error(msg.error)); reject(new Error(msg.error)); break; } } }); }); for (const route of routes) { const routeFiles = files.urlMap.get(route); if (!routeFiles) { console.error("No files found for route:", route); failedRoutes.set(route, new Error(`No files found for ${route}`)); continue; } if (options.pipableStreamOptions?.importMap?.imports) { for (let [, value] of Object.entries( options.pipableStreamOptions?.importMap?.imports )) { options.onClientJSFile?.(value, route); } } const getCss = async (id) => { const cssFiles = collectManifestClientFiles({ manifest: options.serverManifest, root: options.root, pagePath: id }).cssFiles; return cssFiles; }; const pagePath = files.urlMap.get(route)?.page; const propsPath = files.urlMap.get(route)?.props; if (!pagePath) { throw new Error(`No page path found for ${route}`); } const rscResult = await createHandler({ root: options.root, url: route, route, getCss, loader: options.loader, cssFiles: clientCss, moduleBase: options.moduleBase, moduleBasePath: options.moduleBasePath, moduleRootPath, moduleBaseURL: options.moduleBaseURL, pipableStreamOptions: options.pipableStreamOptions ?? {}, inlineCss: options.inlineCss, Html: React__default.Fragment, CssCollector: options.CssCollector, pagePath, propsPath, pageExportName: options.pageExportName, propsExportName: options.propsExportName, logger: createLogger() }); const htmlResult = await createHandler({ root: options.root, url: route, route, getCss, loader: options.loader, cssFiles: clientCss, moduleBase: options.moduleBase, moduleBasePath: options.moduleBasePath, moduleRootPath, moduleBaseURL: options.moduleBaseURL, pipableStreamOptions: options.pipableStreamOptions, inlineCss: options.inlineCss, Html: options.Html, CssCollector: options.CssCollector, pagePath, propsPath, pageExportName: options.pageExportName, propsExportName: options.propsExportName, logger: createLogger() }); if (rscResult.type !== "success" || htmlResult.type !== "success") { if (rscResult.type !== "success") { if (rscResult.type !== "skip") { console.error("Handler failed for route:", route, rscResult.error); } } if (htmlResult.type !== "success") { if (htmlResult.type !== "skip") { console.error("Handler failed for route:", route, htmlResult.error); } } failedRoutes.set(route, new Error(`Handler failed for ${route}`)); continue; } await Promise.all([ // Handle RSC stream new Promise((resolve, reject) => { const chunks = []; const rscTransform = new Transform({ transform(chunk, _encoding, callback) { try { if (chunk) { chunks.push(Buffer.from(chunk)); callback(null, chunk); } } catch (error) { callback(error); } }, async flush(callback) { try { const rscContent = Buffer.concat(chunks).toString("utf-8"); const partial = partialPageData.get(route) || { route }; partial.rsc = { modules: [], // Will be parsed by the client content: rscContent }; partialPageData.set(route, partial); await mergeAndSendPageData(route, resolve); callback(); resolve(); } catch (error) { callback(error); reject(error); } } }); rscResult.stream.pipe(rscTransform); }), // Send HTML stream to worker new Promise((resolve) => { const htmlTransform = new Transform({ transform(chunk, _encoding, callback) { try { options.worker.postMessage({ type: "RSC_CHUNK", id: route, chunk: chunk.toString(), moduleRootPath, moduleBaseURL: options.moduleBaseURL, htmlOutputPath: options.htmlOutputPath, pipableStreamOptions: options.pipableStreamOptions }); callback(null, chunk); } catch (error) { callback(error); } }, flush(callback) { options.worker.postMessage({ type: "RSC_END", id: route }); callback(); resolve(); } }); htmlResult.stream.pipe(htmlTransform); }) ]); } await allRoutesComplete; } catch (error) { console.error("Render error:", error); throw error; } return { failedRoutes, completedRoutes }; } export { renderPages }; //# sourceMappingURL=renderPages.js.map