[developic] CSS-in-JS & styled-components vs emotion

sue·2021년 3월 25일
0

developic project

목록 보기
5/28

CSS-in-JS의 대표적인 라이브러리인 styled-components와 emotion 둘 중 어떤 것을 선택할 지에 앞서, 일반적인 CSS가 아닌 CSS-in-JS를 선택한 배경에 대해 이야기해보고자 한다.

From CSS to CSS-in-**

웹이 태어난 직후에는 CSS(Cascading Style Sheets)가 존재하지 않았다. HTML은 웹 페이지와 그 내용을 구조화하기 위해 사용하는 마크업 언어다. 그런데 웹의 발전에 따라 단순 정보 전달뿐 아니라 페이지를 보기 좋게 디자인하려는 욕구가 증가하면서 디자인과 관련한 태그가 HTML에 추가되기 시작했다. 하지만 이런 식으로 새로운 태그들이 계속 추가된다면 정보의 구조화라는 본래의 목적을 잃게 될 수 있기에 웹 표준기구인 W3C에서 HTML의 모든 스타일 요소를 CSS 라는 새로운 언어로 분리하게 되었다.

그러나 웹이 점점 복잡해지고 동적 기능에 대한 요구가 증가하면서 HTML과 CSS만으로는 화면의 모든 스타일을 제어할 수 없는 상황에 이르렀다. 이를 해결하기 위한 여러 가지 웹 애플리케이션 스타일 구성 방식이 나타났으며 크게 두 갈래로 나뉘어지게 된다. CSS-in-JS와 CSS-in-CSS가 바로 그것이다.

CSS-in-CSS

CSS 모듈

CSS 모듈은 CSS를 모듈화해 사용하는 방식이다. CSS 클래스를 만들면 자동으로 고유한 클래스네임을 만들어서 중첩을 방지한다. 모듈화된 CSS를 불러오면 사용자가 정의했던 클래스네임과 고유한 클래스네임으로 이뤄진 객체가 반환된다.

CSS 모듈의 장점은 고유한 클래스네임이 만들어지면서 중복 및 관리의 위험성이 적고, CSS 클래스 네이밍 규칙이 간소화되는 점이다. 다만 스타일을 한 파일에서 모두 작성하는 것이 아니라 컴포넌트 단위로 많은 CSS 파일을 만들어 따로 관리해야 하는 단점이 있다.

CSS 전처리기

CSS 전처리기는 자신만의 특별한 syntax를 가지고 CSS를 생성하도록 하는 프로그램이다. CSS의 문제점을 변수·함수·상속 등 일반적인 프로그래밍 개념을 사용하여 보완하고 컴파일러를 통해 CSS 포맷으로 변환한다. CSS 전처리기로는 Sass, Less, Stylus가 가장 많이 사용되고 있다.

CSS 전처리기는 CSS 구조를 가독성있고 더 유지보수 하기 좋게 만드는 장점이 있다. 반면 단점으로는 전처리기를 위한 도구가 필요하고 다시 컴파일하는데 시간이 소요되는 점이 있다.

CSS-in-JS

앞선 방식들은 몇가지 문제를 해결해주긴 했지만 근본적인 해결책은 아니었다. 그러던 중, 2014년 페이스북 개발자인 Vjeux가 CSS 코드 작성의 새로운 대안으로 CSS-in-JS를 발표했다. 이 발표에서 Vjeux가 설명한 CSS작성의 어려움은 다음과 같았다.

  • Global namespace: 모든 스타일이 global에 선언되어 별도의 class 명명 규칙을 적용해야 하는 문제
  • Dependencies: css간의 의존관계를 관리하기 힘든 문제
  • Dead Code Elimination: 기능 추가, 변경, 삭제 과정에서 불필요한 CSS를 제거하기 어려운 문제
  • Minification: 클래스 이름의 최소화 문제
  • Sharing Constants: JS의 상태 값을 공유할 수 없는 문제
  • Non-deterministic Resolution: CSS 로드 순서에 따라 스타일 우선 순위가 달라지는 문제
  • Isolation: CSS와 JS가 분리된 탓에 상속에 따른 격리가 어려운 문제

