CSS-in-CSS 와 CSS-in-JS 비교일기

leetrueleetrue
9 min read

✋🏻 들어가며

이번에 회사에서 프로젝트를 하면서, Antd 와 styled-component 를 서버 컴포넌트에 적용하지 못하면서 생긴 애로사항과 FOUC 문제의 발생으로 애를 꽤 먹었었다. 이것을 해결하는 과정에서 스타일을 적용하는 다양한 방법에 대해 알기만하지 이것들에 대해서 명확하게 어떤 차이가 있는지를 설명할 수 있는지 스스로 질문을 던져봤는데, 그 답은 “잘은 모르겠네.” 였다. 그래서 이번 기회에 한번 정리하는 시간을 가져봤다.


😶‍🌫️ CSS

CSS 는 Cascading StyleSheet 로 어떤 화면에서 HTML 요소의 스타일을 지정하는 규칙을 만드는 것이다. CSS 를 작성할 때는 같은 요소에 대해 여러가지 선택자를 이용해서 접근이 가능하며 어디에서든 선언이 가능하다.

다만 이런 특징 때문에, FE 개발자들은 여기 저기 선언된 CSS 속성으로 인해 우선순위가 어떻게 되어있는지 파악이 어려워 코드 한 줄을 지웠음에도 불구하고 이상한 스타일이 적용되어있는 곤란을 겪었던 적이 있을 것이다.

뿐만 아니라, 유동적인 스타일을 작성하거나 특정 변수명을 활용해서 조금 더 가독성 있게 코드를 작성하고 싶은 생각이 들 때가 있는데, 지금까지 언급한 다양한 단점을 보완하면서 HTML 이라는 뼈대에 예쁜 살을 붙여줄 다양한 방법들이 계속해서 만들어지고 있다.

지금부터 CSS module, 프레임워크 그리고 CSS-in-JS 의 다양한 특징들을 알아보고 비교해보자.

📁 브라우저 렌더링 과정에서 CSSOM 이 생성되는 과정을 알고싶다면, 아래 포스팅을 참고하자.

브라우저 동작 과정 - 03. CSSOM 과 스타일 시트

🗂️ CSS module

앞서 서론에서 여기저기 선언된 스타일 때문에 적용되어있는 CSS 를 추적하기 어려운 점에 대해 언급했었다. CSS 는 모든 스타일이 global 하게 선언되기 때문에, 개발자가 직접 “특별한 이름”을 지어줘서 의도치 않은 스타일 뒤섞임(?)이 발생하지 않도록 해야한다.

CSS 에 “특별한 이름”을 지어주는 방법으로 BEM, OOCSS, SMACSS 와 같은 방법론이 있는데 나는 주로 BEM 방식을 많이 사용하곤 했다. (니코쌤 ✋🏻) 흠 하지만 이 방법들에도 분명 “한계”는 존재하고 global 하게 선언되기 보다 지역적으로 스타일을 적용할 수 있다면 “특별한 이름”에 대한 부담은 줄어들 것이다.

✋🏻 CSS module 을 알아보자.

“특별한 이름”에 대한 부담을 줄여줄 방법 중 하나로 CSS module 방식을 사용할 수 있다. 이것은 말 그대로 CSS (를) module (화 했습니다.) 라는 의미로 CSS 를 모듈화 하여 사용하는 방식이다.

CSS module 을 사용하면 아래 그림처럼 컴파일 이후에 클래스명이 고유해진다. 리액트 컴포넌트 파일에서 해당 CSS 파일을 불러올 때, 파일 경로, 파일 이름, 클래스 이름, 해쉬값 등이 사용되어 CSS 파일에 선언한 클래스 이름들을 고유할 수 있도록 변경시킨다.

이러한 이유로 기존의 CSS 에서 발생하는 클래스 중첩 문제를 방지할 수 있어서 각기 다른 모듈을 만들면 같은 클래스 명을 쓴다고 하더라도 중첩되지 않는다.

