StyleX combines the strengths and avoids the weaknesses of both inline styles and static CSS.

Inline Style

  • 장점
    • 컴포넌트 내부에서 스타일을 직접 정의 → props 처럼 유연하게 조합할 수 있음
    • 조건부 처리가 쉬움 <div style= />
    • scoping 문제 없음 → 전역 네이밍 충돌 걱정 없음
    • 런타임에 동적으로 스타일 적용 가능
  • 단점
    • 퍼포먼스 이슈: CSS 엔진은 className 을 최적화 캐시하지만, inline style 은 캐시 불가능
    • 스타일 기능 제한
      • :hover, :focus, @media 같은 pseudo-class, media query 사용 불가능
    • 재사용성 부족: 같은 스타일을 여러 컴포넌트에 적용할때 코드 중복

Static CSS

  • 장점
    • 브라우저 최적화 (스타일시트 캐싱, 렌더링 빠름)
    • 강력한 스타일링 기능
      • :hover, :focus, @media 사용 가능
    • 재사용 쉬움: 같은 className을 여러 곳에 적용 가능
    • 전역 스타일 적용 가능
  • 단점
    • 컴포넌트 결합도 낮음: 컴포넌트와 스타일이 멀리 떨어져 있어서 관리 어려움
    • 네이밍 충돌 위험: .button 이 프로젝트 여러 곳에 있으면 충돌 가능성 있음
    • 동적 스타일 어려움: 런타임 조건에 따라 스타일 변경하려면 별도 로직이 필요

📍 정리

항목 Inline Style Static CSS
퍼포먼스 ❌ 브라우저 캐싱 불가 ✅ 브라우저 캐싱 가능
동적 스타일링 ✅ 조건부 스타일링 간편 ❌ 런타임 조건 스타일링 어려움
스타일 표현력 ❌ 제한적 (:hover, @media 불가) ✅ 강력한 기능 지원 (:hover 등)

StyleX는 Inline Style처럼 컴포넌트 내부에 스타일을 선언할 수 있고, props를 이용해 조건부 스타일링도 간편하게 처리할 수 있다. 또한, className 을 자동 해시화해서 네이밍 충돌을 방지한다.
이외에도, Static CSS 처럼 정적 추출을 해서 성능을 높이고, 미디어 쿼리, pseudo-class 등을 지원한다.

즉, Inline Style 과 Static CSS 의 장점을 취하고, 단점을 피할 수 있다!

StyleX builds optimized styles using collision-free atomic CSS which is superior to what could be authored and maintained by hand.

Atomic CSS

한 가지 스타일 속성(property)당 한 개의 className 을 만든다 (참고)

  • NON Atomic CSS - 여러 스타일을 묶어서 하나의 클래스에 작성 - 재사용 제한적 (한 묶음을 전체로 재사용해야 함) - 네임 충돌 가능성 존재 - 묶음 재사용은 빠르지만, CSS 크기가 커질 수 있음
.button {
  border: none;
  font-size: 14px;
  background-color: purple;
  border-radius: 3px;
}
<button className="button">Hello world</button>
  • Atomic CSS: 모든 스타일 선언은 각각의 규칙이 된다.
    • 스타일 속성 하나당 하나의 클래스
    • 재사용성 아주 높음 (필요한 속성만 조합)
    • 사실상 네이밍 충돌 없음
    • 필요한 속성만 조합해서 스타일을 재사용할 수 있어서 프로젝트의 규모가 커질수록 스타일 시트의 크기는 작아짐
    • 브라우저 최적화
      • 브라우저가 style conflict를 빨리 판단하고 layout 계산도 빠르게 끝난다. → 하나의 class는 하나의 속성만 가지기 때문!
        • CSSOM: 이 요소는 어떤 스타일을 가져야 하지? 겹치는 스타일이 있다면 우선순위를 따져야지. 겹치는 스타일끼리 conflict가 없게 계산해야지
.b-none {
  border: none;
}

.fs-14 {
  font-size: 14px;
}

.bgc-purple {
  background-color: purple;
}

.br-3 {
  border-radius: 3px;
}
<button className="b-none fs-14 bgc-purple br-3">Hello world</button>

Atomic CSS 단점

  1. HTML 이 지저분해진다
  2. 동적으로 스타일을 바꿀때 어렵다: 다양한 조건을 조합하려면 className 을 일일이 조합해야 해서 복잡해짐
  3. 직접 작성하려면 사람이 관리하기 힘들다: 수백 개, 수천 개 클래스를 기억하거나 관리해야 함

StyleX는 이런 문제를 어떻게 해결할까

