diff --git a/contributors.yml b/contributors.yml index e14a496fbb2..fccb8c76f9d 100644 --- a/contributors.yml +++ b/contributors.yml @@ -54,6 +54,7 @@ - graham42 - GregBrimble - hardingmatt +- helderburato - HenryVogt - hkan - Holben888 diff --git a/examples/emotion/.gitignore b/examples/emotion/.gitignore new file mode 100644 index 00000000000..3f7bf98da3e --- /dev/null +++ b/examples/emotion/.gitignore @@ -0,0 +1,6 @@ +node_modules + +/.cache +/build +/public/build +.env diff --git a/examples/emotion/README.md b/examples/emotion/README.md new file mode 100644 index 00000000000..c4a9e1ee58e --- /dev/null +++ b/examples/emotion/README.md @@ -0,0 +1,56 @@ +# Example app with [emotion](https://emotion.sh/) + +This example features how to use [emotion](https://emotion.sh/) with Remix. + +## How this implementation works + +### Emotion related files + +``` +- app/ + - styles/ + - client.context.tsx + - createEmotionCache.ts + - server.context.tsx + - entry.client.tsx + - entry.server.tsx + - root.tsx +``` + +1. `client.context.tsx` - Keeps the client context of styles and to reset based on the flush of Emotion styles sheets after every interaction into the state. +2. `createEmotionCache.ts` - Create an instance of [Emotion cache](https://emotion.sh/docs/@emotion/cache). +3. `server.context.tsx` - Keeps the server context mounted on `entry.server.tsx` + with the Emotion sheets cache. +4. `entry.client.tsx` - Every time that styles update and be re-injected it sets the + Emotion cache to a React state. +5. `entry.server.tsx` - Create the markup with the styles injected to serve on the server response. + +## Preview + +Open this example on [CodeSandbox](https://codesandbox.io/): + +[![Open in CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/remix-run/remix/tree/main/examples/emotion) + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/routes/index.tsx`. The page auto-updates as you edit the file. + +## Commands + +- `dev`: runs your application on `localhost:3000` +- `build`: creates the production build version +- `start`: starts a simple server with the build production code + +## Related Links + +[Emotion](https://emotion.sh/) diff --git a/examples/emotion/app/entry.client.tsx b/examples/emotion/app/entry.client.tsx new file mode 100644 index 00000000000..5705e50cf5a --- /dev/null +++ b/examples/emotion/app/entry.client.tsx @@ -0,0 +1,31 @@ +import { hydrate } from "react-dom"; +import { RemixBrowser } from "remix"; +import React, { useState } from "react"; +import { CacheProvider } from "@emotion/react"; +import createEmotionCache from "./styles/createEmotionCache"; +import ClientStyleContext from "./styles/client.context"; + +interface ClientCacheProviderProps { + children: React.ReactNode; +} + +function ClientCacheProvider({ children }: ClientCacheProviderProps) { + const [cache, setCache] = useState(createEmotionCache()); + + function reset() { + setCache(createEmotionCache()); + } + + return ( + + {children} + + ); +} + +hydrate( + + + , + document +); diff --git a/examples/emotion/app/entry.server.tsx b/examples/emotion/app/entry.server.tsx new file mode 100644 index 00000000000..8d584a44307 --- /dev/null +++ b/examples/emotion/app/entry.server.tsx @@ -0,0 +1,43 @@ +import { renderToString } from "react-dom/server"; +import { RemixServer } from "remix"; +import type { EntryContext } from "remix"; + +import createEmotionServer from "@emotion/server/create-instance"; +import { CacheProvider } from "@emotion/react"; +import createEmotionCache from "./styles/createEmotionCache"; +import ServerStyleContext from "./styles/server.context"; + +export default function handleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext +) { + const cache = createEmotionCache(); + const { extractCriticalToChunks } = createEmotionServer(cache); + + const html = renderToString( + + + + + + ); + + const chunks = extractCriticalToChunks(html); + + const markup = renderToString( + + + + + + ); + + responseHeaders.set("Content-Type", "text/html"); + + return new Response(`${markup}`, { + status: responseStatusCode, + headers: responseHeaders + }); +} diff --git a/examples/emotion/app/root.tsx b/examples/emotion/app/root.tsx new file mode 100644 index 00000000000..ca39a9a3ccf --- /dev/null +++ b/examples/emotion/app/root.tsx @@ -0,0 +1,111 @@ +import { + Links, + LiveReload, + Meta, + Outlet, + Scripts, + ScrollRestoration, + useCatch +} from "remix"; +import { useContext, useEffect } from "react"; +import { withEmotionCache } from "@emotion/react"; +import ServerStyleContext from "./styles/server.context"; +import ClientStyleContext from "./styles/client.context"; +import type { MetaFunction } from "remix"; + +import styled from "@emotion/styled"; + +const Container = styled("div")` + background-color: #ff0000; + padding: 1em; +`; + +export const meta: MetaFunction = () => { + return { title: "Remix with Emotion" }; +}; + +interface DocumentProps { + children: React.ReactNode; + title?: string; +} + +const Document = withEmotionCache( + ({ children, title }: DocumentProps, emotionCache) => { + const serverStyleData = useContext(ServerStyleContext); + const clientStyleData = useContext(ClientStyleContext); + + // Only executed on client + useEffect(() => { + // re-link sheet container + emotionCache.sheet.container = document.head; + + // re-inject tags + const tags = emotionCache.sheet.tags; + emotionCache.sheet.flush(); + tags.forEach(tag => { + (emotionCache.sheet as any)._insertTag(tag); + }); + + // reset cache to re-apply global styles + clientStyleData.reset(); + }, []); + + return ( + + + + + {title ? {title} : null} + + + {serverStyleData?.map(({ key, ids, css }) => ( +