🗒️ CSS module 사용하는 방법 CRA 로 만든 프로젝트에서 CSS module 을 사용하려면 css 파일의 확장자를 ${name}.module.css 로 생성하면 되는데, 사용 방법에 대해서는 벨로퍼트님께서 아주 잘 정리해주신 이 링크를 참고해보자.

💆🏻‍♀️ CSS Preprocessor (CSS 전처리기)

CSS는 클래스명의 중첩 뿐만 아니라 여러 한계를 가지는데(물론 22년에 대폭 업데이트를 예고했기 때문에 그 한계는 많이 극복될 것으로 생각되지만!) 이러한 한계를 극복하기 위해서 CSS 전처리기를 사용하기도 한다. 전처리기가 어떤 것인지 한번 알아보자.

➡️ CSS 전처리기란

자신만의 특별한 syntax(구문)를 가지고 CSS를 생성하도록 하는 프로그램으로 CSS 의 한계를 뛰어넘기 위해 개발된 새로운 형태의 CSS 이다. CSS가 변수와 중첩을 제공하기 이전부터, 이 전처리기를 통해 변수, 함수, 상속 등 일반적인 프로그래밍 개념을 사용하여 CSS를 사용함에 있어 좀 더 자유도를 제공했다.

다시 정리하자면, CSS 전처리기는 CSS 파일을 만들기 전에 미리 처리한다는 의미CSS 구조를 가독성있게 만들어주고 유지 보수가 편리하도록 만들어주는 도구이다. 만약 CSS 전처리기를 통해 CSS 를 작성하면 렌더링 시 브라우저에서 구동 가능한 CSS 로 컴파일 된다.

🗒️ CSS 전처리기의 장점

  • CSS 를 보다 쉽게 작성할 수 있음

  • 다른 개발자들이 내가 작성한 CSS 를 보다 쉽게 이해할 수 있음

  • CSS 로는 구현 불가능한 변수 또는 함수를 사용할 수 있음

🗂️ CSS 전처리기 종류

1️⃣ Sass (Syntactically Awesome Style Sheets)

@mixin border-radius($radius)
    -webkit-border-radius: $radius
    -o-border-radius: $radius
    -moz-border-radius: $radius
    -ms-border-radius: $radius
    border-radius: $radius

.user-list
    // need to use special `@` syntax
    @include border-radius(10px)

Sass는 컴파일 과정을 통해 CSS 파일을 생성해 주는 CSS의 확장 언어이자 전처리기이다. 그렇다고 해서 Sass 가 CSS 의 대체언어는 아니고, 확장 언어이면서 CSS 코드를 생산해내기 위해 사용하는 일종의 도구이다.

Sass 가 제공하는 문법을 기반으로 코드를 작성한 다음에, 이를 컴파일해서 CSS 파일을 빌드하면 Sass 를 통해 스타일 시트를 생산하게 된다.

  • 특징

    • 모든 버전의 CSS 와 완벽하게 호환됨

    • 기능이 다양하고 안정성이 좋아 인지도가 높음

    • Sass 기반의 프레임워크가 다수 존재함

    • 대표적인 기능으로는 변수, 네스팅, 믹스인 등이 있음

  • 컴파일 방법

    • 오리지널 Ruby Sass 사용

      • gem install sass 로 설치

      • sass style.scss style.css 로 컴파일

    • GUI 어플리케이션 사용 – Koala, Hammer, Compass

    • libsass 사용

  • SCSS ? Sass ?

    : SCSS는 구문이다. SCSS 문법을 기반으로 코드를 작성하면, Sass 전처리기의 도움을 받아 컴파일러가 이를 CSS로 빌드할 수 있다. SCSS는 기존 Sass 구문에 비해 좀 더 CSS 코드와 유사한 형태를 띄고 있다. 어떤 것을 쓸지는 개인의 취향의 영역이 아닐까!

    • SCSS

        .text {
            font-size: 20px;
            a {
                color: #ffffff;
            }
        }
      
        @mixin border-radius($radius) {
            -webkit-border-radius: $radius;
               -moz-border-radius: $radius;
                -ms-border-radius: $radius;
                    border-radius: $radius;
        }
      
        .box { @include border-radius(10px); }
      
    • Sass

        .text
            font-size: 20px
            a
              color: #ffffff
      
        =border-radius($radius)
            -webkit-border-radius: $radius;
            -moz-border-radius: $radius;
            -ms-border-radius: $radius;
            border-radius: $radius;
      
        .box
            +border-radius(10px)
      

