{"id":317368,"date":"2020-07-29T07:27:17","date_gmt":"2020-07-29T14:27:17","guid":{"rendered":"https:\/\/css-tricks.com\/?p=317368"},"modified":"2020-07-29T07:27:18","modified_gmt":"2020-07-29T14:27:18","slug":"style9-build-time-css-in-js","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/style9-build-time-css-in-js\/","title":{"rendered":"style9: build-time CSS-in-JS"},"content":{"rendered":"\n

In April of last year, Facebook revealed its big new redesign. An ambitious project, it was a rebuild of a large site with a massive amount of users. To accomplish this, they used several technologies they have created and open-sourced, such as React, GraphQL, Relay, and a new CSS-in-JS library called stylex<\/a>.<\/p>\n\n\n\n

This new library is internal to Facebook, but they have shared enough information about it to make an open-source implementation, style9<\/a>, possible.<\/p>\n\n\n\n\n\n\n

Why another CIJ library?<\/h3>\n\n\n

There are already plenty of CSS-in-JS (CIJ) libraries, so it might not be obvious why another one is needed. style9 has the same benefits as all other CIJ solutions, as articulated by Christopher Chedeau<\/a>, including scoped selectors, dead code elimination, deterministic resolution, and the ability to share values between CSS and JavaScript.<\/p>\n\n\n\n

There are, however, a couple of things that make style9 unique.<\/p>\n\n\n

Minimal runtime<\/h4>\n\n\n

Although the styles are defined in JavaScript, they are extracted by the compiler into a regular CSS file. That means that no styles are shipped in your final JavaScript file. The only things that remain are the final class names, which the minimal runtime will conditionally apply, just like you would normally do. This results in smaller code bundles, a reduction in memory usage, and faster rendering.<\/p>\n\n\n\n

Since the values are extracted at compile time, truly dynamic values can\u2019t be used. These are thankfully not very common and since they are unique, don\u2019t suffer from being defined inline. What\u2019s more common is conditionally applying styles, which of course is supported. So are local constants and mathematical expressions, thanks to babel\u2019s path.evaluate<\/code>.<\/p>\n\n\n

Atomic output<\/h4>\n\n\n

Because of how style9 works, every property declaration can be made into its own class with a single property. So, for example, if we use opacity: 0<\/code> in several places in our code, it will only exist in the generated CSS once. The benefit of this is that the CSS file grows with the number of unique declarations<\/em>, not with the total amount of declarations. Since most properties are used many times, this can lead to dramatically smaller CSS files. For example, Facebook\u2019s old homepage used 413 KB of gzipped CSS. The redesign uses 74 KB for all<\/em> pages. Again, smaller file size leads to better performance.<\/p>\n\n\n\n

\"\"
Slide from Building the New Facebook with React and Relay by Frank Yan<\/a>, at 13:23 showing the logarithmic scale of atomic CSS.<\/figcaption><\/figure>\n\n\n\n

Some may complain about this, that the generated class names are not semantic, that they are opaque and are ignoring the cascade. This is true. We are treating CSS as a compilation target. But for good reason. By questioning previously assumed best practices, we can improve both the user and developer experience.<\/p>\n\n\n\n

In addition, style9 has many other great features, including: typed styles using TypeScript, unused style elimination, the ability to use JavaScript variables, and support for media queries, pseudo-selectors and keyframes.<\/p>\n\n\n

Here’s how to use it<\/h3>\n\n\n

First, install it like usual:<\/p>\n\n\n\n

npm install style9<\/code><\/pre>\n\n\n\n

style9 has plugins for Rollup, Webpack, Gatsby, and Next.js, which are all based on a Babel plugin. Instructions on how to use them are available in the repository<\/a>. Here, we\u2019ll use the webpack plugin.<\/p>\n\n\n\n

const Style9Plugin = require('style9\/webpack');\nconst MiniCssExtractPlugin = require('mini-css-extract-plugin');\n\nmodule.exports = {\n  module: {\n    rules: [\n      \/\/ This will transform the style9 calls\n      {\n        test: \/\\.(tsx|ts|js|mjs|jsx)$\/,\n        use: Style9Plugin.loader\n      },\n      \/\/ This is part of the normal Webpack CSS extraction\n      {\n        test: \/\\.css$\/i,\n        use: [MiniCssExtractPlugin.loader, 'css-loader']\n      }\n    ]\n  },\n  plugins: [\n    \/\/ This will sort and remove duplicate declarations in the final CSS file\n    new Style9Plugin(),\n    \/\/ This is part of the normal Webpack CSS extraction\n    new MiniCssExtractPlugin()\n  ]\n};<\/code><\/pre>\n\n\n

Defining styles<\/h3>\n\n\n

The syntax for creating styles closely resembles other libraries. We start by calling style9.create<\/code> with objects of styles:<\/p>\n\n\n\n

import style9 from 'style9';\n\nconst styles = style9.create({\n  button: {\n    padding: 0,\n    color: 'rebeccapurple'\n  },\n  padding: {\n    padding: 12\n  },\n  icon: {\n    width: 24,\n    height: 24\n  }\n});<\/code><\/pre>\n\n\n\n

Because all declarations will result in atomic classes, shorthands such as flex: 1<\/code> and background: blue<\/code> won\u2019t work, as they set multiple properties. Properties that can be can be expanded, such as padding<\/code>, margin<\/code>, overflow<\/code>, etc. will be automatically converted to their longhand variants. If you use TypeScript, you will get an error when using unsupported properties.<\/p>\n\n\n

Resolving styles<\/h3>\n\n\n

To generate a class name, we can now call the function returned by style9.create<\/code>. It accepts as arguments the keys of the styles we want to use:<\/p>\n\n\n\n

const className = styles('button');<\/code><\/pre>\n\n\n\n

The function works in such a way that styles on the right take precedence and will be merged with the styles on the left, like Object.assign<\/code>. The following would result in an element with a padding of 12px and with rebeccapurple<\/code> text.<\/p>\n\n\n\n

const className = styles('button', 'padding');<\/code><\/pre>\n\n\n\n

We can conditionally apply styles using any of the following formats:<\/p>\n\n\n\n

\/\/ logical AND\nstyles('button', hasPadding && 'padding');\n\/\/ ternary\nstyles('button', isGreen ? 'green' : 'red');\n\/\/ object of booleans\nstyles({\n  button: true,\n  green: isGreen,\n  padding: hasPadding\n});<\/code><\/pre>\n\n\n\n

These function calls will be removed during compilation and replaced with direct string concatenation. The first line in the code above will be replaced with something like 'c1r9f2e5 ' + hasPadding ? 'cu2kwdz ' : ''<\/code>. No runtime is left behind.<\/p>\n\n\n

Combining styles<\/h3>\n\n\n

We can extend a style object by accessing it with a property name and passing it to style9<\/code>.<\/p>\n\n\n\n

const styles = style9.create({ blue: { color: 'blue; } });\nconst otherStyles = style9.create({ red: { color: 'red; } });\n\n\/\/ will be red\nconst className = style9(styles.blue, otherStyles.red);<\/code><\/pre>\n\n\n\n

Just like with the function call, the styles on the right take precedence. In this case, however, the class name can’t be statically resolved. Instead the property values will be replaced by classes and will be joined at runtime. The properties gets added to the CSS file just like before.<\/p>\n\n\n

Summary<\/h3>\n\n\n

The benefits of CSS-in-JS are very much real. That said, we are imposing a performance cost when embedding styles in our code. By extracting the values during build-time we can have the best of both worlds. We benefit from co-locating our styles with our markup and the ability to use existing JavaScript infrastructure, while also being able to generate optimal stylesheets.<\/p>\n\n\n\n

If style9 sounds interesting to you, have a look a the repo and try it out. And if you have any questions, feel free to open an issue or get in touch.<\/p>\n\n\n

Acknowledgements<\/h4>\n\n\n

Thanks to Giuseppe Gurgone for his work on style-sheet<\/a> and dss<\/a>, Nicolas Gallagher for react-native-web<\/a>, Satyajit Sahoo and everyone at Callstack for linaria<\/a>, Christopher Chedeau, Sebastian McKenzie, Frank Yan, Ashley Watkins, Naman Goel, and everyone else who worked on stylex at Facebook for being kind enough to share their lessons publicly. And anyone else I have missed.<\/p>\n\n\n

Links<\/h4>\n\n\n