UNPKG

one

Version:

One is a new React Framework that makes Vite serve both native and web.

154 lines (152 loc) 6.06 kB
import { VIRTUAL_SSR_CSS_ENTRY, VIRTUAL_SSR_CSS_HREF } from "../../constants.mjs"; let cssCache = null, routesPreWarmed = !1; const CSS_CACHE_TTL = 1e3; function SSRCSSPlugin(pluginOpts) { let server; return { name: "one-plugin-ssr-css", apply: "serve", configureServer(server_) { server = server_; const preWarmRoutes = async () => { if (!routesPreWarmed) { routesPreWarmed = !0; try { await server.transformRequest(pluginOpts.entries[0]); const routeFiles = await collectRouteFiles(server, pluginOpts.entries); await Promise.all(routeFiles.map(file => server.transformRequest(file).catch(() => {}))), process.env.ONE_DEBUG && console.log(`[one] Pre-warmed ${routeFiles.length} route modules for CSS collection`); } catch (err) { console.error("[one] Error pre-warming routes:", err); } } }; server.watcher.on("change", () => { cssCache = null; }), server.middlewares.use(async (req, res, next) => { if (req.url?.includes(VIRTUAL_SSR_CSS_HREF)) { await preWarmRoutes(), invalidateModule(server, "\0" + VIRTUAL_SSR_CSS_ENTRY + "?direct"); const now = Date.now(); if (cssCache && now - cssCache.timestamp < CSS_CACHE_TTL) { res.setHeader("Content-Type", "text/css"), res.setHeader("Cache-Control", "no-store"), res.setHeader("Vary", "*"), res.write(cssCache.css), res.end(); return; } const code = await collectStyle(server, pluginOpts.entries); cssCache = { css: code, timestamp: now }, res.setHeader("Content-Type", "text/css"), res.setHeader("Cache-Control", "no-store"), res.setHeader("Vary", "*"), res.write(code), res.end(); return; } next(); }); }, // virtual module // (use `startsWith` since Vite adds `?direct` for raw css request) resolveId(source, _importer, _options) { return source.startsWith(VIRTUAL_SSR_CSS_ENTRY) ? "\0" + source : void 0; }, async load(id, _options) { if (id.startsWith("\0" + VIRTUAL_SSR_CSS_ENTRY)) { const url = new URL(id.slice(1), "https://test.local"); let code = await collectStyle(server, pluginOpts.entries); return url.searchParams.has("direct") || (code = `export default ${JSON.stringify(code)}`), code; } }, // also expose via transformIndexHtml transformIndexHtml: { handler: async () => [{ tag: "link", injectTo: "head", attrs: { rel: "stylesheet", href: VIRTUAL_SSR_CSS_HREF, "data-ssr-css": !0 } }, { tag: "script", injectTo: "head", attrs: { type: "module" }, children: (/* js */ ` import { createHotContext } from "/@vite/client"; const hot = createHotContext("/__clear_ssr_css"); hot.on("vite:afterUpdate", () => { document .querySelectorAll("[data-ssr-css]") .forEach(node => node.remove()); }); `) }] } }; } function invalidateModule(server, id) { const mod = server.moduleGraph.getModuleById(id); mod && server.moduleGraph.invalidateModule(mod); } async function collectStyle(server, entries) { const { transform } = await import("lightningcss"), urls = await collectStyleUrls(server, entries); let out = (await Promise.all(urls.map(async url => { const code = (await server.transformRequest(url + "?direct"))?.code || "", prefix = `/* [collectStyle] ${url} */`; try { const buffer = Buffer.from(code), codeOut = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength), processed = transform({ filename: "code.css", code: codeOut, ...server.config.css.lightningcss }).code.toString(); return [prefix, processed]; } catch (err) { return console.error(` [one] Error post-processing CSS, leaving un-processed: ${err}`), [prefix, code]; } }))).flat().filter(Boolean).join(` `); try { const buffer = Buffer.from(out), codeOut = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength); out = transform({ filename: "code.css", code: codeOut, ...server.config.css.lightningcss }).code.toString(); } catch { console.error(" [one] Error post-processing merged CSS, leaving un-processed"); } return out; } async function collectStyleUrls(server, entries) { const visited = /* @__PURE__ */new Set(); async function traverse(url) { const [, id] = await server.moduleGraph.resolveUrl(url); if (visited.has(id)) return; visited.add(id); const mod = server.moduleGraph.getModuleById(id); mod && (await Promise.all([...mod.importedModules].map(childMod => traverse(childMod.url)))); } return await Promise.all(entries.map(e => server.transformRequest(e))), await Promise.all(entries.map(url => traverse(url))), [...visited].filter(url => url.match(CSS_LANGS_RE)); } const CSS_LANGS_RE = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/, DYNAMIC_IMPORT_RE = /import\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/g; async function collectRouteFiles(server, entries) { const routeFiles = []; for (const entry of entries) try { const result = await server.transformRequest(entry); if (!result?.code) continue; let match; for (; (match = DYNAMIC_IMPORT_RE.exec(result.code)) !== null;) { const importPath = match[1]; importPath && !importPath.includes("node_modules") && !importPath.startsWith("\0") && !importPath.startsWith("virtual:") && (importPath.endsWith(".tsx") || importPath.endsWith(".ts") || importPath.endsWith(".jsx") || importPath.endsWith(".js")) && routeFiles.push(importPath); } } catch {} return routeFiles; } export { SSRCSSPlugin, collectStyle }; //# sourceMappingURL=SSRCSSPlugin.mjs.map