styled-components를 사용하지 않기로 했다

­가은·2024년 1월 14일
49
post-thumbnail
post-custom-banner

styled-components를 처음 사용해본 것은 바야흐로 2021년...
대학생 스타트업 팀의 팀원으로 합류해 프론트엔드 개발을 처음 시작했을 때였다.
그 때의 나는 정말 아무것도 모르는 상태였다.
난 개발계의 도라에몽같았던 학교 선배가 시키는대로 따라갔고,
그 선배가 택했던 CSS 라이브러리인 styled-components를 사용하게 되었다.

그 이후로는 그냥 자연스럽게 계속 styled-components를 사용했다.
내가 그 라이브러리에 너무 익숙한 탓도 있었고, 프로젝트나 인턴을 할 때도 대부분의 사람들이 그 라이브러리를 사용하는 것에 동의했다.
시간이 어느정도 지난 후에는 styled-components 대신 Emotion으로 변경하긴 했지만, 사실 둘은 사용법 측면에서 크게 다르지는 않았으니까.

중간에 잠깐씩 타의로 css module이나 SCSS를 사용했던 적이 있기도 했다.
근데 내가 styled-components에 너무 절여져서 그랬던건지, 그 라이브러리들이 상당히 마음에 들지 않았다.
'styled-components에서는 이랬는데, 여기서는 왜 이러지?' 같은 생각이 들었다.

그냥 요약하자면,
딱히 이유없이 styled-components와 Emotion을 쓰게 됐고,
다른 CSS 라이브러리에 거부감이 느껴졌으며,
관성적으로 styled-components쪽으로 돌아오려고 했다는 것.

현재 나는 넥스터즈 24기에 프론트엔드 개발자로 참여하고 있고
이번 프로젝트에서는 vanilla-extract를 사용하기로 했다.
지금부터 내가 styled-components와 Emotion에서 벗어나 vanilla-extract를 선택하게 된 이유를 설명해볼까 한다.




팀원분과 이번 프로젝트의 기술스택을 정하며, 어떤 CSS 라이브러리를 쓸 지에 관한 이야기를 나누었다.

후보는 tailwind, Emotion, vanilla-extract였다.
내가 개인적으로 tailwind 스타일에 극강의 불호를 느끼고 있어서 tailwind는 후보에서 제외하게 되었다..
vanilla-extract는 팀원분께서 제안해주신 라이브러리였는데, zero-runtime에다가 요즘 기업들이 많이 사용하고 있다고 강력 추천하셨다.

그래서 vanilla-extract와 Emotion을 비교해보게 되었다.


🍞 zero-runtime vs runtime

vanilla-extract와 Emotion의 가장 큰 차이점인 것 같다.
vanilla-extract는 zero-runtime CSS이고, Emotion은 runtime CSS이다.

먼저 runtime CSS에서는 Javascript 런타임 중에 CSS를 생성한다.
즉 Javascript 코드에서 스타일을 동적으로 생성하고 적용한다.

zero-runtime CSS에서는 빌드 시점에 CSS를 생성한다.
그래서 런타임에는 추가적인 Javascript 실행 없이 이미 생성된 CSS를 사용하게 된다.
원리는 아래와 같다.

1. 스타일 정의

  • 개발자가 Javascript 파일 내에서 CSS 스타일을 정의한다.

2. 빌드 시점에 스타일 추출

  • 라이브러리는 빌드 과정에서 스타일 정의를 찾아 CSS 파일을 생성한다.
  • 각 스타일 정의는 고유한 클래스 이름을 부여받는다.

3. 스타일 사용

  • 생성된 CSS 파일이 런타임에 HTML 문서에 연결된다.
  • Javascript는 미리 생성된 클래스 이름만 참조하여 필요한 스타일을 적용한다.

즉 스타일 시트를 별도의 CSS 파일로 추출해서, 브라우저가 페이지를 렌더링할 때 CSS를 즉시 사용할 수 있는 것이다.
이 경우 런타임에 추가적인 스타일 계산이나 적용이 없기 때문에 런타임 성능이 개선된다.