2️⃣ Less

.border-radius(@radius) {
    -webkit-border-radius: @radius;
    -o-border-radius: @radius;
    -moz-border-radius: @radius;
    -ms-border-radius: @radius;
    border-radius: @radius;
}

.user-list {
    // need to use special `.` syntax
    .border-radius(10px);
}

Less는 트위터의 부트스트랩(Bootstrap)에 사용되면서 알려졌다. 브라우저에서 자바스크립트 문법을 취하고 있으며 Node.js 기반으로 구동된다.

Less는 브라우저에 내장된 JS 인터프리터만으로 컴파일 가능하므로 그만큼 디펜던시에서 자유롭다. Sass 다음으로 활발히 개척되고 있어서, 쓸만한 라이브러리나 mixin 구현물들을 제법 쉽게 찾을 수 있다고 한다.

3️⃣ Stylus

.border-radius() {
    -webkit-border-radius: arguments
    -o-border-radius: arguments
    -moz-border-radius: arguments
    -ms-border-radius: arguments
    border-radius: arguments
}

.user-list
    border-radius: 10px

Stylus는 상대적으로 프로그래밍 언어의 특징을 많이 가지고 있어 CSS 프로퍼티 내에서 연산자나 함수, 루프 등을 비교적으로 자유롭게 사용할 수 있다. 그와 반대로 이런 특징들 때문에, 문법이 혼재해 있어서 처음 전처리를 시작하는 사람에게는 상대적으로 장벽이 높은 편이다.

⬅️ CSS 후처리기란

CSS 전처리기는 화면을 보여주기 전에 CSS가 변경되는 반면, 후처리기는 화면에 보여진 이후에 CSS 를 변경한다.

  • 전처리기 : 사용자가 코드를 작성 👉🏻 기본 CSS 로 변환 👉🏻 렌더링

  • 후처리기 : 사용자가 코드를 작성 👉🏻 렌더링 👉🏻 외부 프로그램을 사용해 스타일 변경

1️⃣ PostCSS

PostCSS는 CSS 후처리기이며, CSS 작성을 더 편하게 만들어주는 javascript 도구들(Plugins)이다. 일상적인 CSS 동작을 자동화하기 위해 자바스크립트 기반 플러그인을 사용하는 소프트웨어 개발 도구인데 위키백과, 페이스북, 깃허브의 코드를 개발하기 위해 사용되어 왔다.

Sass와 Less와 달리 PostCSS는 CSS 컴파일 틀 언어가 아닌 CSS 도구 개발을 위한 프레임워크이다. 그러나 Sass와 Less와 같은 틀 언어를 개발하기 위해 사용할 수 있다.

CSS Frameworks

Bootstrap 또는 Tailwind CSS 는 워낙 유명해서 아마 한 번 쯤은 들어봤을텐데, 이것들은 그런 것들을 포함한 게 CSS 프레임워크이다. CSS 프레임워크는 보통 미리 정의된 클래스여서 클래스네임을 직접 생성하지 않고 미리 정의된 클래스네임을 작성해서 스타일을 입힐 수 있다.


🖐🏻 잠깐,

지금까지는 CSS 를 사용하는 것에 도움을 주는 것들에 대해 정리해봤다. 다시 돌아보면 크게 세 가지가 있었다.

  • CSS modules

  • Pre-Post processors (CSS 전-후처리기)

  • CSS Frameworks

많은 분들이 이것은 CSS-in-CSS 로 작성하셨는데, 이게 무엇을 의미하는지 명확한 내용을 찾지는 못했다. 다만, 앞서 이야기했듯 CSS 사용을 도와주는 무언가로 생각하면 좋을 것 같다.

