diff --git a/contributors.yml b/contributors.yml index e71ae938dab..76bd120044d 100644 --- a/contributors.yml +++ b/contributors.yml @@ -381,3 +381,4 @@ - zachdtaylor - zainfathoni - zhe +- guatedude2 diff --git a/examples/theme-ui/.eslintrc.js b/examples/theme-ui/.eslintrc.js new file mode 100644 index 00000000000..ced78085f86 --- /dev/null +++ b/examples/theme-ui/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"], +}; diff --git a/examples/theme-ui/.gitignore b/examples/theme-ui/.gitignore new file mode 100644 index 00000000000..3f7bf98da3e --- /dev/null +++ b/examples/theme-ui/.gitignore @@ -0,0 +1,6 @@ +node_modules + +/.cache +/build +/public/build +.env diff --git a/examples/theme-ui/README.md b/examples/theme-ui/README.md new file mode 100644 index 00000000000..99bf17112be --- /dev/null +++ b/examples/theme-ui/README.md @@ -0,0 +1,49 @@ +# Example app with [theme-ui](https://theme-ui.com/) + +This example features how to use [theme-ui](https://theme-ui.com/) with Remix. + +## How this implementation works + +Since [theme-ui](https://theme-ui.com/) is derived from [emotion](https://emotion.sh/), this example shows how we can leverage Emotion's server-side-caching to enable [theme-ui](https://theme-ui.com/) server side rendering. + +*This implementation was based off [Saas-UI Remix guide](https://www.saas-ui.dev/docs/core/installation/remix-guide).* + +### Theme-UI Related files + +``` +- app/ + - styles/ - context.tsx - createEmotionCache.tsx + - entry.client.tsx + - entry.server.tsx + - root.tsx +``` + +1. `context.tsx` - Creates the server and client context. +2. `createEmotionCache.ts` - Create an instance of [Emotion cache](https://emotion.sh/docs/@emotion/cache). +4. `entry.client.tsx` - Consumes the emotion cache generated by the server and creates a provider that is then passed on the the app. +5. `entry.server.tsx` - Create the markup with the styles injected to serve on the server response. + + +## 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 + +[Theme-UI](https://theme-ui.com/) diff --git a/examples/theme-ui/app/entry.client.tsx b/examples/theme-ui/app/entry.client.tsx new file mode 100644 index 00000000000..cbaa3bb7c0c --- /dev/null +++ b/examples/theme-ui/app/entry.client.tsx @@ -0,0 +1,32 @@ +import React, { useState } from 'react'; +import { hydrate } from 'react-dom'; +import { CacheProvider } from '@emotion/react'; +import { RemixBrowser } from '@remix-run/react'; + +import { ClientStyleContext } from './styles/context'; +import createEmotionCache from './styles/createEmotionCache'; + +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/theme-ui/app/entry.server.tsx b/examples/theme-ui/app/entry.server.tsx new file mode 100644 index 00000000000..e5e2acca873 --- /dev/null +++ b/examples/theme-ui/app/entry.server.tsx @@ -0,0 +1,38 @@ +import { renderToString } from 'react-dom/server'; +import { CacheProvider } from '@emotion/react'; +import createEmotionServer from '@emotion/server/create-instance'; +import { RemixServer } from '@remix-run/react'; +import type { EntryContext } from '@remix-run/node'; // Depends on the runtime you choose + +import { ServerStyleContext } from './styles/context'; +import createEmotionCache from './styles/createEmotionCache'; + +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/theme-ui/app/root.tsx b/examples/theme-ui/app/root.tsx new file mode 100644 index 00000000000..2cddc17c20d --- /dev/null +++ b/examples/theme-ui/app/root.tsx @@ -0,0 +1,64 @@ +import React, { useContext, useEffect } from 'react'; +import { withEmotionCache } from '@emotion/react'; +import { ThemeProvider } from '@theme-ui/core'; +import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react'; +import type { MetaFunction, LinksFunction } from '@remix-run/node'; // Depends on the runtime you choose + +import { ServerStyleContext, ClientStyleContext } from './styles/context'; + +export const meta: MetaFunction = () => ({ + charset: 'utf-8', + title: 'New Remix App', + viewport: 'width=device-width,initial-scale=1', +}); + +interface DocumentProps { + children: React.ReactNode; +} + +const Document = withEmotionCache(({ children }: 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 reapply global styles + clientStyleData?.reset(); + }, []); + + return ( + + + + + {serverStyleData?.map(({ key, ids, css }) => ( +