From 6b4a4ed9d9e9f735562b5ecc9ea05355bd520698 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 28 Apr 2024 19:14:26 +0900 Subject: [PATCH] feat(react-server): style --- .../src/features/bootstrap/css.ts | 89 +++++++++++++++++++ .../src/features/bootstrap/plugin.ts | 10 ++- examples/react-server/vite.config.ts | 6 ++ 3 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 examples/react-server/src/features/bootstrap/css.ts diff --git a/examples/react-server/src/features/bootstrap/css.ts b/examples/react-server/src/features/bootstrap/css.ts new file mode 100644 index 00000000..e1bf1113 --- /dev/null +++ b/examples/react-server/src/features/bootstrap/css.ts @@ -0,0 +1,89 @@ +import type { DevEnvironment, PluginOption } from "vite"; +import { $__global } from "../../global"; +import { createVirtualPlugin } from "../utils/plugin"; + +// +// environment api port of +// https://github.com/hi-ogawa/vite-plugins/tree/main/packages/ssr-css +// + +export const SSR_CSS_ENTRY = "virtual:ssr-css.css"; + +export function vitePluginSsrCss(): PluginOption { + return [ + { + name: vitePluginSsrCss.name + ":invalidate", + configureServer(server) { + server.middlewares.use((req, _res, next) => { + if (req.url === `/@id/__x00__${SSR_CSS_ENTRY}`) { + const { moduleGraph } = $__global.server.environments["client"]; + const mod = moduleGraph.getModuleById(`\0${SSR_CSS_ENTRY}?direct`); + if (mod) { + moduleGraph.invalidateModule(mod); + } + } + next(); + }); + }, + }, + createVirtualPlugin("ssr-css.css?direct", async () => { + const styles = await Promise.all([ + `/****** react-server ********/`, + collectStyle($__global.server.environments["react-server"], [ + "/src/entry-react-server", + ]), + `/****** client **************/`, + collectStyle($__global.server.environments["client"], [ + "/src/entry-client", + ]), + ]); + return styles.join("\n\n"); + }), + ]; +} + +async function collectStyle(server: DevEnvironment, entries: string[]) { + const urls = await collectStyleUrls(server, entries); + const codes = await Promise.all( + urls.map(async (url) => { + const res = await server.transformRequest(url + "?direct"); + return [`/*** ${url} ***/`, res?.code]; + }), + ); + return codes.flat().filter(Boolean).join("\n\n"); +} + +async function collectStyleUrls( + server: DevEnvironment, + entries: string[], +): Promise { + const visited = new Set(); + + async function traverse(url: string) { + const [, id] = await server.moduleGraph.resolveUrl(url); + if (visited.has(id)) { + return; + } + visited.add(id); + const mod = server.moduleGraph.getModuleById(id); + if (!mod) { + return; + } + await Promise.all( + [...mod.importedModules].map((childMod) => traverse(childMod.url)), + ); + } + + // ensure vite's import analysis is ready _only_ for top entries to not go too aggresive + await Promise.all(entries.map((e) => server.transformRequest(e))); + + // traverse + await Promise.all(entries.map((url) => traverse(url))); + + // filter + return [...visited].filter((url) => url.match(CSS_LANGS_RE)); +} + +// cf. https://github.com/vitejs/vite/blob/d6bde8b03d433778aaed62afc2be0630c8131908/packages/vite/src/node/constants.ts#L49C23-L50 +const CSS_LANGS_RE = + /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/; diff --git a/examples/react-server/src/features/bootstrap/plugin.ts b/examples/react-server/src/features/bootstrap/plugin.ts index a2b7bbf9..05218a4d 100644 --- a/examples/react-server/src/features/bootstrap/plugin.ts +++ b/examples/react-server/src/features/bootstrap/plugin.ts @@ -3,6 +3,7 @@ import { tinyassert, typedBoolean } from "@hiogawa/utils"; import type { Manifest, PluginOption, ViteDevServer } from "vite"; import { $__global } from "../../global"; import { createVirtualPlugin } from "../utils/plugin"; +import { SSR_CSS_ENTRY, vitePluginSsrCss } from "./css"; export const ENTRY_CLIENT_BOOTSTRAP = "virtual:entry-client-bootstrap"; @@ -31,7 +32,11 @@ export function vitePluginEntryBootstrap(): PluginOption { createVirtualPlugin("ssr-assets", async () => { let ssrAssets: SsrAssets; if ($__global.server) { - const { head } = await getIndexHtmlTransform($__global.server); + let { head } = await getIndexHtmlTransform($__global.server); + head = [ + head, + ``, + ].join("\n"); ssrAssets = { head, bootstrapModules: ["/@id/__x00__" + ENTRY_CLIENT_BOOTSTRAP], @@ -56,12 +61,13 @@ export function vitePluginEntryBootstrap(): PluginOption { ...js.map((href) => ``), ].join("\n"); ssrAssets = { - bootstrapModules: [`/${entry.file}`], head, + bootstrapModules: [`/${entry.file}`], }; } return `export default ${JSON.stringify(ssrAssets)}`; }), + vitePluginSsrCss(), ]; } diff --git a/examples/react-server/vite.config.ts b/examples/react-server/vite.config.ts index 9606458e..173bd5e4 100644 --- a/examples/react-server/vite.config.ts +++ b/examples/react-server/vite.config.ts @@ -42,6 +42,12 @@ export default defineConfig((_env) => ({ environments: { client: { + dev: { + optimizeDeps: { + // [feedback] no optimizeDeps.entries for initial scan? + // entries: [] + }, + }, build: { outDir: "dist/client", minify: false,