그렇다면 이제부터는 많은 FE 개발자들이 사용하고 있는 styled-component 가 속해있는 CSS-in-JS 를 살펴보자. 앞서 정리했던 것들은 CSS 만 사용했다면 이제부터는 JavaScript 코드로 CSS 를 작성하게 된다.


📒 CSS-in-JS

CSS-in-JS 는 자바스크립트 코드로 CSS를 작성하게 된다. CSS-in-JS 의 가장 큰 특징은 CSS module 과 같이 “모듈화”가 된다는 점인데 이것은 문서 기준이 아닌 컴포넌트 기준으로 스타일을 모듈화시킨다. 그렇기 때문에 컴포넌트 기반의 개발 작업에 용이하다.

JavaScript 를 사용해 CSS 를 작성하게 되면서, JavaScript 의 함수와 값을 공유하게 되며 컴포넌트에서 전달하는 값에 따라 동적으로 스타일을 적용할 수 있다.

아래는 styled-components 사용 예시인데, 컴포넌트에 props 를 전달해서 이 값에 따라 스타일을 적용하는 예시이다.

import React from 'react';
import styled, { css } from 'styled-components';

const StyledButton = styled.button`
    padding: 6px 12px;
    border-radius: 8px;
    color: ${(props) => props.color || 'white'};
    ${(props) => 
        props.promary &&
        css `
        color: white;
        background: navy;
        border-color: navy;
        `
    }
`;

function Button({ children, ...props }) {
    return <StyledButton {...props}>{children}</StyledButton>
}

export default Button;

🗒️ CSS-in-JS 의 특징

  • 클래스명을 지정하지 않는다.

    → 런타임에서 동적인 클래스명이 생성되며 head 태그 내에 style 태그가 생성된다.

  • JavaScript 로 작성되었기 때문에 JavaScript가 파싱되면 CSS-in-JS도 다시 파싱된다.

    → CSS-in-CSS 에 비해 비교적 속도가 느리다.

  • CSS가 컴포넌트에 종속된다.

    → 컴포넌트가 렌더링되었을 때에만 해당 스타일을 적용한다.

    → 모든 CSS 를 로드하지 않음

  • CSS 전처리기를 내장하고 있다.

🗒️ CSS-in-JS 의 장점

  • 모듈화가 되어있어 독립적

  • JavaScript와 상태 공유가 가능

  • CSS 우선순위 이슈 해결

  • 클래스 이름의 최소화

  • 미사용 코드 검출이 용이

🗒️ CSS-in-JS 의 단점

가장 큰 단점은 아무래도 CSS가 아니라 JS를 통해 불러오기 때문에 성능상 CSS보다 매우 떨어지는 편이다. 또한 번들의 크기 역시 성능에 영향을 줄 수 있다.

  • 런타임 오버헤드를 더함

  • 번들 크기를 늘림

  • React DevTools 를 어지럽힘


CSS-in-CSS vs CSS-in-JS

개발을 하면서 가장 힘든건 ‘무엇이 더 좋은가?’ 를 생각하며 최선의 답을 찾아가는 과정인 것 같다. 그렇기 때문에 늘 비슷한 무언가를 접하면 비교하는 과정은 필수적이다. CSS-in-CSS 와 CSS-in-JS 는 어떤 차이가 있고 어떤 상황에서 사용하면 좋을까?

