Developer/CSS

코드관리와 퍼포먼스 관점에서의 CSS-in-JS vs CSS in CSS

jaddong 2024. 7. 3. 16:01
320x100

CSS-in-CSS

빌드타임에서 모든 스타일을 생성되어 빌드의 결과물인 css파일의 클래스네임으로 html에 스타일링된다.

ex) bootstrap, tailwinds, sass, less

우리 컴포넌트에서 알아보기

  • 이미 빌드타임에 만들어져 있는 stylesheet로 스타일이 변경되는 걸 확인할 수 있다.

 

장점

  • 단순한 CSS 작성으로 러닝 커브가 낮다.
  • styled-component나 emotion같은 라이브러리가 포함되어있지 않아 번들 사이즈가 작다.
  • style과 관련하여 가독성을 해치지 않는다.
<span class={"line-clamp-2 truncate break-all leading-normal"}>{text}</span>

단점

  • 초기 로딩에 거대한 stylesheet 파일들이 포함될 수 있다.
  • 중복 코드가 발생할 수 있다.
더보기

List라는 컴포넌트가 아래와 같은 스타일이 필요했고, 이에 row라는 클래스네임을 지정했다. Item이라는 컴포넌트가 필요했고 이에 동일한 스타일이 필요해서 같은 클래스네임을 넣어줬다.

.row {
  padding: 0.5rem;
  border: 1px solid #ddd;
}
 
<List className="row"></List>

 

<Item className="row"></Item>

 

몇 달 뒤 다른 동료가 코드를 보았을 때 row라는 스타일이 어디에서 왔는지 딱 코드만 봤을 때 알 수가 없다. row클래스네임을 vscode에서 검색해봐야하고, 비슷한 유형의 스타일을 작성할 때 중복되진 않은 지 확인해봐야한다. 

  • BEM으로 className을 잘 확장할 수 있지만 코드베이스에서 여러 사람이 작업할 때 일관성을 유지하는 것이 문제이다.
더보기

중첩된 CSS 규칙은 아래와 같이 유지 관리가 어려워진다.

.row {
  padding: 0.5rem;
  border: 1px solid #ddd;
	 
	span{
		color: black;
	}
}
.tx-blue{
	color:blue
}

 

<Item className="row">
	<span className="tx-blue">text<span>
</Item>

 

 

또한 자바스크립트 변수와 이용해 동적 style을 구성할 수 없다.

function ExampleDiv({ width, height }:{width: number; height: number;}) {
  return <div class=`bg-black	w-[${width}px] h-[${height}px]`>안녕하세요</div>;
}
.w-{변수}px{
	width: {변수}px;
}

.h-{변수}px{
	height: {변수}px;
}

 

 

CSS-in-JS

초기 런타임에 global css가 작성되고 prop, state따라 runtime에서 javascript가 스타일을 동적으로 생성한다.

ex) styled-components, emotion

 

우리 컴포넌트에서 알아보기

  • prop에 따라 class가 바뀌고 그에 따른 stylesheet가 자동 생성되는 것을 확인할 수 있다.

 

성능 심층 분석

Spot : Emotion 에서 Sass로

  • 멤버 브라우저 화면은 20명의 유저를 보여줄 것이며,
  • 리스트 아이템을 감싸던 React.memo를 제거했습니다. 그리고,
  • 맨 위에 있는 <BrowseMembers> 컴포넌트가 1초마다 강제로 렌더링하도록 하고, 처음 10개의 렌더링 시간을 기록합니다.

  • With Emotion : React DevTools를 사용해 페이지를 프로파일링 했고 처음 10번의 렌더링 시간 평균은 54.3ms 였습니다.
  • Without Emotion : 위에서 설명한 것과 동일한 테스트를 반복했고 처음 10개 렌더링의 평균으로 27.7ms를 얻었습니다. 이는 원래 시간보다 48% 감소한 값입니다!

 

 

Airbnb : Sass에서 CSS in JS(react-with-styled)를 거쳐 Linaria로

  • 런타임 프레임워크 (react-with-styles, Emotion)와 빌드 타임 프레임워크 (Linaria, Treat)로 나누어 측정했고
  • 성능 향상은 주로 빌드시 JS에서 정적 CSS 파일로 스타일을 추출하는 Linaria 덕분에 이루어졌으므로 JS 번들 또는 런타임 CPU 오버헤드가 없으므로 거의 제로에 가까운 런타임 Treat보다 약간 우위에 있습니다

 

 

결론

  • 코드 관리와 생산성에 있어서는 🙋‍♀️ CSS in JS 가 우수하다.
    • className 규칙을 만들 필요나 팀원간의 유지해야하는 비용 소모가 적다.
    • 자바스크립트 변수와 리액트 props와 state를 이용해 다이나믹 스타일링에 제한없이 개발하기때문에 속도가 빠르다.
    • 중복 코드를 방지를 방지할 수 있다.
  • 렌더링 퍼포먼스에 있어서는 🙋‍♀️ CSS in CSS 가 우수한 편이다.
    • 런타임에서 css를 생산할 필요가 없기때문에, 런타임 오버헤드를 줄일 수 있다.
    • 브라우저에서 리액트가 렌더링 하는 동안 모든 프레임에 대해 모든 CSS 규칙을 효과적으로 재계산하게 할 필요가 없다.

 

