이 글의 목적
- 다양한 CSS-in-JS 라이브러리가 있는데 이들은 어떤 차이점이 있을까?
- 더 나아가 어떤 상황에서 어떤 라이브러리를 사용하면 좋을까?
CSS-in-JS가 대세인 이유
- 중복되지 않는 class 이름을 고려할 필요가 없다.
- JS 코드와 CSS가 상태 값을 공유할 수 있다.
- 컴포넌트와 스타일 코드를 쉽게 오갈 수 있다.
- 자동으로 vendor-prefix을 붙여준다.
⇒ 개발 친화적 🌻 DX ( Developer Experience )
CSS-in-JS 트랜드
2020, 2021 들어서서 많은 CSS-in-JS 라이브러리가 등장했다.
css-in-js의 동작 방식은 크게 runtime, zero-runtime 으로 나눠진다.
- runtime이 반드시 성능저하를 발생시키진 않고 프로젝트 규모와 상황에 따라 달라질 수 있음을 염두하고 살펴보도록 하자.
runtime
javascript runtime에서 필요한 CSS를 동적으로 만들어 적용한다.
대표적으로 잘 알려진 styled-component, emotion 이 있다.
아래 styled-components로 만든 예시를 살펴보자.
버튼의 상태가 바뀌면서 style 코드가 동적으로 생성되어 삽입되는 것을 볼 수 있다.
css-loader가 필요 없다.
- css파일을 생성하지 않기에 webpack에서 css-loader가 필요 없다.
런타임 오버헤드가 발생할 수 있다.
- 런타임에서 동적으로 스타일을 생성하기에 스타일이 수시로 변경된다면...
- ex) 스크롤, 드래그 앤 드랍 관련 복잡한 에니메이션
styled-components 과 emotion의 차이를 알고 싶다면?
zero-runtime
런타임에 css를 생성하지않으면서 페이지를 더 빨리 로드할 수 있다.
JS 번들에서 styles코드를 모두 실행되어야 페이지가 로드된다.
runtime에서 스타일이 생성되지 않는다.
- props 변화에 따른 동적인 스타일은 css 변수를 통해 적용한다.
빌드 타임에 css를 생성해야기에 webpack 설정을 해야 한다.
- React CRA을 사용한다면 eject해서 webpack 설정해야 하는데 굉장히 번거롭다.
- runtime에서 css polyfill를 사용할 수 없어 브라우저 버전 이슈가 있을 수 있다.
첫 load는 빠르지만, 첫 paint는 느릴 수 있다.
css styles까지 모두 로드가 되어야 첫 paint를 시작된다.
반면 runtime에서는 style를 로드하면서 첫 paint를 시작할 수 있다. ( 로딩화면 )
대표적인 라이브러리
- linaria
- styled-component 문법 그대로 사용해서 러닝커브가 없을 것 같다.
- styled-components와 속도 비교
- mini-css-extract-plugin에 의해 critical css를 판단할 수 없는 경우 linaria의 collect를 사용가능하다.
- vanilla-extract
- 사실상 typescript로 css를 작성하는 라이브러리. (
.css.ts
)
- css-module와 거의 흡사하다.
- tagged template literals를 지원하지 않는다.
- 현재 굉장히 높은 만족도와 관심을 받고 있다.
+ ) SSR
- critical CSS
- critical CSS 추출과 runtime CSS 생성은 trade-off 관계를 갖고 있다.
- SSR에서 중요한 쟁점이다. 사전에 CSS 추출을 할 것인가?
near-zero-runtime ( stitches )
SSR 환경에서도 잘 동작이 되도록 세팅이 되었다.
runtime overhead와 zero-runtime의 제약을 해결 ⇒ 빠르다
benchmarks
런타임에서 각각의 CSS 프로퍼티가 Atomic CSS처럼 적용된다.
- 반복되는 style을 atomics class로 변환하여 class를 재사용한다.
- 불필요한 런타임에서의 props interpolations를 줄인다.
const StitchesButton1 = styled("button", {
color: "red",
fontSize: 24,
});
const StitchesButton2 = styled("button", {
color: "red",
fontSize: 24,
});
하지만 style 순서가 바뀌면 재사용할 수 없다.
const StitchesButton2 = styled("button", {
fontSize: 24,
color: "red",
});
참고
emotion과 같이 CSSStylesSheet.insertRule 을 사용하여 CSSOM에 직접 삽입한다.
사전에 정의한 variants에 의해서 runtime 스타일링이 진행된다.
- 공식문서가 굉장히 친절하고 친절하게 되어 있어서
최고의 개발경험 제공
- 사전에 정의한 테마 변수, variants 자동완성 제공
그리고 한계점...
- css style extraction이 되지 않는다.
- tagged template literals를 지원하지 않는다.
Object syntax을 사용하는 이유는 keep the bundle size to a minimum이라고 한다.
+ ) Atomic CSS
- 필요한 수치를 입력을 해두면 필요한 CSS를 자동으로 생성을 해두는 주문형(on-demand) 패러다임
- 대표적인 라이브러리는 tailwindcss
- 중복된 style 코드를 atomic한 클래스로 묶음으로 style sheet 사이즈를 줄인다.
CSS-in-JS 사용에 고려할 사항
- runtime overhead가 발생할 될 서비스인가?
- 없다면 기존 runtime CSS-in-JS를 써도 전혀 문제가 없을 것이다.
- 직접 스타일을 작성하는가? 아니면 CSS 소스를 사용하는가?
- tagged template literals를 지원하지 않는다면 CSS 소스를 옮기기 굉장히 불편할 것이다.
- 따라서 stitches와 vanilla-extract의 도입은 CSS 변환의 수고스러움을 감수해야 할 것이다.
- SSR인가 CSR인가?
- SSR를 설정하기 불편한 것이 있고 Critical CSS 최적화된 것이 있다.
- CSR는 runtime stylesheets, SSR는 static CSS에 이점을 갖는다. 참고
멋진 개발팀들에서는 어떻게 라이브러리를 선택하는지 살펴보면 큰 도움이 될 것이다.
개인적으로 생각했을 땐 결국 개발 친화적 🌻
이 제일 중요한 이슈인 것 같다.
- 개발팀에서 편하게 디자인 시스템을 구축할 수 있는가?
- 개발 리소스 비용이 크기 때문...
맺으면서
프론트엔드의 흐름, build-time?
현대 프론트엔드의 작업흐름이 build-time에서
- 최신 CSS-in-JS 라이브러리들이 build-time(zero-runtime)으로 개발되고 있다.
- TypeScript를 통해서 build-time 및 run-time 이전에 type 체크, code IntelliSense를 제공해준다.
- webpack, babel를 통해서 다양한 브라우저에서 동작하는 앱을 만든다.
- Next.js가 각광을 받으면서 CSR에서 SSR로 웹이 개발되어 가고 있다.
- React, Vue를 넘어서 현재 Svelte가 많은 사람들의 주목을 받고 있다.
- run-time에서 Virtual DOM를 통해 비교하여 변경사항을 반영하는 것이 아닌
build-time에서 어느 부분이 변경될지 파악하고 DOM을 업데이트하는 효율적인 명령 코드로 변환하여 사용한다.
javascript → typescript → build-time → compiler → c++
대학교 1학년 때부터 세뇌받듯이 들었던 말이 프로그래밍 언어의 근본은 C++
이다.
javascript, python 같은 스크립트 언어는 감히 고개를 내밀지도 못했다.
뛰어난 개발자들이 프론트엔드 생태계에 들어오면서 컴파일 시점에서의 최적화가 고도화 된 것 같다.
카카오의 좋은 문화 중 하나가 복잡할 수록 본질로 돌아가라
이다.
그렇게 복잡한 웹 프론트엔드 생태계가 근본(?)을 되찾아가고 있지 않나 생각이 들었다.
참고
글 이전
내용 너무 좋아요 👍