Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: pass server css to browser via shared build state #352

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions packages/react-server/examples/basic/e2e/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,26 +418,36 @@ test("unocss hmr @dev", async ({ page, browser }) => {
).toHaveCSS("font-weight", "300");
});

test("react-server css @js", async ({ page }) => {
test("react-server css normal @js", async ({ page }) => {
checkNoError(page);

await page.goto("/test/css");
await expect(page.getByText("css normal")).toHaveCSS(
"background-color",
"rgb(250, 250, 200)",
);
await expect(page.getByText("css module")).toHaveCSS(
"background-color",
"rgb(200, 250, 250)",
);
});

testNoJs("react-server css @nojs", async ({ page }) => {
testNoJs("react-server css normal @nojs", async ({ page }) => {
await page.goto("/test/css");
await expect(page.getByText("css normal")).toHaveCSS(
"background-color",
"rgb(250, 250, 200)",
);
});

test("react-server css module @js", async ({ page }) => {
checkNoError(page);

await page.goto("/test/css");
await expect(page.getByText("css module")).toHaveCSS(
"background-color",
"rgb(200, 250, 250)",
);
});

testNoJs("react-server css module @nojs", async ({ page }) => {
await page.goto("/test/css");
await expect(page.getByText("css module")).toHaveCSS(
"background-color",
"rgb(200, 250, 250)",
Expand Down
45 changes: 33 additions & 12 deletions packages/react-server/src/features/assets/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import fs from "node:fs";
import path from "node:path";
import { tinyassert, typedBoolean } from "@hiogawa/utils";
import type { Manifest, Plugin, ViteDevServer } from "vite";
import {
type Manifest,
type Plugin,
type ViteDevServer,
isCSSRequest,
} from "vite";
import { $__global } from "../../lib/global";
import type { PluginStateManager } from "../../plugin";
import {
Expand All @@ -19,7 +23,7 @@ export interface SsrAssetsType {

export const SERVER_CSS_PROXY = "virtual:server-css-proxy.js";

export function vitePluginServerAssets({
export function serverAssetsPluginClient({
manager,
}: { manager: PluginStateManager }): Plugin[] {
return [
Expand Down Expand Up @@ -117,22 +121,39 @@ export function vitePluginServerAssets({
return code + `if (import.meta.hot) { import.meta.hot.accept() }`;
}
if (manager.buildType === "client") {
// TODO: probe manifest to collect css?
const files = await fs.promises.readdir("./dist/rsc/assets", {
withFileTypes: true,
});
const code = files
.filter((f) => f.isFile() && f.name.endsWith(".css"))
.map((f) => path.join(f.path, f.name))
.map((f) => `import "/${f}";\n`)
// shaky way to keep css module import side effect
const code = manager.serverCssIds
.map((url) => `export * from "${url}";\n`)
.join("");
return code;
return { code, map: null, moduleSideEffects: "no-treeshake" };
}
tinyassert(false);
}),
];
}

export function serverAssertsPluginServer({
manager,
}: { manager: PluginStateManager }): Plugin[] {
// TODO
// - css should be processed on server plugin? (currently only id is passed over to client)
// - css code split by route?
// - css ordering?
return [
{
name: serverAssertsPluginServer.name + ":build",
apply: "build",
buildEnd() {
if (manager.buildType === "rsc") {
manager.serverCssIds = [...this.getModuleIds()].filter((id) =>
isCSSRequest(id),
);
}
},
},
];
}

async function getIndexHtmlTransform(server: ViteDevServer) {
const html = await server.transformIndexHtml(
"/",
Expand Down
9 changes: 7 additions & 2 deletions packages/react-server/src/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
import { CSS_LANGS_RE } from "../features/assets/css";
import {
SERVER_CSS_PROXY,
vitePluginServerAssets,
serverAssertsPluginServer,
serverAssetsPluginClient,
} from "../features/assets/plugin";
import type { RouteManifest } from "../features/router/manifest";
import {
Expand Down Expand Up @@ -66,6 +67,8 @@ class PluginStateManager {
routeToClientReferences: Record<string, string[]> = {};
routeManifest?: RouteManifest;

serverCssIds: string[] = [];

// expose "use client" node modules to client via virtual modules
// to avoid dual package due to deps optimization hash during dev
nodeModules = {
Expand Down Expand Up @@ -147,6 +150,8 @@ export function vitePluginReactServer(options?: {

routeManifestPluginServer({ manager }),

serverAssertsPluginServer({ manager }),

// this virtual is not necessary anymore but has been used in the past
// to extend user's react-server entry like ENTRY_CLIENT_WRAPPER
createVirtualPlugin(
Expand Down Expand Up @@ -339,7 +344,7 @@ export function vitePluginReactServer(options?: {
ssrRuntimePath: RUNTIME_SERVER_PATH,
}),
...vitePluginClientUseClient({ manager }),
...vitePluginServerAssets({ manager }),
...serverAssetsPluginClient({ manager }),
...routeManifestPluginClient({ manager }),
createVirtualPlugin(ENTRY_CLIENT_WRAPPER.slice("virtual:".length), () => {
// dev
Expand Down