×

CSS-in-JS Performance Cost – Mitigating Strategies

MMS Founder
MMS Bruno Couriol

Article originally posted on InfoQ. Visit InfoQ

CSS-in-JS became popular in some contexts as a way to link a component logic to its styling. Aggelos Arvanitakis reminded developers about cases in which the cost of CSS-in-JS can no longer be neglected, and provided mitigating strategies.

Arvanitakis argued in an article that, notwithstanding the benefits delivered by CSS-in-JS, it may still create performance issues in some applications. Focusing on React, and two popular CSS-in-JS libraries (styled-components and emotion), Arvanitakis compared two versions of the same code, only one of which used CSS-in-JS styling. The unstyled version goes as follows:

import React from 'react';

const NormalDiv = props => <div {...props} />

const App = () => {
  const [randomValue, setRandomValue] = React.useState(0);
  return (
    <React.Fragment>
      {new Array(50).fill(null).map((__, i) => (
        <NormalDiv key={i}>Hello World</NormalDiv>
      ))}
      <button onClick={() => setRandomValue(Math.random())}>Force Rerender</button>
    </React.Fragment>
  );
};

The styled version is:

import styled from '@emotion/styled';

const StyledDiv = styled.div``;

const App = () => {
  const [randomValue, setRandomValue] = React.useState(0);
  return (
    <React.Fragment>
      {new Array(50).fill(null).map((__, i) => (
        <StyledDiv key={i}>Hello World</StyledDiv>
      ))}
      <button onClick={() => setRandomValue(Math.random())}>Force Rerender</button>
    </React.Fragment>
  );
};

The styled CSS-in-JS implementation appeared to take about 50% more time to render than the unstyled version. While in many occasions, the performance cost related to CSS-in-JS is hardly perceived, in other cases (for instance featuring large component trees), it may become impossible to neglect. Arvanitakis hypothesized that the performance cost observed with some libraries may be due to how they modify the component tree (using Context and adding Context.Consumer to read style values) and dynamically apply styling (<style> tags containing the dynamically injected CSS). Arvanitakis explained:

Everything was fine until I implemented a Table. I started noticing that the rendering felt slow, especially when the number of rows got more than 50. Thus, I opened my devtools to try and investigate it.
(…)
So, to wrap up, the combination of multiple Context consumers (which means additional elements that React has to coordinate) and the inherent housekeeping that dynamic styling goes with, may be slowing down your app.

Arvanitakis thus concluded with the following pieces of advice:

  1. Don’t over-compose styled components
  2. Prefer “static” components
  3. Avoid unneeded React re-renders
  4. Investigate whether a zero-runtime CSS-in-JS library can work for your project
    (…) If you app doesn’t need support for theming and doesn’t make heavy and complex use of the cssprop, then a zero-runtime CSS-in-JS library might be a good candidate. As a bonus you will shave ~12KB off your overall bundle size, since most CSS-in-JS libraries range between 10KB — 15KB, while zero-runtime ones (like linaria) are < 1KB.

Arvanitakis however cautioned that performance refactoring should occur only after experiencing or measuring performance issues. Oleg Isonen, the author of the JSS CSS-in-JS library, explained the tradeoff of the 4 commonly used CSS-in-JS strategies and linked to a performance benchmark comparing CSS-in-JS libraries (as of March 2019). A benchmark run with selected libraries turned out as follows:

benchmark run comparing CSS-in-JS libraries

CSS-in-JS may be limited to React-like component-based frameworks. Other popular frameworks such as Vue, Svelte, or Angular use other colocation strategies to give developers similar benefits (like scoped CSS and tree-shakeable CSS). Angular developers, for instance, may define their components with a html-like template file, a css file, and a JavaScript file. The .js file would then reference the other two:


import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export  class AppComponent {
  title = 'angular programming.';
}

The three .js, .css, and .html files are expected to be colocated in the same directory. Alternatively, Angular developers may prefer, when templates and styles are short or simple enough, to colocate template and style directly in the @Component definition, in the form of strings. Both approaches differ on syntax but provide the same scoping benefits.

Vue promotes a single .vue file, which encloses <style> tags containing CSS styling information, the html-like template, and JavaScript methods handling the component behavior. Similarly, Svelte reads component definitions in .svelte files which also contain in the same file, styling, templating, and logic information. CSS-in-JS is yet another form of colocation used by frameworks like React which use JavaScript render functions instead of templates.

As Evan Czaplicki, Elm’s creator, posited in Twitter, components are objects. Colocating and encapsulating style and template properties together with the methods handling the component logic seems to respond to the Single Responsibility Principle, as expressed by Robert C. Martin:

Another wording for the Single Responsibility Principle is:

Gather together the things that change for the same reasons. Separate those things that change for different reasons.

If you think about this you’ll realize that this is just another way to define cohesion and coupling. We want to increase the cohesion between things that change for the same reasons, and we want to decrease the coupling between those things that change for different reasons.

Arvanitakis’ full article contains additional profile information which the reader may review online.

Subscribe for MMS Newsletter

By signing up, you will receive updates about our latest information.

  • This field is for validation purposes and should be left unchanged.