Zero-Runtime CSS From JS With Linaria
In short
Linaria is a JavaScript library allowing CSS to be written within JavaScript without incurring runtime costs. It extracts CSS to separate files during the build process, addressing issues like runtime overhead and duplicated CSS in JavaScript bundles. With features like support for JavaScript expressions at build time and seamless integration with tools like Webpack, Linaria aims to offer the flexibility and maintainability of CSS-in-JS without the associated runtime drawbacks.
Introduction to Linaria
Linaria is a library which allows you to write CSS inside your JavaScript without the runtime cost.
The CSS is extracted out to plain old CSS files with the Babel preset, and critical CSS can be determined with the included helpers.
The idea for Linaria came from css-literal-loader and the syntax of styled-components. It also uses the same parser as styled-components. Glam by Sunil Pai has been another great source of inspiration.
The API looks like this:
You write CSS syntax (plus nesting) in a tagged template literal and tag it with the css function from Linaria. It returns a class name for you to use. This gets optimized with the Babel preset which reduces it down to just a class name:
It’s possible to use JavaScript expressions inside the template literal and they will be evaluated at build time, useful for shared utility functions and constants:
You can also compose multiple styles together with the include helper:
which is equivalent to writing:
Features
- CSS is extracted at build time, no runtime is included
- JavaScript expressions such as function calls, variables, conditionals etc. are supported and evaluated at build time
- CSS can be extracted for inlining during SSR
- CSS syntax with Sass like nesting
- Integrates with existing tools like Webpack to provide features such as Hot Reload
Why?
Existing CSS in JS libraries are pretty amazing as they provide several benefits over the traditional way of writing CSS, making it much more maintainable. They also come with its costs:
- CSS needs to be parsed, auto-prefixed and applied to the document at runtime
- Bundle-size is increased as the CSS parser needs to be bundled with JavaScript bundle
- CSS code is bundled with JavaScript, making it impossible to download and parse both in parallel
- Even if you do SSR and serve CSS in parallel, the same CSS is still duplicated in the JavaScript bundled, and wastefully parsed again
These costs may not be significant for all applications, and most of the time, it outweighs the benefits gained from CSS in JS.
The main goal of Linaria is to provide a similar level of flexibility and maintainability while not having the runtime cost associated with it.
How does it differ from CSS modules?
It’s pretty similar, except a few key differences:
- You can write CSS in the same file as rest of the component, no back n forth between files
- You can share configuration between your CSS and JavaScript without having to keep multiple files in sync
- You can just use JavaScript for loops, conditionals etc., along with various JavaScript libraries like polished
- You can make sure that you don’t have any CSS you don’t use with linters like ESLint, and it also works with tools likes Flow or Jest
How does it differ from other CSS in JS libraries?
Despite the similarities, there are many differences between Linaria and the existing CSS in JS libraries:
- The CSS is extracted out from the JavaScript completely, and no runtime is left
- It is required that all the CSS can be evaluated at build time, which means it’s currently not possible to use dynamic values like props
- The CSS can be served as a separate file, improving load time by parallelizing the downloads and decreasing parsing time
When it comes to Emotion, there's a separate post on how Linaria differs from Emotion and styled components.
Want to give it a try? Check Linaria on GitHub, and don't forget to star it. We’re still actively working on it, so please report any issues you find. Pull requests are always welcome.