StyleX의 해결책

사람이 직접 Atomic CSS 를 관리하는 것은 정말 귀찮다. (클래스 개수가 많고, 이름 짓기도 귀찮고, 조합하기도 힘듦)

  • Babel 플러그인 단계에서 자동으로 Atomic CSS 를 만들어줌
  • hash(예: data-stylex-hash="abc123")를 붙여서 충돌도 막고 (컴파일러가 자동으로 해시 생성 + props 와 맵핑까지 해줌 !!)
  • 필요한 class만 조합해서 최소한의 className 을 만들어줌
  • 사용하지 않은 스타일은 빌드 타임에 버려서 번들 최적화까지 해줌
  • 자동 해시 덕분에 className 자체도 짧고 충돌 위험이 없음

빌드 타임에 stylex가 하는 일은?

Stylex 는 Zero Runtime CSS (CSS-in-JS) 라이브러리의 일종으로, 빌드 타임에 CSS 파일이 준비된다.
따라서, 컴파일러가 별도로 필요하다. stylex 는 아래 컴파일러를 함께 사용하도록 안내한다.
@stylexjs/babel-plugin 는 JS로 작성된 코드를 빌드 타임에 CSS 파일로 변환해주는 컴파일러다. (하지만, 나는 사이드 프로젝트에서 해당 컴파일러를 사용하지 않았다. Babel 플러그인을 사용하면 기본 SWC 컴파일러가 비활성화되는데, Next.js는 Rust 기반의 SWC를 사용해 빌드 속도가 빠르기 때문에 이를 유지하고 싶었다. 내가 사용한 컴파일러는 이것이다.) 아래 역할을 한다.

  • 클래스명 생성
  • 불필요한 스타일 제거(tree-shaking)
  • 최적화된 CSS 추출
  1. stylex.create를 찾아서
    • CSS 클래스를 미리 만들어 놓고
    • CSS 파일이나 style 태그로 추출
  2. stylex.props를 찾아서
    • 어떤 스타일이 적용될지를 분석하고
    • 미리 className을 mapping해서 적용
  3. 코드를 변환

예제 코드로 이해하자면 아래와 같다.

  • 컴파일 전 (개발자가 작성한 코드)
<button {...stylex.props(styles.base, primary && styles.primary)}>
  • 컴파일 후
<button
  className={primary ? "_baseHash _primaryHash" : "_baseHash"}
  data-stylex-hash={primary ? "baseHash primaryHash" : "baseHash"}
/>
._baseHash {
  color: gray;
}

._primaryHash {
  color: blue;
}

개인적인 궁금증 💭

왜 이런 문법 형태를 사용했을까? (props 를 왜 사용했을까? 저 문법이 뜻하는 바가 뭘까?)

import * as stylex from "@stylexjs/stylex";

const styles = stylex.create({
  button: {
    backgroundColor: "blue",
    color: "white",
  },
});

function Button() {
  return <button {...stylex.props(styles.button)}>Click</button>;
}
  1. className 이상의 데이터를 포함하고자 props 라는 이름을 사용했을 것이다.
<button {...stylex.props(styles.button)} />

⬇️ 컴파일된 결과

<button className="_abc123" dir="ltr" data-style-src="global-layout/Header.tsx:118"/>
  1. 리액트 개발자들에게 익숙한 형태
// 리액트 문법
<button {...props} />

// stylex 문법
<button {...stylex.props(styles.base, styles.primary)} />
  1. 조건부, 배열, null, safe 처리까지 지원
stylex.props(
  styles.base,
  isPrimary && styles.primary,
  isDarkMode ? styles.dark : styles.light
);

빌드 타임에 가능한 className 을 모두 추출stylex.create()하고, 런타임에는 조건부에 맞춰서 선택stylex.props()한다. 하지만, 스타일 자체를 런타임에 생성하는 것은 안 됨!

결국 이 모든 기능을 효율적으로 처리하려면 단순한 문자열 병합만으로는 한계가 있기 때문에, StyleX는 스타일 정보를 JavaScript 객체 형태로 감싸 {…} 방식으로 JSX에 전달하도록 설계되었을 것이다! 🤔

Next.js App Router 사이드 프로젝트를 하면서 SSR 을 최대한 활용하고 싶어서 Stylex 를 선택했다. 아직 개발 단계이지만, 페이스북에서 꾸준히 업데이트하고 있고, 실제로 SSR 과도 궁합이 좋아 보여 계속 주시할 예정이다!

Stylex 공식문서는 여기서 확인하세요 🔎

댓글남기기