Migrating to Linaria

Linaria is a zero-runtime CSS-in-JS library with an API similar to Emotion, but with significantly improved performance.

The documentation for Linaria can be found on Github, and is worth reading through. We’ll address the differences with Emotion 9 and the migration steps below.

Why Linaria?

In our testing, switching from Emotion to Linaria improves component render time by ~25-30%. This is because Linaria is a zero-runtime CSS-in-JS solution. Whilst Emotion applies styles at runtime by injecting them into style tags in the DOM, Linaria uses Babel to parse the React tree at build time, extracting styles to CSS files.

By extracting styles to CSS files, we can better rely on the browser’s inherent ability to cache CSS, and to load CSS in parallel with JavaScript. Because no JavaScript has to be executed to apply styles, this also reduces the time it takes to render React components.

Though Linaria’s API is very similar to Emotion’s, the use of Linaria requires a slightly different approach to writing styles due to the intentional lack of runtime evaluation.

emotion-to-linaria codemod

We have created a codemod that can be run to convert most components in your stores from Emotion to Linaria. This codemod will update the import declarations and rewrite some of your styling to syntax that is compatible with Linaria.

There are some patterns that are not possible to codemod and will require your input. The codemod will show warnings for those lines of code that need to be manually changed.

Codemod is not fully compatible with Windows, it causes a lot of incorrect modifications. Run Codemod on a MacOS or Linux operating system.

Running the codemod

To run the codemod, simply open your terminal and run this command FROM THE PROJECT ROOT:

Copy
Copied
npx @jetshop/flight-codemod emotion-to-linaria ./src

Running this command from a different directory may seem like it works, but it will insert the wrong import paths.

Warnings

You may receive warnings for lines of code that could not be automatically modded. Take note of the filename and line number that appear after the warning, as that will indicate where you need to look.

- ** using css inside a styled template literal is no longer supported. Try using a regular className or consider inline styles **

This occurs when css has been used inside a styled or css template literal. For example:

Copy
Copied
// This is no longer valid

const CategoryName = styled('span')`
  cursor: pointer;
  ${({ hasSubcategories }) =>
    hasSubcategories &&
    css`
      &:before {
        margin-right: 0.5rem;
        font-weight: 400;
      }
    `};
`

function Comp(props) {
  return <CategoryName hasSubcategories={props.hasSubcategories} />
}

In this case, the best solution is to use a class name instead:

Copy
Copied
// This resolves the issue
import { cx } from 'linaria'

const CategoryName = styled('span')`
  cursor: pointer;

  &.has-subcategories {
    &:before {
      margin-right: 0.5rem;
      font-weight: 400;
    }
  }
`

function Comp(props) {
  return (
    <CategoryName
      className={cx(props.hasSubcategories && 'has-subcategories')}
    />
  )
}

- ** A CSS template literal with an interpolated expression was used inside a className prop. This cannot be polyfilled. Please fix manually - consider using a style prop instead. **

This error occurs in cases where you have used the css function inside a className prop, and have an interpolated expression inside that css function. For example:

Copy
Copied
// This is no longer valid

<ColorSwatch
  className={css`
    border-color: ${swatchColor === 'white' ? '#b8b9b8' : swatchColor};
  `}
/>

The best solution here is to use a style prop:

Copy
Copied
// This resolves the issue

<ColorSwatch
  style={{
    borderColor: ${swatchColor === 'white'
      ? '#b8b9b8'
      : swatchColor};
  }}
/>

Manual changes

There are some differences from Emotion that the codemod does not handle therefore you have to change them manually.

- ** innerRef prop name change **

In Emotion to pass a ref to a styled component you had to use innerRef prop name. In Linaria the prop name is simply ref.

- ** withComponent is not supported **

Linaria does not have withComponent therefore you have to implement some workarounds to achive the same result.

Let's take the following example:

Copy
Copied
import Button from '../../ui/Button'

