본문 바로가기
카테고리 없음

번역) 확장 가능한 CSS를 위한 Meta(Facebook) 해결책 StyleX

by 얼굴값하는사람 2024. 1. 2.
반응형

Facebook이 CSS와 관련하여 어떤 문제에 직면했는지 이해하면 StyleX의 디자인 결정에 대하여 더 많이 이해하는 데 도움이 됩니다.

3년 전, Facebook 디자인 시스템 구성 요소를 담당하는 사람들은 문제에 직면했습니다. 그들은 Facebook의 전체 웹 프론트엔드를 완전히 React로 재작성하고 있었고 CSS를 처리할 방법이 필요했습니다. 이는 많은 대규모 프로젝트와 회사가 직면하는 어려움입니다. 빌드 타임과 런타임, CSS와 JavaScript로 선언된 CSS, Tailwind와 오픈소스 CSS 시스템을 사용할지 여부 등.. 옵션이 너무 많습니다. 

Facebook이 결정한 것은 애플리케이션 아키텍처의 세 번째 핵심인 새로운 CSS 플랫폼을 구축하는 것이었습니다. GraphQL과 Relay는 데이터를 처리하고, React는 DOM을 처리하며, 이제 StyleX가 스타일을 처리합니다. 게다가 그들은 이 새로운 CSS 시스템이 과거의 실수로부터 배우기를 원했습니다.

Facebook의 이전 아키텍처는 CSS 모듈과 유사했습니다. 그러나 이 접근 방식에는 확장 문제가 있었습니다. 크기 조정 문제를 해결하기 위해 필요에 따라 CSS를 런타임에 로드했습니다. 그러나 지연 로딩으로 인해 다른 경로를 통해 사이트를 탐색하면 CSS가 다른 순서로 로드되어 예상치 못한 스타일이 생성되는 셀렉터 우선순위 문제가 발생했습니다.

StyleX는 항상 원하는 스타일을 얻을 수 있도록 보장하는 "확정적 해상도(Deterministic resolution)" 를 제공하여 이 문제를 해결합니다 . 이 결정적 해상도의 가치를 이해하기 위해 간단한 버튼부터 시작해 보겠습니다.

 

StyleX 버튼

StyleX 구성 요소인 Button 의 예를 살펴보겠습니다.

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

const styles = stylex.create({
  base: {
    appearance: "none",
    borderWidth: 0,
    borderStyle: "none",
    backgroundColor: "blue",
    color: "white",
    borderRadius: 4,
    paddingBlock: 4,
    paddingInline: 8,
  },
});

export default function Button({
  onClick,
  children,
}: Readonly<{
  onClick: () => void;
  children: React.ReactNode;
}>) {
  return (
    <button {...stylex.props(styles.base)} onClick={onClick}>
      {children}
    </button>
  );
}

 

우선, 스타일은 이를 사용하는 구성 요소와 바로 같은 위치에 배치됩니다. 이는 emotion CSS 작성 스타일이 마음에 든다면 DX 및 코드 가독성 관점에서 큰 승리입니다. 그러나 NET과 같은 런타임 시스템에서는 얻을 수 없는 컴파일 타임 CSS의 이점을 여전히 얻을 수 있습니다 emotion.

불행하게도 이러한 단축 스타일을 사용하려는 경우 실제 Tailwind 사용 편의성을 얻을 수 없습니다(디자인 토큰에 대한 지원이 있고 원할 경우 해당 단축 스타일을 만들 수는 있지만). Tailwind 속기가 없기 때문에 스타일을 제어할 수 있습니다.

 

Control Over Styling

Tailwind를 구성 요소 시스템의 기초로 사용할 때의 문제점은 스타일을 제어할 수 있다는 것입니다. color버튼 소비자가 의 및 backgroundColor만 변경할 수 있도록 허용하고 싶다고 가정해 보겠습니다 Button. Tailwind 구성요소를 사용하면 이를 위한 특정 소품을 가질 수 있지만 사람들이 몇 가지 스타일 이상을 조정할 수 있도록 하려면 확장이 불가능합니다. 따라서 일부 저자는 원하는 것은 extraClasses무엇이든 추가할 수 있는 속성을 허용합니다. 그러나 제한 없이 원하는 대로 변경할 수 있으므로 나중에 구성 요소를 버전화하기가 어렵습니다.

StyleX는 이에 대한 환상적인 솔루션을 제공합니다.

import type { StyleXStyles } from "@stylexjs/stylex/lib/StyleXTypes";

