Skip to content

Commit

Permalink
test(react-server): add client css demo
Browse files Browse the repository at this point in the history
  • Loading branch information
hi-ogawa committed Jun 4, 2024
1 parent ea81871 commit 8590023
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 16 deletions.
94 changes: 94 additions & 0 deletions packages/react-server/examples/basic/e2e/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,100 @@ test("react-server css hmr @dev", async ({ page, browser }) => {
}
});

test("client css @js", async ({ page }) => {
checkNoError(page);

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

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

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

await page.goto("/test/css");
await waitForHydration(page);

const checkClientState = await setupCheckClientState(page);

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

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 client normal")).toHaveCSS(
"background-color",
"rgb(250, 250, 123)",
);
}
});

// TODO: is this vite's default behavior?
test("client css module no hmr @dev", async ({ page, browser }) => {
checkNoError(page);

await page.goto("/test/css");
await waitForHydration(page);

// check client state is reset (i.e. no hmr)
await page.getByPlaceholder("test-input").fill("hello");

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

await expect(page.getByPlaceholder("test-input")).toHaveValue("");

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

test("server action @js", async ({ page }) => {
checkNoError(page);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"use client";

import "./css-client-normal.css";
import cssModule from "./css-client-module.module.css";

export function CssClientNormal() {
return <div id="css-client-normal">css client normal</div>;
}

export function CssClientModule() {
return <div className={cssModule.test}>css client module</div>;
}
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 dashed gray;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#css-client-normal {
background: rgb(250, 250, 200);
padding: 20px;
width: 200px;
border: 1px dashed gray;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import "./css-normal.css";
import { CssClientModule, CssClientNormal } from "./_client";
import cssModule from "./css-module.module.css";

export default function Page() {
Expand All @@ -8,6 +9,8 @@ export default function Page() {
<div className="flex flex-col gap-2">
<div id="css-normal">css normal</div>
<div className={cssModule.test}>css module</div>
<CssClientNormal />
<CssClientModule />
</div>
</div>
);
Expand Down
2 changes: 2 additions & 0 deletions packages/react-server/src/entry/browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
LayoutRoot,
LayoutStateContext,
ROUTER_REVALIDATE_KEY,
RouteAssetLinks,
RouteManifestContext,
routerRevalidate,
} from "../features/router/client";
Expand Down Expand Up @@ -160,6 +161,7 @@ export async function start() {
<RouteManifestContext.Provider
value={(globalThis as any).__routeManifest}
>
<RouteAssetLinks />
<LayoutRoot />
</RouteManifestContext.Provider>
</LayoutHandler>
Expand Down
18 changes: 4 additions & 14 deletions packages/react-server/src/entry/server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,10 @@ import type { SsrAssetsType } from "../features/assets/plugin";
import {
LayoutRoot,
LayoutStateContext,
RouteAssetLinks,
RouteManifestContext,
preloadAssetDeps,
} from "../features/router/client";
import {
type RouteManifest,
getRouteAssetDeps,
} from "../features/router/manifest";
import type { RouteManifest } from "../features/router/manifest";
import type { ServerRouterData } from "../features/router/utils";
import {
createModuleMap,
Expand Down Expand Up @@ -104,19 +101,12 @@ export async function renderHtml(

const routeManifest = await importRouteManifest();

function ServerPreload() {
preloadAssetDeps(getRouteAssetDeps(routeManifest, url.pathname));
return null;
}

const reactRootEl = (
<RouterContext.Provider value={router}>
<LayoutStateContext.Provider value={{ data: layoutPromise }}>
<RouteManifestContext.Provider
value={(globalThis as any).__routeManifest}
>
<RouteManifestContext.Provider value={routeManifest}>
<RouteAssetLinks />
<LayoutRoot />
<ServerPreload />
</RouteManifestContext.Provider>
</LayoutStateContext.Provider>
</RouterContext.Provider>
Expand Down
6 changes: 5 additions & 1 deletion packages/react-server/src/features/assets/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,11 @@ export function vitePluginServerAssets({
`/******* react-server ********/`,
collectStyle($__global.dev.reactServer, [ENTRY_REACT_SERVER]),
`/******* client **************/`,
collectStyle($__global.dev.server, [ENTRY_CLIENT]),
collectStyle($__global.dev.server, [
ENTRY_CLIENT,
// TODO: dev should also use RouteManifest to manage client css
...manager.rscUseClientIds,
]),
]);
return styles.join("\n\n");
}),
Expand Down
24 changes: 23 additions & 1 deletion packages/react-server/src/features/router/client.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import ReactDom from "react-dom";
import { useRouter } from "../../client";
import { ActionRedirectHandler } from "../server-action/client";
import {
type AssetDeps,
Expand Down Expand Up @@ -37,7 +38,7 @@ export function routerRevalidate() {
return { [ROUTER_REVALIDATE_KEY]: true };
}

export function preloadAssetDeps(deps: AssetDeps) {
function preloadAssetDeps(deps: AssetDeps) {
for (const href of deps.js) {
ReactDom.preloadModule(href);
}
Expand All @@ -46,6 +47,27 @@ export function preloadAssetDeps(deps: AssetDeps) {
}
}

export function RouteAssetLinks() {
const pathname = useRouter((s) => s.location.pathname);
const routeManifest = React.useContext(RouteManifestContext);
const deps = React.useMemo(
() => getRouteAssetDeps(routeManifest, pathname),
[pathname, routeManifest],
);
return (
<>
{deps.js.map((href) => (
<link key={href} rel="modulepreload" href={href} />
))}
{deps.css.map((href) => (
// @ts-expect-error precedence to force head rendering
// https://react.dev/reference/react-dom/components/link#special-rendering-behavior
<link key={href} rel="stylesheet" href={href} precedence="high" />
))}
</>
);
}

export const RouteManifestContext = React.createContext<RouteManifest>(
undefined!,
);
Expand Down

0 comments on commit 8590023

Please sign in to comment.