Skip to content

Instantly share code, notes, and snippets.

@developit
Last active October 13, 2020 09:09
Show Gist options
  • Save developit/e71922c340bcb93f983ac6bc037eab02 to your computer and use it in GitHub Desktop.
Save developit/e71922c340bcb93f983ac6bc037eab02 to your computer and use it in GitHub Desktop.
asdf

Preact CLI with styled-components: TypeScript Edition

This shows how to use styled-components with preact-cli.

Styles are collected and inlined during pre-rendering, with no extra build or configuration step required.

The only configuration change required is to add a constant, just to prevent SSR-specific styled-components code from being loaded on the client.

{
"presets": [
"preact-cli/babel"
],
"plugins": [
"babel-plugin-styled-components"
]
}
build
node_modules
package-lock.json
size-plugin.json
import styled, { ThemeProvider } from "styled-components";
import type { JSX } from "preact";
const Button = styled.button`
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border-radius: 3px;
color: ${props => props.theme.main};
border: 2px solid ${props => props.theme.main};
`;
const theme = {
main: "mediumseagreen"
};
export function App(): JSX.Element {
return (
<div>
<Button theme={{ main: "royalblue" }}>Ad hoc theme</Button>
<ThemeProvider theme={theme}>
<div>
<Button>Themed</Button>
<Button theme={{ main: "darkorange" }}>Overidden</Button>
</div>
</ThemeProvider>
</div>
);
}
import { App } from "./App";
import wrap from './styled-components-ssr';
export default wrap(App);
{
"name": "preact-cli-styled-components",
"version": "1.0.0",
"scripts": {
"dev": "preact watch",
"build": "preact build",
"serve": "serve build -s -n",
"start": "npm run -s build && npm run -s serve"
},
"eslintConfig": {
"extends": "preact"
},
"dependencies": {
"preact": "^10.3.4",
"preact-render-to-string": "^5.1.4",
"styled-components": "^5.0.1"
},
"devDependencies": {
"babel-plugin-styled-components": "^1.10.7",
"eslint": "^6.8.0",
"eslint-config-preact": "^1.1.1",
"preact-cli": "^3.0.0-rc.10",
"serve": "^11.3.0"
}
}
module.exports = (config, env, helpers) => {
// Define a `process.env.SSR` boolean constant:
const DefinePlugin = helpers.getPluginsByName(config, "DefinePlugin")[0];
DefinePlugin.plugin.definitions['process.env.SSR'] = String(env.ssr);
};
import { h, Fragment, ComponentType, JSX } from "preact";
let wrap = (App: ComponentType) => App;
// For SSR only: wrap the app to collect and append styles
if (process.env.SSR) {
// We use require() here so that these large interfaces don't get bundled into the client:
const {
ServerStyleSheet,
StyleSheetManager
}: typeof import("styled-components") = require("styled-components");
wrap = (App: ComponentType) => {
const sheet = new ServerStyleSheet();
// **This wrapper component is required.**
// It ensures getStyleElement() runs only after <App> has been rendered.
// Here's how:
// VDOM is rendered depth-first, and children are rendered in first-to-last order.
// By wrapping `sheet.getStyleElement()` in a component and placing that component
// *after* <StyleSheetManager><App/></StyleSheetManager>, we leverage the fact that
// <StyleTags> will always be "rendered" (and thus called) after <App> is rendered.
// This ensures `getStyleElement()` is invoked after rendering App, when styles are collected.
const StyleTags = () => {
// styled-components typings are broken and explicitly force React.Element, so we override:
return sheet.getStyleElement() as unknown as JSX.Element;
}
return (props) => (
<Fragment>
<StyleSheetManager sheet={sheet.instance}>
<App {...props} />
</StyleSheetManager>
<StyleTags />
</Fragment>
);
};
}
export default wrap;
{
"compileOnSave": false,
"compilerOptions": {
"reactNamespace": "preact",
"allowJs": true,
"jsx": "preserve",
"noEmit": true
},
"typeAcquisition": {
"enable": true
}
}
@pierremouchan
Copy link

Hello, Thanks for sharing this setup for Preact and Styled Components.
However, i end up with both styles injected in the index.html and inside the main bundle, do you have any clues on this ?

Thanks.
Screenshot 2020-10-13 at 10 46 12

@pierremouchan
Copy link

Screenshot 2020-10-13 at 10 58 53

Nevermind, it was caused by the critical inline CSS in App ^^

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment