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

feat(react-server): handle css in react-server #205

Merged
merged 16 commits into from
Mar 19, 2024
Merged
1 change: 1 addition & 0 deletions packages/react-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pnpm preview

- `index.html`
- `src/entry-client.tsx`
- `src/entry-react-server.tsx`
- `src/routes/**/(page|layout).tsx`
- `"use client"`
- `"use server"`
Expand Down
86 changes: 81 additions & 5 deletions packages/react-server/examples/basic/e2e/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ test("404", async ({ page }) => {
await page.getByText("Not Found: /test/not-found").click();
});

test("@dev rsc hmr", async ({ page }) => {
test("rsc hmr @dev", async ({ page }) => {
checkNoError(page);

await page.goto("/test");
Expand All @@ -51,7 +51,7 @@ test("@dev rsc hmr", async ({ page }) => {
await checkClientState();
});

test("@dev common hmr", async ({ page }) => {
test("common hmr @dev", async ({ page }) => {
checkNoError(page);

await page.goto("/test");
Expand All @@ -71,7 +71,7 @@ test("@dev common hmr", async ({ page }) => {
await checkClientState();
});

test("@dev client hmr", async ({ page }) => {
test("client hmr @dev", async ({ page }) => {
checkNoError(page);

await page.goto("/test");
Expand All @@ -93,7 +93,7 @@ test("@dev client hmr", async ({ page }) => {
expect(await res.text()).toContain("<div>test-hmr-edit-div</div>");
});

test("css", async ({ page, browser }) => {
test("unocss", async ({ page, browser }) => {
await page.goto("/test");
await expect(page.getByRole("heading", { name: "RSC Experiment" })).toHaveCSS(
"font-weight",
Expand All @@ -107,10 +107,12 @@ test("css", async ({ page, browser }) => {
).toHaveCSS("font-weight", "700");
});

test("@dev css hmr", async ({ page, browser }) => {
test("unocss hmr @dev", async ({ page, browser }) => {
await page.goto("/test");
await page.getByText("hydrated: true").click();

const checkClientState = await setupCheckClientState(page);

await expect(page.getByRole("heading", { name: "RSC Experiment" })).toHaveCSS(
"font-weight",
"700",
Expand All @@ -123,6 +125,8 @@ test("@dev css hmr", async ({ page, browser }) => {
"300",
);

await checkClientState();

// verify new style is applied without js
const page2 = await browser.newPage({ javaScriptEnabled: false });
await page2.goto("/test");
Expand All @@ -131,6 +135,78 @@ test("@dev css hmr", async ({ page, browser }) => {
).toHaveCSS("font-weight", "300");
});

test("react-server css", async ({ 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)",
);
});

test("react-server css @nojs", async ({ browser }) => {
const page = await browser.newPage({ javaScriptEnabled: false });
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)",
);
});

test("react-server css hmr @dev", async ({ page, browser }) => {
await page.goto("/test/css");
await page.getByText("hydrated: true").click();

const checkClientState = await setupCheckClientState(page);

await expect(page.getByText("css normal")).toHaveCSS(
"background-color",
"rgb(250, 250, 200)",
);
await editFile("./src/routes/test/css/css-normal.css", (s) =>
s.replace("rgb(250, 250, 200)", "rgb(250, 250, 123)"),
);
await expect(page.getByText("css normal")).toHaveCSS(
"background-color",
"rgb(250, 250, 123)",
);

await expect(page.getByText("css module")).toHaveCSS(
"background-color",
"rgb(200, 250, 250)",
);
await editFile("./src/routes/test/css/css-module.module.css", (s) =>
s.replace("rgb(200, 250, 250)", "rgb(123, 250, 250)"),
);
await expect(page.getByText("css module")).toHaveCSS(
"background-color",
"rgb(123, 250, 250)",
);

await checkClientState();

// verify new style is applied without js
{
const page = await browser.newPage({ javaScriptEnabled: false });
await page.goto("/test/css");
await expect(page.getByText("css normal")).toHaveCSS(
"background-color",
"rgb(250, 250, 123)",
);
await expect(page.getByText("css module")).toHaveCSS(
"background-color",
"rgb(123, 250, 250)",
);
}
});

test("server action with js", async ({ page }) => {
await page.goto("/test/action");
await page.getByText("hydrated: true").click();
Expand Down
1 change: 1 addition & 0 deletions packages/react-server/examples/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"test-e2e-preview": "E2E_PREVIEW=1 playwright test",
"vc-build": "pnpm build && bash misc/vercel-serverless/build.sh",
"vc-release": "vercel deploy --prod --prebuilt misc/vercel-serverless",
"vc-release-staging": "vercel deploy --prebuilt misc/vercel-serverless",
"cf-build": "SSR_ENTRY=/src/adapters/cloudflare-workers.ts pnpm build && bash misc/cloudflare-workers/build.sh",
"cf-preview": "cd misc/cloudflare-workers && wrangler dev",
"cf-release": "cd misc/cloudflare-workers && wrangler deploy"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.test {
background: rgb(200, 250, 250);
padding: 20px;
width: 200px;
border: 1px solid gray;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#css-normal {
background: rgb(250, 250, 200);
padding: 20px;
width: 200px;
border: 1px solid gray;
}
14 changes: 14 additions & 0 deletions packages/react-server/examples/basic/src/routes/test/css/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import "./css-normal.css";
import cssModule from "./css-module.module.css";

export default function Page() {
return (
<div className="flex flex-col gap-2">
<h5 className="font-bold">css</h5>
<div className="flex flex-col gap-2">
<div id="css-normal">css normal</div>
<div className={cssModule.test}>css module</div>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default async function Layout(props: React.PropsWithChildren) {
"/test/action",
"/test/deps",
"/test/head",
"/test/css",
"/test/not-found",
]}
/>
Expand Down
1 change: 0 additions & 1 deletion packages/react-server/examples/basic/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export default defineConfig({
react(),
unocss(),
vitePluginReactServer({
entry: "/src/entry-react-server.tsx",
plugins: [testVitePluginVirtual()],
}),
vitePluginLogger(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { handler } from "@hiogawa/react-server/entry-react-server";
2 changes: 1 addition & 1 deletion packages/react-server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hiogawa/react-server",
"version": "0.1.0-pre.7",
"version": "0.1.0-pre.8",
"license": "MIT",
"type": "module",
"exports": {
Expand Down
5 changes: 3 additions & 2 deletions packages/react-server/src/entry/server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
initDomWebpackSsr,
invalidateImportCacheOnFinish,
} from "../lib/ssr";
import { invalidateModule } from "../plugin/utils";
import { ENTRY_REACT_SERVER, invalidateModule } from "../plugin/utils";

export async function handler(request: Request): Promise<Response> {
const reactServer = await importReactServer();
Expand All @@ -31,7 +31,7 @@ export async function importReactServer(): Promise<
typeof import("./react-server")
> {
if (import.meta.env.DEV) {
return __rscDevServer.ssrLoadModule(__rscEntry) as any;
return __rscDevServer.ssrLoadModule(ENTRY_REACT_SERVER) as any;
} else {
return import("/dist/rsc/index.js" as string);
}
Expand Down Expand Up @@ -65,6 +65,7 @@ export async function renderHtml(
if (import.meta.env.DEV) {
// ensure latest css
invalidateModule(__devServer, "\0virtual:ssr-assets");
invalidateModule(__devServer, "\0virtual:react-server-css.js");
invalidateModule(__devServer, "\0virtual:dev-ssr-css.css?direct");
}
const assets = (await import("virtual:ssr-assets" as string)).default;
Expand Down
32 changes: 17 additions & 15 deletions packages/react-server/src/plugin/css.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import type { ViteDevServer } from "vite";

// cf.
// cf
// https://github.com/hi-ogawa/vite-plugins/blob/3c496fa1bb5ac66d2880986877a37ed262f1d2a6/packages/vite-glob-routes/examples/demo/vite-plugin-ssr-css.ts
// https://github.com/remix-run/remix/blob/dev/packages/remix-dev/vite/styles.ts

export async function collectStyle(server: ViteDevServer, entries: string[]) {
const urls = await collectStyleUrls(server, entries);
const styles = await Promise.all(
urls.map(async (url) => {
const res = await server.transformRequest(url + "?direct");
return res?.code;
}),
);
return styles.filter(Boolean).join("\n\n");
}

export async function collectStyleUrls(
server: ViteDevServer,
entries: string[],
) {
const visited = new Set<string>();

async function traverse(url: string) {
Expand All @@ -27,20 +42,7 @@ export async function collectStyle(server: ViteDevServer, entries: string[]) {
// traverse
await Promise.all(entries.map((url) => traverse(url)));

const styles = await Promise.all(
[...visited].map(async (url) => {
if (!url.match(CSS_LANGS_RE)) {
return;
}
const mod = await server.ssrLoadModule(url);
if ("default" in mod && typeof mod["default"] === "string") {
return mod["default"];
}
return;
}),
);

return styles.filter(Boolean).join("\n");
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
Expand Down
Loading
Loading