near-zero runtime

  • stitches.js : stitches.js는 styled-components와 유사한 api를 가진 css-in-js라이브러리이지만 near-zero runtime 을 표방하고 있습니다. 단어 그대로 runtime을 아예 가지지 않는 것은 아니지만, component prop에 의한 interpolation을 최소화하는 방향의 API를 제공합니다.
  • tailwind + twin.macro : tailwind에 emotion css같이 사용할 수 있도록 추가된 형태로 최초에 로드하는 CSS크기는 작지만, 이후 runtime overhead 발생하긴 한다.
더보기

카카오 엔터: Emotion에서 Tailwind CSS + Twin.Macro로

 

카카오페이지 웹을 새로 개편할 때는 Emotion CSS를 사용함에 따라 발생하는 이런 문제들을 그대로 답습하면 안 된다고 생각했습니다. 우리가 극복해야 했던 문제들은 다음과 같습니다.

  1. 디자인 시스템을 위한 부가적인 코드가 실제 기능 코드를 침범한다. (Theme)
    • 디자인 코드가 컴포넌트의 가독성을 크게 해치거나 (CSS-in-JS), 기능 코드와 파편화되어 추가 비용을 발생시킨다 (Styled-Component).

이 문제들을 해결하려면 다음과 같은 전제가 보장되어야 합니다.

  1. 디자인 시스템을 위한 부가적인 코드가 실제 기능 코드를 침범하지 않아야 한다. (적어도 실제 컴포넌트 내부 구현을 침범하면 안 된다)
  2. 디자인 코드가 파편화되지 않아야 하며, 컴포넌트의 가독성을 해치는 부분을 최소화해야 한다.

우리 팀은 Tailwind CSS라는 라이브러리가 이 전제들을 만족시키는 가장 적절한 해결책이라고 생각했고, 따라서 문제 해결을 위한 도구로 프로젝트에 tailwind를 도입하도록 결정했습니다. tailwind를 사용하면 디자인 시스템에서 정의한 네이밍을 사용하기 위해 컴포넌트 내에 어떠한 코드도 추가할 필요가 없으며, CSS-in-JS 방식과 유사하게 HTML 요소 자체에 디자인을 명시하기 때문에 기능 코드와 디자인 코드의 파편화를 막되 유틸리티(utility)라는 개념을 통해 가독성을 해치는 부분을 최소화할 수 있습니다.

 

 

나의 생각

🚀 만들고 있는 서비스에서 rendering 최적화가 필요하지 않은 정도의 컴포넌트들만 다룬다면 runtime overhead는 무시할만한 수준일 수도 있고, 인터넷 속도나 지역에 따라 runtime overhead가 유저에게 영향을 미칠 수도 있다. 그런데 오히려 runtime이 존재하지 않음으로써 이를 따로 해결해주어야 하는 상황을 만날 수 있다. 그렇기 때문에, 무조건 ‘좋다 나쁘다’로 나눌 수 없으며 서비스의 특성과 계획을 고려하여 알맞은 CSS 사용방법을 선택해야 할 것이다.

 

🧐 개인적으로는 서비스의 크기가 커지면 커질 수록 CSS in JS에 맞는 부분이라고 생각이 든다. stylesheet은 일정하고 그 일정한 stylesheet를 이용해 개발을 하면 좋지만, 현실적으로 서비스 규모에 따라 stylesheet의 양이 많아지는 경우가 많고….(컴포넌트도 늘고….) 그래서 페이지에서 진입해 불러오는 stylesheet리소스를 최소화하고 그 이후 유저의 액션에 따라 stylesheet생성 및 렌더링 퍼포먼스가 유저에게 느껴질 정도가 아니라면 문제가 없다는 생각도 든다. zero-runtime 혹은 near-zero runtime처럼 하이브리드하게 가는 것이 결국엔 좋아보이긴하다.

 

🌠 SPA가 크게 유행을 한 이유가 자바스크립트를 이용한 자연스러운 인터렉션와 스타일, jQuery처럼 직접 DOM을 조작하지 않고 virtural DOM을 사용한다는 점에서 떴다고 생각하는데, 그러면서 간과했던 SEO 라던가 렌더링 퍼포먼스 같은 것들을 보완하기위한 style 라이브러리나 Next.js같은 프레임워크에 초점이 맞춰지는 느낌이다.

 

 

참고

FE개발그룹에서는 Tailwind CSS를 왜 도입했고, 어떻게 사용했을까?

(번역) 우리가 CSS-in-JS와 헤어지는 이유

CSS-in-JS, 무엇이 다른가요?

Real-world CSS vs. CSS-in-JS performance comparison

Rebuilding our tech stack for the new Facebook.com

웹 컴포넌트 스타일링 관리 : CSS-in-JS vs CSS-in-CSS

Jamstack에서 스타일시트를 최적화하는 법

반응형