- Runtime
- Usage
- Builds
- Core
- Uno (default)
- Attributify
- Mini
- Bundler Usage
- Preventing FOUC
- Zero runtime CSS-in-JS : Is this where great DX meets top-notch Web Performance?
- Background : CSS-in-JS, DX and Web Performance
- Speed Impact of styled-components
- Enter Zero Runtime CSS-in-JS
- Speed Comparison : CSS modules vs styled-components vs linaria
- Conclusion
Runtime
UnoCSS runtime provide a CDN build that runs the UnoCSS engine right in the browser. It will detect the DOM changes and generate the styles on the fly.
Usage
Add the following line to your index.html :
script src="https://cdn.jsdelivr.net/npm/@unocss/runtime">script>
script src="https://cdn.jsdelivr.net/npm/@unocss/runtime">script>
To configure UnoCSS (optional):
script> // pass unocss options window.__unocss = rules: [ // custom rules. ], presets: [ // custom presets. ], // . > script>
script> // pass unocss options window.__unocss = rules: [ // custom rules. ], presets: [ // custom presets. ], // . > script>
By default, the Uno preset is be applied.
The runtime does not come with preflights, if you want to have style resets, you can either add your own, or use one from Reset package.
link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@unocss/reset/normalize.min.css"> link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@unocss/reset/tailwind.min.css">
link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@unocss/reset/normalize.min.css"> link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@unocss/reset/tailwind.min.css">
Builds
Several builds are available for different use cases.
Core
script src="https://cdn.jsdelivr.net/npm/@unocss/runtime/core.global.js">script>
script src="https://cdn.jsdelivr.net/npm/@unocss/runtime/core.global.js">script>
Uno (default)
With @unocss/preset-uno preset:
script src="https://cdn.jsdelivr.net/npm/@unocss/runtime/uno.global.js">script>
script src="https://cdn.jsdelivr.net/npm/@unocss/runtime/uno.global.js">script>
Attributify
With @unocss/preset-uno and @unocss/preset-attributify presets:
script src="https://cdn.jsdelivr.net/npm/@unocss/runtime/attributify.global.js">script>
script src="https://cdn.jsdelivr.net/npm/@unocss/runtime/attributify.global.js">script>
Mini
With @unocss/preset-mini and @unocss/preset-attributify preset:
script src="https://cdn.jsdelivr.net/npm/@unocss/runtime/mini.global.js">script>
script src="https://cdn.jsdelivr.net/npm/@unocss/runtime/mini.global.js">script>
Bundler Usage
import initUnocssRuntime from '@unocss/runtime' initUnocssRuntime( /* options */ >)
import initUnocssRuntime from '@unocss/runtime' initUnocssRuntime( /* options */ >)
Preventing FOUC
Since UnoCSS runs after the DOM is present, there can be a «flash of unstyled content» (FOUC) which may leads the user to see the page as unstyled.
Use un-cloak attribute with CSS rules such as [un-cloak] < display: none >to hide the unstyled element until UnoCSS applies the styles for it.
div class="text-blue-500" un-cloak> This text will only be visible in blue color. div>
div class="text-blue-500" un-cloak> This text will only be visible in blue color. div>
Zero runtime CSS-in-JS : Is this where great DX meets top-notch Web Performance?
A few weeks ago, a client team asked for my inputs on the performance impact of the styled-components library. They were already using styled-components for one of their website frontends. But, as they were about to build a different frontend (for a different website), they wanted to evaluate if styled-components was the right choice in the ever-evolving frontend ecosystem. This was a novel task for me. As a frontend speed & scalability guy, I often get involved with the websites when their frontend choices & setup are long in place. And often, my first job is to find the solutions living within the constraints of those choices. But, here, I had to provide inputs to help make that choice. 😊
Background : CSS-in-JS, DX and Web Performance
Before I talk about the findings from the exercise, here’s a quick brief on the friction in the web development world with respect to CSS-in-JS, good developer experience (DX) and web performance.
- All the additional styling related JavaScript adds to the parse, compile and execution time on the visitors’ browsers.
- The UI rendering gets delayed as the styling JavaScript often executes after a lot of other JavaScript (because, SPAs!).
- The styling may not render if the JavaScript fails to execute. JavaScript errors are a lot more probable than CSS or HTML errors.
As a result of this, all the DX goodness of CSS-in-JS and top-notch web performance appear like an either-or.
Speed Impact of styled-components
So, what is the speed impact of using CSS-in-JS? To measure this, I created two versions of the below page (with NextJS) — one version styled via CSS modules and another with styled-components.
And, below are the measured performance numbers:
- When using styled-components with the right optimizations, the start of page render during loading of the SPA can be at-par with CSS modules based approach.
- But, the overhead of the additional JavaScript exhibits some overhead in interactivity and re-rendering of the components.
Enter Zero Runtime CSS-in-JS
I then decided to evaluate zero runtime CSS-in-JS. More specifically, I evaluated linaria and studied astroturf.
These are CSS-in-JS libraries so we get to write our styling within the component files. They extract the CSS into separate CSS files during the build process. They do not require runtime JavaScript execution for styling. These libraries achieve dynamic props based styling via CSS variables at runtime.
So, I created a linaria styled version of the same page used for benchmark. This gave an opportunity to compare how similar it’s styling is to styled-components:
const Button = styled.button` border-radius: 3px; margin: 0.5em 1em; padding: 0.25em 1em; $props => props.primary && props.color && css` background: $props.color>; border: 2px solid $props.color>; color: white; `> $props => !props.primary && props.color && css` background: transparent; border: 2px solid $props.color>; color: $props.color>; `> `;
const Button = styled.button` border-radius: 3px; margin: 0.5em 1em; padding: 0.25em 1em; background: $props => (props.primary ? props.color : 'transparent')>; color: $props => (props.primary ? 'white' : props.color)>; border-width: 2px; border-style: solid; border-color: $props => (props.color)>; `;
const Button = styled.button` border-radius: 3px; margin: 0.5em 1em; padding: 0.25em 1em; $props => props.primary && props.color && css` background: $props.color>; border: 2px solid $props.color>; color: white; `> $props => !props.primary && props.color && css` background: transparent; border: 2px solid $props.color>; color: $props.color>; `> `;
const Button = styled.button` border-radius: 3px; margin: 0.5em 1em; padding: 0.25em 1em; background: $props => (props.primary ? props.color : 'transparent')>; color: $props => (props.primary ? 'white' : props.color)>; border-width: 2px; border-style: solid; border-color: $props => (props.color)>; `;
Unrelated to performance, but something of paramount importance — linaria’s similarity can make it’s adoption easier and future migrations simpler. However, I’m not sure if other zero runtime CSS-in-JS libraries are as similar to styled-components as linaria.
Speed Comparison : CSS modules vs styled-components vs linaria
So, how does the linaria styled version of the same page perform compare to the other versions? Below are the performance numbers:
Based on the above charts:
- Styling with linaria reduces the amount of JavaScript shipped and executed when rendering the routes. This improves the interactivity and route rendering speed.
- The speed difference may be higher for larger screens and complex UI interactions.
Conclusion
Based on the undertaken benchmarks and having written (very limited) linaria styling, my findings from this exercise were as following:
- Zero runtime CSS-in-JS library like linaria definitely seems offer a better balance of great DX and top notch web performance.
- Linaria’s styling similarity to styled-components makes it’s adoption easier and future migrations simpler.
- That stated, the traction and the dev ecosystem for styled-components is miles ahead of linaria, astroturf or any other CSS-in-JS library (as of 2021).
Based on the above findings, we decided to undertake a more detailed evaluation of linaria. To do so, we plan to style a few components and pages for the new website with linaria. During this process, we plan to check on the following aspects:
- Whether critical CSS extraction for our website’s server-side rendering works smooth with linaria.
- Will some linaria limitations (like not supporting dynamic props based media queries) inhibit the styling work.
- Ensure all the stakeholders are aware of no IE support (since the CSS variables leveraged by linaria for dynamic styling do not work with IE).
Note : Source code for the benchmark used to derive the results shared above is located here.
You can follow me on my twitter. I mostly tweet about my web development, site speed and entrepreneurship experiences.