const CheckoutButton = styled(Button)`
  background-color: #000000;
`.withComponent('a')

We want to have to use the Button styling but render it as an a link component. All we have to do is separate the styling from the Button component:

Copy
Copied
// before
const Button = styled('button')`
  background-color: blue;
  color: white;
  border-radius: 4px;
`

export default Button

// after
export const buttonStyle = `
  background-color: blue;
  color: white;
  border-radius: 4px;
`

const Button = styled('button')`
  ${buttonStyle};
`

export default Button

Differences to Emotion 9

Linaria styled components and dynamic props

Whilst it is still possible to use the styled syntax to build components using Linaria, props passed to those components may only be used for CSS values. Under the hood, the props will be converted to CSS custom properties, which are not supported in IE11. If you require IE11 support, see alternative approaches below.

Copy
Copied
// This syntax is fine, although the height and width will not be applied in IE11.
import { styled } from 'linaria/react'

const Box = styled.div`
  background-color: orange;
  height: ${(props) => props.size}px;
  width: ${(props) => props.size}px;
`

function Comp(props) {
  return <Box size={props.size} {...props} />
}

If you require IE11 support, the recommended approach is to simply use inline styling for dynamic values:

Copy
Copied
// This syntax works in all browsers
import { styled } from 'linaria/react'

const Box = styled.div`
  background-color: orange;
`

function Comp(props) {
  return (
    <Box
      size="large"
      style={{ height: props.size, width: props.size }}
      {...props}
    />
  )
}

If you’d like to apply different styles conditionally and the values themselves aren’t dynamic, you can also use CSS classes:

Copy
Copied
// This syntax works in all browsers
import { styled } from 'linaria/react'
import { cx } from 'linaria'

const Box = styled.div`
  background-color: orange;

  &.important {
    background-color: red;
  }

  &.notice {
    background-color: yellow;
  }
`

function Comp(props) {
  return (
    <Box
      {...props}
      className={cx(props.notice && 'notice', props.important && 'important')}
    />
  )
}

The above could also be expressed using the css function that Linaria exports, instead of the styled function. The css function returns a class name that can be applied to the className prop:

Copy
Copied
// This syntax works in all browsers
import { cx, css } from 'linaria'

const boxStyles = css`
  background-color: orange;

  &.important {
    background-color: red;
  }

  &.notice {
    background-color: yellow;
  }
`

function Comp(props) {
  return (
    <div
      {...props}
      className={cx(
        boxStyles,
        props.notice && 'notice',
        props.important && 'important'
      )}
    />
  )
}

The css function

As with Emotion 9, Linaria exports a css function that can be used to create styles that should be passed to an element’s className prop.

Copy
Copied
import { css } from 'linaria'

const styles = css`
	color: blue;
	&:hover {
		color: red;
	}
`

<div className={styles} />

The difference with Emotion 9 is that styles created with the css function cannot be interpolated into other style declarations.

Copy
Copied
// This will not work as intended

import { css } from 'linaria'

const baseStyles = css`
  color: blue;
`

const combinedStyles = css`
  ${baseStyles};
  background: white;
`

To achieve this, you should simply omit the css function from the styles that you want to interpolate:

Copy
Copied
// This works!

import { css } from 'linaria'

// Do not use `css` here
const baseStyles = `
	color: blue;
`

const combinedStyles = css`
  ${baseStyles};
  background: white;
`

The css prop

Emotion 9 has a css prop that can be use similarly to style.

Copy
Copied
// This no longer works

function Component() {
  return (
    <div
      css={`
        color: red;
      `}
    />
  )
}

Instead of doing this, use a style prop or move the styles outside of the component

Copy
Copied
// This is fine

function Component() {
  return <div style={{ color: 'red' }} />
}

// or this

const styles = css`
  color: red;
`

function Component() {
  return <div className={styles} />
}
Copyright © Norce 2023. All right reserved.