[CSS] CSS-in-JS 라이브러리 간단 분석(runtime, zero-runtime)

김효선·2024년 6월 26일
0
post-thumbnail

CSS-in-JS 라이브러리는 JS와 상태를 공유할 수 있어서 동적으로 스타일링을 할 수 있는 장점이 있습니다. 컴포넌트 기반 개발과 잘 맞아떨어지고 스타일 충돌 문제를 방지할 수 있어 개발자들에게 많은 사랑을 받고 있습니다(저 포함)✨
CSS-in-JS 는 동작 방식이 런타임에 스타일을 생성하는 방식과 빌드 타임에 스타일을 생성하는 방식으로 나누어집니다.

스타일 생성 방식: 런타임 vs 빌드 타임

런타임 스타일 생성

자바스크립트가 실행되는 시점(컴포넌트가 렌더링될 때) 스타일을 생성합니다. 동적으로 <style> 태그를 생성하여 <head> 태그에 추가됩니다.
대표적인 라이브러리로는 styled-components, emotion, JSS 등이 있습니다.

styled-components 및 emotion의 런타임 스타일 생성

const StyledButton = styled.button`
  background-color: ${props => props.primary ? 'blue' : 'gray'};
  color: white;
`;
    
// 렌더링 시점에:
<StyledButton primary>Click Me</StyledButton>

브라우저의 <head> 섹션이나 특정 <style> 태그 안에 동적으로 생성된 스타일이 삽입됩니다.

<style>
  .sc-AxjAm { background-color: blue; color: white; }
</style>

개발 모드에서 주로 사용되며 <style> 태그를 생성하여 스타일을 삽입합니다.

단점 - 런타임 오버헤드가 발생할 수 있습니다. 스타일을 계산하고 적용하는 데 시간이 걸리므로 초기 로딩 속도가 느려질 수 있습니다. SSR 설정이 별도로 필요할 수 있습니다.

빌드 타임 스타일 생성 (zero-runtime에 관하여)

zero-runtime이란 런타임에 스타일을 생성하거나 적용하는 작업이 없는 것을 의미

스타일이 빌드 과정에서 정적으로 생성되어 CSS 파일로 출력됩니다.
초기 로딩 성능이 개선되며, 스타일 적용이 빠르고 브라우저의 스타일 계산 부담이 줄어듭니다.
대표적으로 styled-components / emotion, linaria, stitches, @vanilla-extract 등이 있습니다.

styled-components & emotion (완전히 zero-runtime은 아님)

styled-components & emotion 의 경우 개발 모드와 프로덕션 모드에 차이가 있는데, 프로덕션 모드에선 빌드 타임에 스타일을 미리 생성해서 최적화합니다.
(개발 모드에서의 <style> 태그와 프로덕션 모드에서 <style> 태그를 비교해 보면 차이가 납니다)

  • 프로덕션 모드에서 styled-components는 내부 Babel 플러그인을 통해 템플릿 리터럴을 해석하여 최종적으로 CSS 스타일 시트를 생성합니다.
    이렇게 생성된 CSS는 하나의 스타일 태그에 모두 적용되어, 불필요한 DOM 조작을 줄이고 초기 렌더링 속도를 향상시킵니다.
  • 프로덕션 모드에서 emotion은 빌드 타임에서 스타일을 생성하여 내부 Babel 플러그인을 통해 컴파일되고(이 과정에서 템플릿 리터럴을 CSS로 변환), 최적화된 스타일 시트를 생성해서 CSSOM에 직접 주입합니다.

내부 Babel 플러그인들(babel-plugin-styled-components, @emotion/babel-plugin)이 제공하는 최적화는 런타임 오버헤드를 완전히 제거하지는 않습니다! 이 플러그인들은 스타일의 생성과 적용 과정을 최적화하고 일부 작업을 빌드 타임으로 이전하지만, 몇 가지 런타임 작업은 여전히 필요하기 때문에 완전한 zero-runtime 으로 볼 수는 없습니다. 컴포넌트가 렌더링될 때 스타일을 적용하는 런타임 작업이 여전히 필요합니다..!

Stitches (완전히 zero-runtime은 아님)