위와 같은 이유로 런타임 성능 면에서는 vanilla-extract가 훨씬 우수하다.


🍞 동적 스타일링

Emotion이 아닌 다른 라이브러리에 궁금했던 것 중 하나는, CSS props를 이용한 동적 스타일링 가능 여부였다.

안타깝게도 vanilla-extract는 zero-runtime CSS이기에 CSS props를 이용한 동적 스타일링이 불가능하다.
CSS props 방식은 런타임에 Javascript를 사용하여 스타일을 계산하고 적용하는 방식이다.
하지만 zero-runtime CSS의 경우 빌드 시점에 모든 CSS를 생성하기 때문에 위와 같은 방법을 사용할 수 없다.

대신 zero-runtime CSS에서는 CSS 변수를 사용하여 동적 스타일링을 처리할 수 있다.

근데 찾아보니 코드가 뭔가 좀 구렸다...
쩝...

다행히 vanilla-extract에서는 Sprinkles, Recipes같은 기능들을 제공해줘서 조금 더 깔쌈하게 코드를 작성할 수 있다.

Sprinkles는 vanilla-extract를 위한 zero-runtime atomic CSS 프레임워크이다.
공식문서의 설명에 따르면 Sprinkles는

  • CSS-in-JS의 일반적인 스타일 생성 오버헤드 없이
  • 커스텀 유틸리티 클래스의 정적 집합을 생성하고
  • 빌드 시점에 정적으로, 혹은 런타임에 동적으로 구성한다.

내 번역의 한계로.. 뭔가 좀 알아듣기 어려운 설명이 되었는데..
아무튼 이걸 사용하면 런타임에 동적으로 스타일을 적용할 수 있다.
물론 미리 정의된 스타일 집합의 조합을 런타임에 변경할 수 있는 것이지, 런타임에 새롭게 스타일을 정의하거나 새로운 스타일을 적용할 수는 없다.

예를 들면 아래와 같다.

import React from 'react';
import { defineProperties } from '@vanilla-extract/sprinkles';

const colors = {
  red: '#f44336',
  green: '#4caf50',
  blue: '#2196f3',
};

const properties = defineProperties({
  properties: {
    color: colors,
  },
});

const sprinkles = properties;

function MyButton() {
  return <button className={sprinkles({color: 'blue'})}>Click Me!</button>
}

여기서는 간단하게 예시를 작성했지만, 실제로 defineProperties 내에서 properties 말고도 conditions, defaultCondition, shorthands 등의 속성을 사용하여 더 복잡한 스타일링을 할 수 있다.

Recipes라는 것이 있는데,
multi-variant styles를 만드는 것을 도와준다고 한다.

import { createRecipe, style } from '@vanilla-extract/recipes';

const buttonVariants = createRecipe({
  base: style({ // base는 기본적인 스타일을 정의
    padding: '10px',
    border: 'none',
    borderRadius: '4px',
    fontSize: '1rem',
  }),
  variants: { // variants는 다양한 style variants를 정의
    color: {
      primary: style({ backgroundColor: 'blue', color: 'white' }),
      secondary: style({ backgroundColor: 'grey', color: 'white' }),
    },
  },
});

export function MyButton() {
  return <button className={buttonVariants({ color: 'primary' })}>Click Me!</button>
}

이런 느낌으로 사용할 수 있다.

vanilla-extract에는 SprinklesRecipes 말고도 Dynamic같은 기능들을 사용해 깔끔한 스타일링을 할 수 있다.
이 글은 vanilla-extract 기능을 설명하는 글은 아니니 이정도에서 넘어가보자.

사실 빌드 시점에 CSS가 생성되는 만큼, CSS 변수를 쓴다고 해도 동적 스타일링에 한계가 있다는 점은 조금 아쉽다.
그래도 vanilla-extract의 여러 기능들을 사용한다면 충분히 보완 가능하다는 생각이 든다.


🍞 SSR

