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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: css-in-js integration for styled-jsx and styled-components #14

Merged
merged 17 commits into from Aug 16, 2022
40 changes: 22 additions & 18 deletions app/layout.server.tsx
@@ -1,37 +1,41 @@
import '@/styles/globals.css';
import React from 'react';
import AddressBar from '@/ui/AddressBar.client';
import Header from '@/ui/Header.client';
import RootStyleRegistry from '@/ui/RootStyleRegistry.client';
import nextPackageJson from 'next/package.json';
import React from 'react';
import GlobalNav from './GlobalNav.client';
import Footer from '@/ui/Footer.client'

export default function RootLayout({ children }: { children: any }) {
return (
<html>
<head>
<title>Next.js Layouts and Routing Playground</title>
</head>
<body className="overflow-y-scroll bg-zinc-900">
<div className="grid grid-cols-[1fr,minmax(auto,240px),min(800px,100%),1fr] gap-x-8 py-12">
<div className="col-start-2">
<GlobalNav />
</div>
<RootStyleRegistry>
huozhi marked this conversation as resolved.
Show resolved Hide resolved
<body className="overflow-y-scroll bg-zinc-900">
<div className="grid grid-cols-[1fr,minmax(auto,240px),min(800px,100%),1fr] gap-x-8 py-8">
<Header />
<div className="col-start-2">
<GlobalNav />
</div>

<div className="col-start-3 space-y-6">
<AddressBar />
<div className="col-start-3 space-y-6">
<AddressBar />

<div className="rounded-xl border border-zinc-800 bg-black p-8">
{children}
<div className="rounded-xl border border-zinc-800 bg-black p-8">
{children}
</div>
</div>
</div>

<div className="col-start-3 col-end-4 mt-28">
<div className="flex space-x-6 text-sm text-zinc-600">
<div>React: {React.version}</div>
<div>Next: {nextPackageJson.version}</div>
</div>
<Footer
reactVersion={React.version}
nextVersion={nextPackageJson.version}
/>
</div>
</div>
</body>
</body>
</RootStyleRegistry>
</html>
);
}
5 changes: 3 additions & 2 deletions package.json
Expand Up @@ -17,9 +17,10 @@
},
"dependencies": {
"clsx": "^1.2.1",
"next": "^12.2.4",
"next": "^12.2.5-canary.1",
"react": "^0.0.0-experimental-9fcaf88d5-20220801",
"react-dom": "^0.0.0-experimental-9fcaf88d5-20220801"
"react-dom": "^0.0.0-experimental-9fcaf88d5-20220801",
"styled-components": "^6.0.0-alpha.7"
},
"devDependencies": {
"@types/node": "18.0.3",
Expand Down
40 changes: 40 additions & 0 deletions ui/Footer.client.tsx
@@ -0,0 +1,40 @@
export default function Footer({
reactVersion,
nextVersion,
}: {
reactVersion: string;
nextVersion: string;
}) {
return (
<div className="col-start-2 col-end-4 mt-28 flex items-center justify-between">
<style jsx>{`
.power-by {
color: rgb(82 82 91);
display: inline-flex;
align-items: center;
}
.power-by svg {;
huozhi marked this conversation as resolved.
Show resolved Hide resolved
}
.power-by-text {
margin-right: .25rem;
}
`}
</style>

<span className="power-by">
<span className="power-by-text">Powered by</span>
<svg height="20" viewBox="0 0 283 64" fill="none">
<path
fill="currentColor"
d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z"
></path>
</svg>
</span>

<div className="flex space-x-6 text-sm text-zinc-600">
<div>React: {reactVersion}</div>
<div>Next: {nextVersion}</div>
</div>
</div>
);
}
43 changes: 43 additions & 0 deletions ui/Header.client.tsx
@@ -0,0 +1,43 @@
import styled from 'styled-components';

const HeadContainer = styled.header`
position: relative;
height: 64px;
align-items: center;
padding: 0px 8px;
margin-bottom: 48px;
display: flex;
border: 0 solid #e5e7eb;
color: rgb(244 244 245);
grid-column-start: 2;
grid-column-end: 4;
`;

const Title = styled.span`
margin: 0 8px;
`;

const NextJsLogo = (props: any) => (
<svg
// style={transform:translateX(4%);shape-rendering:auto}
version="1.1"
viewBox="0 0 148 90"
// width="82"
huozhi marked this conversation as resolved.
Show resolved Hide resolved
xmlnsXlink="http://www.w3.org/1999/xlink"
{...props}
>
<path
d="M34.992 23.495h27.855v2.219H37.546v16.699h23.792v2.219H37.546v18.334h25.591v2.219H34.992v-41.69zm30.35 0h2.96l13.115 18.334 13.405-18.334L113.055.207 83.1 43.756l15.436 21.429H95.46L81.417 45.683 67.316 65.185h-3.018L79.85 43.756 65.343 23.495zm34.297 2.219v-2.219h31.742v2.219h-14.623v39.47h-2.554v-39.47H99.64zM.145 23.495h3.192l44.011 66.003L29.16 65.185 2.814 26.648l-.116 38.537H.145v-41.69zm130.98 38.801c-.523 0-.914-.405-.914-.928 0-.524.391-.929.913-.929.528 0 .913.405.913.929 0 .523-.385.928-.913.928zm2.508-2.443H135c.019.742.56 1.24 1.354 1.24.888 0 1.391-.535 1.391-1.539v-6.356h1.391v6.362c0 1.808-1.043 2.849-2.77 2.849-1.62 0-2.732-1.01-2.732-2.556zm7.322-.08h1.379c.118.853.95 1.395 2.149 1.395 1.117 0 1.937-.58 1.937-1.377 0-.685-.521-1.097-1.708-1.377l-1.155-.28c-1.62-.38-2.36-1.166-2.36-2.487 0-1.602 1.304-2.668 3.26-2.668 1.82 0 3.15 1.066 3.23 2.58h-1.354c-.13-.828-.85-1.346-1.894-1.346-1.1 0-1.832.53-1.832 1.34 0 .642.472 1.01 1.64 1.284l.987.243c1.838.43 2.596 1.178 2.596 2.53 0 1.72-1.33 2.799-3.453 2.799-1.987 0-3.323-1.029-3.422-2.637z"
fillRule="nonzero"
></path>
</svg>
);

export default function Header() {
return (
<HeadContainer>
<NextJsLogo height={40} fill={`rgb(244 244 245)`} />
<Title>The React Framework</Title>
</HeadContainer>
);
}
40 changes: 40 additions & 0 deletions ui/RootStyleRegistry.client.tsx
@@ -0,0 +1,40 @@
import React, { useState } from 'react'
// @ts-ignore
huozhi marked this conversation as resolved.
Show resolved Hide resolved
import { StyleRegistry, createStyleRegistry } from 'styled-jsx'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
import { useFlushEffects } from 'next/dist/client/components/hooks-client'

export default function RootStyleRegistry({ children } : { children: JSX.Element }) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we build this into Next.js somehow? I would love to prevent the world where folks need this in userland.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intended that to let users to create their own custom styles registry for any css-in-js, so that we can keep the bundle as minimal as possible on client side. Even styled-jsx we removed the built-in support for layouts. This approach was brought up by @sebmarkbage

What users need to do is:

  • Wrap the content under a client component
  • use useFlushEffects API in that client component to consume flushed style content for each streaming render

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Part of this story is that we discourage using either of these approaches since this approach doesn't work well inside Server Components since you have to resend them, and the client runtime overhead is not worth it compared to .css file generating solutions. If we were encouraging their usage then maybe the tradeoff of having them built-in would make more sense, but now it's a benefit that it's an opt-in to a solution we're not recommending.

This approach works well with existing mechanisms that these libraries use and it doesn't require useFlushEffects to be built-in to React.

That said, we might add something to React to make it easier to have this work out of the box. Not because we encourage it but because it's so heavily used for legacy purposes and it'll conflict with some other new features too. That would require heavy refactoring to these libraries anyway so it's not as useful in the short term anyway.

const [jsxStyleRegistry] = useState(() => createStyleRegistry())
const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet())
const styledJsxFlushEffect = () => {
const styles = jsxStyleRegistry.styles()
jsxStyleRegistry.flush()
return <>{styles}</>
}
const styledComponentsFlushEffect = () => {
const styles = styledComponentsStyleSheet.getStyleElement()
styledComponentsStyleSheet.seal()
return <>{styles}</>
}

useFlushEffects(() => {
return (
<>
{styledJsxFlushEffect()}
{styledComponentsFlushEffect()}
</>
)
})

// Only include style registry on server side for SSR
if (typeof window === 'undefined') {
return (
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
<StyleRegistry registry={jsxStyleRegistry}>{children}</StyleRegistry>
</StyleSheetManager>
)
}

return children
}