export default function Button({
  onClick,
  children,
  style,
}: Readonly<{
  onClick: () => void;
  children: React.ReactNode;
  style?: StyleXStyles<{
    backgroundColor?: string;
    color?: string;
  }>;
}>) {
  return (
    <button {...stylex.props(styles.base, style)} onClick={onClick}>
      {children}
    </button>
  );
}

 

이라는 또 다른 속성을 추가 style하고 이를 재정의할 수 있는 스타일로만 제한했습니다. 그리고 호출 style뒤에 넣기 때문에 재정의 스타일이 기본 스타일을 적절하게 재정의한다는 것을 보장할 수 있습니다.styles.basestylex.props

스타일링에 대한 통제력이 얼마나 강화되었는지 확인해보세요. 즉, ButtonCSS를 변경할 수 있는 것과 변경할 수 없는 것에 대한 명확한 경계가 있기 때문에 자신 있게 버전을 관리할 수 있습니다.

Button스타일을 재정의할 때 our를 사용하는 방법은 다음과 같습니다 .

이라는 또 다른 속성을 추가 style하고 이를 재정의할 수 있는 스타일로만 제한했습니다. 그리고 호출 style뒤에 넣기 때문에 재정의 스타일이 기본 스타일을 적절하게 재정의한다는 것을 보장할 수 있습니다.styles.basestylex.props

스타일링에 대한 통제력이 얼마나 강화되었는지 확인해보세요. 즉, ButtonCSS를 변경할 수 있는 것과 변경할 수 없는 것에 대한 명확한 경계가 있기 때문에 자신 있게 버전을 관리할 수 있습니다.

Button스타일을 재정의할 때 our를 사용하는 방법은 다음과 같습니다 .

const buttonStyles = stylex.create({
  red: {
    backgroundColor: "red",
    color: "blue",
  },
});

<StyleableButton onClick={onClick} **style={buttonStyles.red}**>
  Styleable Button
</StyleableButton>

우리가 무엇을 변경하고 있는지는 매우 명확하며 TypeScript는 구성 요소 작성자가 재정의하려고 의도한 스타일만 재정의할 수 있도록 강제합니다.

 

Design Tokens And Theming

세분화된 수준에서 스타일을 재정의할 수 있다는 점은 훌륭하지만 합리적인 디자인 시스템에는 디자인 토큰 및 테마에 대한 지원이 필요하며 StyleX는 두 가지 모두에 대한 탁월한 유형 안전 지원을 제공합니다.

몇 가지 토큰을 정의하는 것부터 시작해 보겠습니다.

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

export const buttonTokens = stylex.defineVars({
  bgColor: "blue",
  textColor: "white",
  cornerRadius: "4px",
  paddingBlock: "4px",
  paddingInline: "8px",
});

bgColor특정 CSS 속성으로 제한되는 대신 다음과 같은 이름을 사용할 수 있습니다 . 그런 다음 이 토큰을 다음과 같이 매핑할 수 있습니다 Button.

import * as stylex from "@stylexjs/stylex";
import type { StyleXStyles, Theme } from "@stylexjs/stylex/lib/StyleXTypes";

import "./ButtonTokens.stylex";
import { buttonTokens } from "./ButtonTokens.stylex";

export default function Button({
  onClick,
  children,
  style,
  theme,
}: {
  onClick: () => void;
  children: React.ReactNode;
  style?: StyleXStyles;
  theme?: Theme<typeof buttonTokens>;
}) {
  return (
    <button {...stylex.props(theme, styles.base, style)} onClick={onClick}>
      {children}
    </button>
  );
}

const styles = stylex.create({
  base: {
    appearance: "none",
    borderWidth: 0,
    borderStyle: "none",
    backgroundColor: buttonTokens.bgColor,
    color: buttonTokens.textColor,
    borderRadius: buttonTokens.cornerRadius,
    paddingBlock: buttonTokens.paddingBlock,
    paddingInline: buttonTokens.paddingInline,
  },
});

그래서 이제 우리는 styles디자인 토큰을 기반으로 하는 것과 Button같은 하드 코딩된 값 borderWidth과 테마 값 의 조합을 기반으로 를 생성하고 있습니다 .colortextColor

또한 속성을 추가하고 theme이를 stylex.props.

소비자 측에서는 createTheme버튼 토큰을 사용하고 해당 테마를 기반으로 하는 테마를 만들 수 있습니다.

const DARK_MODE = "@media (prefers-color-scheme: dark)";

const corpTheme = stylex.createTheme(buttonTokens, {
  bgColor: {
    default: "black",
    [DARK_MODE]: "white",
  },
  textColor: {
    default: "white",
    [DARK_MODE]: "black",
  },
  cornerRadius: "4px",
  paddingBlock: "4px",
  paddingInline: "8px",
});

