UNPKG

vite-plugin-react-server

Version:
269 lines (266 loc) 9.56 kB
/** * vite-plugin-react-server * Copyright (c) Nico Brinkkemper * MIT License */ import { join, dirname } from 'node:path'; import { performance } from 'node:perf_hooks'; import React__default from 'react'; 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 { createHandler } from '../helpers/createHandler.js'; import { stat, readFile, mkdir, writeFile } from 'node:fs/promises'; import { getBundleManifest } from '../helpers/getBundleManifest.js'; import { createInputNormalizer } from '../helpers/inputNormalizer.js'; import { MIME_TYPES } from '../config/mimeTypes.js'; import { InlineCssCollector } from '../css-collector-inline.js'; import { CssCollector } from '../css-collector.js'; import { collectModuleGraphCss } from '../collect-manifest-client-files.js'; let resolvedConfig = null; let serverManifestPath = null; let loader = null; function reactServerPlugin(options) { const timing = { start: performance.now() }; let files; let cssModules = /* @__PURE__ */ new Set(); let buildCssFiles = /* @__PURE__ */ new Set(); let root = process.cwd(); let userOptions; let resolvedPages; let serverManifest = {}; 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:react-stream-server", enforce: "post", api: { meta: { timing }, addCssFile(path) { buildCssFiles.add(path); } }, configResolved(_resolvedConfig) { resolvedConfig = _resolvedConfig; serverManifestPath = join( userOptions.build.outDir, userOptions.build.server, ".vite/manifest.json" ); timing.configResolved = performance.now(); const plugins = resolvedConfig.plugins; const transformerIndex = plugins.findIndex( (p) => p.name === "vite:react-transform" ); const preserverIndex = plugins.findIndex( (p) => p.name === "vite-plugin-react-server:preserve-directives" ); if (transformerIndex === -1) { throw new Error("Transformer plugin not installed"); } if (preserverIndex < transformerIndex) { throw new Error( "Transformer plugin isn't installed or isn't running before preserver" ); } }, async configurePreviewServer(server) { if (root !== server.config.root) { root = server.config.root; } if (typeof loader !== "function") { loader = (id) => import(id); } const normalize = createInputNormalizer({ root, removeExtension: false, preserveModulesRoot: userOptions.build.preserveModulesRoot ? userOptions.moduleBase : undefined }); server.middlewares.use(async (req, res, next) => { const [key, value] = normalize(req.url); const fileRoot = key.startsWith("node_modules") ? root : join(root, userOptions.build.outDir, userOptions.build.static); try { const filePath = join(fileRoot, value); const stats = await stat(filePath); if (stats.isFile()) { const ext = value.slice(value.lastIndexOf(".")); const contentType = MIME_TYPES[ext] || "application/octet-stream"; res.setHeader("Content-Type", contentType); const content = await readFile(filePath); res.end(content); return; } next(); } catch (error) { console.log("Error serving static file:", error); next(); } }); }, async configureServer(server) { if (typeof loader !== "function") { loader = server.ssrLoadModule; } if (server.config.root !== root && typeof server.config.root === "string" && server.config.root !== process.cwd() && server.config.root !== "") { console.log( "[vite:plugin-react-server] Root dir changed in configureServer hook", server.config.root, root ); root = server.config.root; } const activeStreams = /* @__PURE__ */ new Set(); server.ws.on("restart", (path) => { console.log( "[vite-plugin-react-server] 🔧 Plugin changed, preparing for restart:", path ); for (const res of activeStreams) { res.writeHead(503, { "Content-Type": "text/x-component", "Retry-After": "1" }); res.end('{"error":"Server restarting..."}'); } activeStreams.clear(); }); server.middlewares.use(async (req, res, next) => { if (req.headers.accept !== "text/x-component") return next(); if (typeof loader !== "function") { loader = server.ssrLoadModule; } let route = req.url?.replace("/index.rsc", ""); if (!route || route === "") { route = "/"; } try { const handler = await createHandler({ root, url: typeof userOptions.moduleBaseURL === "string" && userOptions.moduleBaseURL !== "" ? new URL(route, userOptions.moduleBaseURL).href : route, route, getCss: async (id) => { const cssFiles = await collectModuleGraphCss({ moduleGraph: server.moduleGraph, pagePath: id, onCss: void 0 }); if (userOptions.inlineCss) { const InlineMap = /* @__PURE__ */ new Map(); await Promise.all(Array.from(cssFiles.entries()).map(async ([file, fileUrl]) => { const content = await server.ssrLoadModule(fileUrl + "?inline"); if (content) { InlineMap.set(file, { content: content["default"], path: file, type: "text/css" }); } })); return InlineMap; } return cssFiles; }, cssFiles: [], logger: createLogger(), loader, moduleBase: userOptions.moduleBase, moduleBasePath: userOptions.moduleBasePath, moduleBaseURL: userOptions.moduleBaseURL, moduleRootPath: root, pipableStreamOptions: userOptions.pipableStreamOptions, Html: React__default.Fragment, CssCollector: userOptions.inlineCss ? InlineCssCollector : CssCollector, onCssFile: void 0, inlineCss: userOptions.inlineCss, propsPath: files.urlMap.get(route)?.props ?? route, pagePath: files.urlMap.get(route)?.page ?? route, pageExportName: userOptions.pageExportName, propsExportName: userOptions.propsExportName }); if (handler.type === "success") { handler.stream?.pipe(res); } activeStreams.add(res); } finally { res.on("close", () => { activeStreams.delete(res); }); } }); }, async config(config, configEnv) { if (typeof config.root === "string" && config.root !== root && config.root !== process.cwd() && config.root !== "") { console.log( "[vite:plugin-react-server] Root dir changed in config hook", config.root, root ); root = config.root; } const resolvedPagesResult = await resolvePages(userOptions.build.pages); if (resolvedPagesResult.type === "error") { throw resolvedPagesResult.error; } resolvedPages = resolvedPagesResult.pages; files = await checkFilesExist(resolvedPages, userOptions, root); const resolvedConfig2 = resolveUserConfig({ isClient: false, config, configEnv, userOptions, files }); if (resolvedConfig2.type === "error") { throw resolvedConfig2.error; } return resolvedConfig2.userConfig; }, async buildStart() { if (!timing.buildStart) { timing.buildStart = performance.now(); } else { console.log("Build already started"); } }, handleHotUpdate({ file }) { if (file.endsWith(".css")) { cssModules.add(file); } }, async generateBundle(_options, bundle) { if (!resolvedConfig) { throw new Error("Resolved config not found"); } serverManifest = getBundleManifest({ pluginContext: this, bundle, moduleBase: userOptions.moduleBase, preserveModulesRoot: userOptions.build.preserveModulesRoot }); if (serverManifestPath) { await mkdir(dirname(serverManifestPath), { recursive: true }); await writeFile( serverManifestPath, JSON.stringify(serverManifest, null, 2) ); } } }; } export { reactServerPlugin }; //# sourceMappingURL=plugin.js.map