CSS-in-JS는 말 그대로 자바스크립트 코드에서 CSS를 작성하는 방식을 말한다. 이를 통해 CSS를 자바스크립트 변수를 사용해 정의할 수 있으며 컴포넌트 스타일을 분리하고 재사용 가능하게 만들 수 있다.

const Title = styled.h1`
  font-family: sans-serif;
  font-size: 48px;
  color: #f15f79;
`;
const App = () => {
     return(
          <Title>
               Hello world!
          </Title>         
)
;

기존 웹사이트는 HTML, CSS, JavaScript를 각자 별도의 파일로 두었다. 하지만 React, Vue, Angluar와 같은 모던 자바스크립트 라이브러리가 인기를 끌고 컴포넌트 기반의 개발 방법의 주류가 됨에 따라 한 컴포넌트에 HTML, CSS, JavaScript를 모두 포함하는 패턴이 많이 사용되고 있다.

CSS-in-JS는 앞서 이야기한 CSS의 문제를 해결하고자 위해 등장했기 때문에 다음과 같은 특징을 가진다.

  • Global namespace: class명이 build time에 유니크한 해시값으로 변경되기 때문에 별도의 명명 규칙이 필요하지 않다.
  • Dependencies: CSS가 컴포넌트 단위로 추상화되기 때문에 CSS 파일(모듈) 간에 의존성을 신경 쓰지 않아도 된다.
  • Dead Code Elimination: 컴포넌트와 CSS가 같은 구조로 관리되므로 수정 및 삭제가 쉽다.
  • Minification: 네임스페이스 충돌을 막기 위해 BEM 같은 방법론을 사용하면 class 명이 길어질 수 있지만, CSS-in-JS는 짧은 길이의 유니크한 클래스를 자동으로 생성한다.
  • Sharing Constants: CSS 코드를 JS에 작성하므로 컴포넌트의 상태에 따른 동적 코딩이 가능하다.
  • Non-deterministic Resolution: CSS가 컴포넌트 스코프에서만 적용되기 때문에 우선순위에 따른 문제가 발생하지 않는다.
  • Isolation: CSS가 JS와 결합해 있으므로 상속에 관한 문제를 신경 쓰지 않아도 된다.

이 외에도 CSS-in-JS를 사용하면 다른 사람들이 프로젝트 내에서 일일이 컴포넌트에 맞는 스타일을 찾는 대신 한번에 코드를 쉽게 이해할 수 있으므로 코드 공유가 더 쉽고 효율적이다. 또 CSS와 컴포넌트를 동일한 파일에 정의하기 때문에 컴포넌트의 CSS는 컴포넌트가 로드될 때만 로드되어 가상 DOM에 대한 불필요한 부하를 줄여준다. JavaScript 파일 내에서 CSS가 정의되기 때문에 JavaScript 로직을 사용하여 CSS의 속성 값을 정의할 수 있는 장점으로 향상된 동적 스타일링이 가능하다는 장점이 있다.

물론 단점 역시 존재한다. 스타일이 자바스크립트 파일에 정의된다는 것은 자바스크립트가 비활성화된 경우 컴포넌트 스타일에 영향을 미칠 수 있다는 것을 의미하며, 일반적으로 웹 페이지를 로드할 때 브라우저는 CSS를 읽고 적용하는데 CSS-in-JS를 사용할 때는 동적으로 CSS 스타일 태그를 생성한 다음 이를 읽고 웹페이지에 적용하여 이에 따른 비용과 시간이 들어간다.

각 도구마다 장단점을 가지고 있기 때문에 서비스 특성과 프로젝트를 고려하여 선택하는 것이 중요하다. 리액트 컴포넌트 기반인 우리 프로젝트의 경우 중복되는 컴포넌트 스타일을 재사용할 수 있고, 해당 컴포넌트의 스타일만 정의하기 때문에 스타일 적용 범위가 한정되어 사이드 이펙트 확률을 줄일 수 있으며, CSS에서 자바스크립트 로직을 사용할 수 있는 등의 CSS-in-JS의 장점이 더 앞선다고 판단했기 때문에 CSS-in-JS 방식을 선택하게 되었다.


다음으로는 CSS-in-JS의 대표적인 라이브러리인 styled-components와 emotion을 비교해보았다.

Styled Components

styled-components는 리액트를 처음 배울 때 같이 학습을 시작했던 아이였다. css, sass, postCSS 등을 사용해본 후 styled-components로 넘어왔을 때 매우 신세계라고 느꼈던 기억이 생생하다. 그 이유는 바로 컴포넌트 단위의 스타일링 방식 때문이었다.

styled-components를 사용하면 클래스 이름이 다른 요소나 컴포넌트에 영향을 미치는지에 대해 신경쓰지 않고 스타일이 지정된 재사용 가능한 컴포넌트를 만들 수 있다.

import styled from 'styled-components'

const App = () => {
  const color = 'blue';
  
  const BlueDiv = styled.div`
    background-color: ${color};
  `;
  

  return (
    <BlueDiv>
      Hello World
    </BlueDiv>
  );
}

export default App;

styled-components의 특징은 다음과 같다.

  • props를 styled-components에 전달하기 쉬움
  • 테마 작업을 위한 다양한 옵션 제공
  • 모든 유형의 styled-components에 적용할 수 있는 전역 스타일을 허용
  • 거의 모든 CSS 프레임워크와 호환되어 모든 테마 및 스타일링 요구 사항 지원

Emotion

emotion은 styled-components와 마찬가지로 CSS-in-JS 라이브러리에서 가장 인기 있는 CSS 중 하나이며 다음과 같이 className 요소에 스타일을 전달할 수 있다.

import { css, cx } from '@emotion/css'

const App = () => {
  const color = 'blue'
  
  const styles = css`
    background-color: ${color};
  `;

  return (
    <div className={styles}>
      Hello World
    </div>
  );
}

export default App;

이 패키지는 프레임워크나 라이브러리에 구애받지 않으므로 React를 사용하지 않는 애플리케이션에서 사용하는 버전이다.

emotion은 다양한 사용 사례를 위해 만들어진 여러 패키지가 있기 때문에, 번들 크기를 늘리지 않도록 필요에 맞는 패키지를 설정하면 된다. (ex. for React : yarn add @emotion/core, for RN : @emotion/native)

또한 styled-components에서 사용하는 styled.div style API를 @emotion/styled패키지를 설치해 이모션에서도 다음과 같이 똑같이 사용할 수 있다.

import styled from '@emotion/styled';

const Button = styled.button`
  width: 100px;
  height: 40px;
  background-color: black;
  color: white;
`;

추가적으로 Next.js 10버전 이상에서 사용 시 서버사이드렌더링과 관련한 별도의 설정이 필요없다는 장점이 있다.

결론

두 라이브러리 모두 유용하고 대중적이며 스타일링에 필요한 기능을 지원한다. CSS-in-JS 라이브러리를 처음 사용하는 경우 styled-components로 시작하는 것을 선호한다. 일반적으로 CSS-in-JS를 더 잘 이해하려면 styled-components가 CSS-in-JS의 기본 개념과 실제 프로젝트에서 어떻게 작동하는지 이해하는 데 도움이 된다고 한다.

하지만 이전에 CSS-in-JS 라이브러리를 사용한 적이 있고 더 작고 더 빠른 라이브러리를 찾고 있다면 Emotion을 사용해볼 수 있다. Emotion은 더 작은 번들 크기를 가지며 npm 트렌드 상 styled-component보다 더 높은 사용률을 나타내고 있다. 또한 styled api를 사용하면 styled components와 같은 방식으로 스타일 컴포넌트를 만들 수 있기 때문이다.

npm trends: https://www.npmtrends.com/@emotion/core-vs-styled-components

결론적으로 우리는 CSS-in-JS의 대표적인 라이브러리인 emotion과 styled-components 중, 비슷한 기능을 수행하면서 더 적은 번들 크기를 가지고 있고, styled-components가 가지고 있는 style API 기능을 동일하게 사용할 수 있으며, styled-components는 서버사이드렌더링 시 별도의 작업이 필요한 반면, emotion은 Next.js 10버전 이상 사용 시 추가 작업이 필요하지 않다는 장점을 고려해 emotion을 선택했다.

0개의 댓글