미디어 쿼리를 기반으로 테마 값을 지정하기 위해 객체 구문을 사용할 수도 있습니다. 예를 들어 이 경우 어두운 모드에서는 버튼 색상이 반전됩니다.

그런 다음 페이지 코드에서 테마를 구성 요소에 직접 보낼 수 있습니다.

<Button onClick={onClick} **theme={corpTheme}**>
  Corp Button
</Button>

또는 테마를 지정하는 컨테이너 내에 버튼을 넣을 수도 있습니다.

<div {...stylex.props(corpTheme)}>
  <Button onClick={onClick}>
    Corp Button
  </Button>
</div>

CSS 변수의 마법을 통해 Button내부의 모든 항목이 div이제 해당 테마를 갖게 됩니다.

물론 이 모든 것은 컴파일 타임에 계산되고 클래스가 문자열로 코드에 주입되기 때문에 React Server Components 및 Server Side Rendering에서 작동합니다.

 

Conditional and Dynamic Styles

종종 우리는 빌드 시간 CSS를 정적이라고 생각하지만 StyleX는 조건부 스타일과 동적 스타일을 모두 지원합니다. emphasis원래 버튼에 플래그를 추가해 보겠습니다 .

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

const styles = stylex.create({
  ...,
  emphasized: {
    fontWeight: "bold",
  },
});

export default function Button({
  onClick,
  children,
  emphasized,
}: Readonly<{
  onClick: () => void;
  children: React.ReactNode;
  emphasized?: boolean;
}>) {
  return (
    <button
      {...stylex.props(styles.base, emphasized && styles.emphasized)}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

우리가 해야 할 일은 강조된 스타일에 대한 또 다른 섹션을 정의에 추가한 styles다음 플래그를 확인하고 조건부로 스타일을 추가하는 것뿐입니다. 정말 쉽습니다!

저는 StyleX가 할 수 있는 일의 표면적인 부분만 살펴보았습니다. 위치나 색상과 같은 값을 런타임에 생성해야 하는 경우 스타일은 동적일 수 있습니다 . 이와 같은 옵션은 variant다른 옵션을 추가하여 stylex.create변형을 정의한 다음 소품을 기반으로 올바른 변형 스타일을 사용하여 쉽게 지원됩니다.

StyleX 팀은 또한 모든 OpenProps를 StyleX로 포팅하여 다양한 간격 옵션, 색상, 애니메이션 등을 손쉽게 사용할 수 있도록 했습니다.

 

결론

StyleX를 구축한 다음 이를 Facebook.com 의 React 재작성의 주요 구성 요소로 사용한 최종 결과는 사이트가 시작하는 데 약 130Kb의 CSS에서 실행된다는 것입니다. 많은 것처럼 보일 수도 있지만 CSS는 모든 경로의 모든 기능을 다루었습니다. 브라우저는 이를 한 번 로드하고 완료됩니다. 더 이상 로딩 순서 문제가 없습니다. 3년의 개발 끝에 이제 최대 170Kb에 이르렀지만 개발자 수와 기능을 생각하면 그 추세가 인상적입니다.

StyleX는 Facebook에서 3년 동안 사용되어 왔으며 전투적으로 강화되었습니다. 이제는 모든 작업과 경험을 활용할 수 있는 오픈 소스로 나아가고 있습니다.

StyleX는 Tailwind만큼 사용하기 쉽지 않기 때문에 많은 분들이 StyleX를 무시할 것이라고 확신합니다. 나는 그것이 사실이라는 것을 부정하지는 않지만, 내 생각에는 믿을 수 없을 정도로 강력한 두 시스템인 Tailwind와 StyleX는 스펙트럼의 반대쪽 끝을 위한 것입니다.

나는 Tailwind가 일부 색상, 간격 및 중단점 값 이외의 세트 디자인 시스템 없이 신속하게 작업하는 소규모 팀에 많은 가치를 제공한다고 생각합니다.

StyleX는 훨씬 더 큰 프로젝트, 팀, 심지어 팀 그룹을 지원하도록 설계되었습니다. StyleX는 이 스펙트럼의 끝에서 우리에게 귀중한 도구를 제공합니다. 나는 그것에 대해 매우 감사하고 있습니다. 저는 대기업에서 일했는데 팀 전체에 걸쳐 디자인 시스템을 구축하는 것은 쉽지도 않고 잘 이해되지도 않습니다. Meta가 스펙트럼의 끝 부분에 있는 새로운 도구를 오픈 소스로 가져오는 것을 보는 것은 매우 기쁩니다. 매우 감사합니다.

 

 

 

원글

https://jherr2020.medium.com/stylex-metas-solution-to-scalable-css-0e06972d9bc4

반응형