빌드 타임에 CSS를 생성하여 성능을 최적화하는 CSS-in-JS 라이브러리입니다.
스타일을 빌드 타임에 추출하지만, 실제로 런타임에 <style> 태그를 동적으로 삽입하므로 완전한 zero-runtime이라고 할 수 없습니다. 런타임에서 스타일을 적용하는 과정이 포함됩니다.

import { styled } from '@stitches/react';

const Button = styled('button', {
  backgroundColor: 'gray',
  borderRadius: '4px',
  border: 'none',
  color: 'white',
  padding: '10px 20px',
  cursor: 'pointer',
  '&:hover': {
    backgroundColor: 'darkgray',
  },
});

//...
<Button>버튼</Button>

빌드 타임에 Stitches가 생성하는 CSS 객체

const styles = {
  h1: {
    fontSize: '24px',
    color: '#333',
  },
  button: {
    backgroundColor: 'gray',
    borderRadius: '4px',
    border: 'none',
    color: 'white',
    padding: '10px 20px',
    cursor: 'pointer',
    '&:hover': {
      backgroundColor: 'darkgray',
    },
  },
};

HTML에 삽입된 <style> 태그

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Stitches Example</title>
  <style>
    /* Injected by Stitches at runtime */
    .title_hash {
      font-size: 24px;
      color: #333;
    }

    .button_hash {
      background-color: gray;
      border-radius: 4px;
      border: none;
      color: white;
      padding: 10px 20px;
      cursor: pointer;
    }
    .button_hash:hover {
      background-color: darkgray;
    }
  </style>
</head>
<body>
  <div id="root">
    <button class="button_hash">버튼</button>
  </div>
</body>
</html>

Linaria

빌드 타임에 CSS를 생성하여 런타임 오버헤드가 없는 zero-runtime CSS-in-JS 라이브러리입니다.
Linaria의 문법은 Styled Components와 유사합니다!

import { styled } from '@linaria/react';

const Button = styled.button`
  background: gray;
  border-radius: 4px;
  border: none;
  color: white;
  padding: 10px 20px;
  cursor: pointer;
  &:hover {
    background: darkgray;
  }
`;

//...
<Button>버튼</Button>

빌드 타임에 Linaria가 생성하는 CSS 파일 (예: styles.css)

.button_classname {
  background: gray;
  border-radius: 4px;
  border: none;
  color: white;
  padding: 10px 20px;
  cursor: pointer;
}
.button_classname:hover {
  background: darkgray;
}

HTML에 삽입된 <style> 태그
Linaria는 빌드된 HTML 파일에 <style> 태그를 포함시켜 스타일을 적용

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Linaria Example</title>
  <style>
    .button_classname {
      background: gray;
      border-radius: 4px;
      border: none;
      color: white;
      padding: 10px 20px;
      cursor: pointer;
    }
    .button_classname:hover {
      background: darkgray;
    }
  </style>
</head>
<body>
  <div id="root">
    <button class="button_classname">버튼</button>
  </div>
</body>
</html>

Vanilla Extract

개발 모드와 프로덕션 모드에서 빌드 타임 스타일 생성을 지향하는 zero-runtime CSS-in-JS 라이브러리입니다.

  • TypeScript와 함께 정적 타입과 최적화된 스타일 생성을 제공합니다.
  • 개발 모드와 프로덕션 모드 모두에서 일관된 방식으로 스타일을 처리하며, 빌드 타임 스타일 생성을 통해 런타임 오버헤드를 최소화합니다✨

Vanilla Extract의 컴파일 타임 CSS 파일 생성

// styles.css.ts
import { style } from '@vanilla-extract/css';
    
export const button = style({
  backgroundColor: 'blue',
  color: 'white',
});
    
// Component.tsx
import { button } from './styles.css';
    
function Component() {
  return <button className={button}>버튼</button>;
}

컴파일 타임에 styles.css 파일이 생성되고, HTML 파일에 포함됩니다.

<link rel="stylesheet" href="styles.css">

styles.css 내용

.button { background-color: blue; color: white; }

프로젝트 규모와 복잡도, SSR인지 CSR인지, 스타일링 방식 등 기준을 고려하여 요구사항에 맞는 CSS-in-JS 라이브러리를 선택하는 것이 중요하다고 봅니다✨

profile
개발을 게임처럼!

0개의 댓글