CSSCSS-in-JS
Global namespace별도의 클래스 명명 규칙을 적용해야함클래스명이 빌드 타임에 유니크하게 변경되므로 별도의 명명 규칙 필요없음
Dependencies하나의 element에 여러 CSS 룰이 적용되기 때문에, 모든 스타일을 개발자가 기억해야 하는 문제CSS가 컴포넌트 단위로 추상화되기 때문에 CSS(모듈)간 의존성 고려가 필요없음
Dead Code EliminationCSS가 JS와 분리되어 관리되기 때문에 기능 변경에 따른 동기화 문제 발생 (삭제, 수정 등의 유지보수 문제)(주로) 컴포넌트와 CSS가 동일한 파일내에 존재하기 때문에 수정 및 삭제가 용이
Minification중복 제거를 위해 긴 클래스 이름 사용해 문서 사이즈가 커질 우려빌드 타임에 짧은 길이의 유니크한 클래스를 자동으로 생성하여 문서의 볼륨을 낮춰줌
Sharing ConstantsCSS가 분리돼 있어 JS의 상태 값을 공유하기 어려움컴포넌트 상태 공유가 가능
Non-deterministic ResolutionCSS 로드 순서에 따라 우선순위가 달라지기 때문에 CSS 로드 순서를 기억해야 함CSS가 컴포넌트 스코프로 적용되므로 우선순위에 따른 문제가 없음
IsolationCSS는 부모로부터 스타일을 상속하므로 하위 컴포넌트가 영향을 받음CSS가 컴포넌트에 격리되어 있기 때문에 상속 문제가 없음

1️⃣ 컴포넌트 기반 개발의 경우

🙃 CSS-in-CSS : 보이지 않는 컴포넌트와 관련된 스타일을 포함해 모든 CSS를 가져오므로 비효율적

🙂 CSS-in-JS : 활성화된 컴포넌트와 관련된 스타일만 가져오므로 효율적

2️⃣ 인터랙티브한 웹 페이지 개발의 경우

🙂 CSS-in-CSS : 렌더링 시 모든 CSS를 로드하므로 컴포넌트(JS)가 변경되더라도 CSS에 영향이 없으며 바로 적용

🙃 CSS-in-JS : 상태가 변경되어 컴포넌트가 리렌더링될 때마다 JS 내에 있는 CSS 코드를 같이 파싱해야 하므로 성능 저하의 우려가 있음

3️⃣ 컨텐츠가 많은 웹 페이지 개발의 경우

🙃 비교적 속도가 느린 CSS-in-JS 지양

굳이 비교는 해봤지만 결론은 뭐가 더 좋은건 없고 상황에 따른 적절한 판단을 내려야한다. 그리고 어쩔 수 없이 개인의 취향도 어느정도 반영이 될 것 같다. 개발을 빠르게 해서 생산성을 높이는 경우라면 CSS-in-JS 를 고려하면 좋겠지만 어느정도 프로젝트의 사이즈가 크다면 CSS-in-CSS 가 좋을 것 같다.

추가적으로 최근에 Next.js App 디렉토리로 개발을 했는데, 아직 CSS-in-JS 를 적용할 때 FOUC 해결이 쉽지가 않고, 서버 컴포넌트에서 CSS-in-JS 를 사용할 수가 없는 문제가 있어서 이 때는 CSS-in-CSS 를 사용하면 좋을 것 같다. (피눈물)


👩🏻‍🌾 마무리

뭔가 정리가 많이 된 것 같다. 사실 styled-component 를 사용할 때 컴포넌트명이 명확해져서 가독성이 높아지고, 또 tailwindCSS 를 사용했을 때는 클래스네임이 너무 많아서 가독성이 떨어지는 문제 때문에 이 점만 놓고서 tailwindCSS 를 미워했었다. 하지만 최근 프로젝트를 진행하면서 겪었던 문제들과 앞으로는 어떤게 좋을까를 두고 봤을 때 조금은 CSS-in-CSS에 눈길이 더 가기는 한다. 😅

많이 만들어보면서, 어떤 상황에 무엇이 최적일지 찾아가는 연습을 계속 해봐야겠다. 그리고 CSS-in-JS 의 동작 과정에 대해서도 조금 더 깊게 다뤄봐야겠다. 오늘도 재밌었다-!


참고

0
Subscribe to my newsletter

Read articles from leetrue directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

leetrue
leetrue

직면하는 모든 문제에 유치한 것은 없으며, 의미 없는 삽질 또한 없다고 믿습니다.