SSR을 사용하는 환경에서는 Emotion 설정이 더 간단하고, 초기 렌더링 성능이 좋다.

Emotion은 동적으로 CSS를 생성하고, 이를 HTML에 인라인으로 삽입한다.
그래서 클라이언트가 HTML을 처음 받았을 때 이미 필요한 CSS를 모두 가지고 있게 된다.
이로 인해 초기 렌더링 시 깜박임이 발생하지 않는다.

하지만 vanilla-extract는 CSS 파일을 빌드 시점에 생성하기 때문에, SSR 환경에서는 추가적인 설정이 필요하다.

프로젝트에 SSR을 사용한다면 Emotion이 더 적합할 수 있겠지만, 이번에 우리는 CSR을 사용할 예정이다.
Next.js를 사용할 지에 대한 의논을 해보았지만 굳이 쓸 이유가 없다는 결론이 나와서 typescript + React만 사용하기로 했다.
그래서 이 부분에서는 Emotion의 이점이 없다고 판단했다.


🍞 스타일 재사용성

새로운 CSS 라이브러리에서 CSS props 외에 궁금했던 점은 스타일 재사용성이었다.
스타일 중복을 최대한 줄일 수 있는 코드를 작성할 수 있는지가 궁금했다.

vanilla-extract에서는 스타일 객체를 배열 형태로 제공한다.
그래서 아래와 같이 중복된 스타일코드를 줄일 수 있다.

import { style } from '@vanilla-extract/css';

const base = style({
  padding: '10px',
  border: 'none',
  borderRadius: '4px',
  fontSize: '1rem',
});

const primaryButton = style([base, { backgroundColor: 'blue', color: 'white' }]);
const secondaryButton = style([base, { backgroundColor: 'grey', color: 'white' }]);

스타일 재사용성 부분에서도 Emotion과 비슷하게 사용할 수 있을 것 같다.


🍞 타입 안정성

vanilla-extract는 Typescript로 작성되었다.
그래서 스타일과 테마 변수에 대한 타입 검사를 제공한다.

1. 스타일 작성 시 타입 검사, 2. 테마 변수에 대한 타입 검사, 3. 스타일 변형에 대한 타입 검사가 이루어진다.

이러한 타입 검사로 개발자가 잘못된 값을 사용하는 것을 막음으로써 버그를 방지할 수 있다.

Emotion은 Typescript와 함께 사용할 경우 1. 스타일 작성 시 타입 검사가 가능하여, vanilla-extract와 마찬가지로 CSS 속성과 값에 대한 타입 검사를 할 수 있다.
하지만 2. 테마 변수와 3. 스타일 변형에 대한 타입 검사의 경우, 개발자가 직접 테마 타입을 정의하고 관리해주어야 한다.

그래서 타입 안정성 측면에서는 vanilla-extract가 더 강력하다.
개발 과정을 안정화시키고 버그를 줄이기 위해서는 vanilla-extract가 더 좋아보인다.


🍞 개발자 경험 (DX)

솔직히 말하면 나는 Emotion이 더 편하긴 하다.
개발 속도나 효율을 따졌을 때는 새로운 vanilla-extract보다 익숙한 Emotion이 더 나을 것이다.
또 vanilla-extract보다는 styled-components와 Emotion 자료가 훨씬 더 많기도 하다.

하지만.
이제는 이유없이 익숙한 것만 고집하는 것에서 탈피하고싶다.
사이드 프로젝트인 만큼, 너무 효율만 따지기보다는 새로운 기술을 학습해보고 싶은 마음도 있다.

그래서 이렇게 이것저것 따져보고 vanilla-extract를 사용하기로 결정했다.
이번 프로젝트에서는 새로운 것들을 많이 접해볼 수 있을 것 같다.


🍞 요약

  • zero-runtime vs runtime -> vanilla-extract 승
  • 동적 스타일링 -> Emotion 승
  • SSR -> 무승부
  • 스타일 재사용성 -> 무승부
  • 타입 안정성 -> vanilla-extract 승

참고자료

post-custom-banner

0개의 댓글