styleX
Meta에서 출시한 CSS-in-JS 라이브러리.
컴파일러 기반의 라이브러리로, 컴파일 시 CSS 파일로 변환함 (전통적인 CSS에 가까운 성능 제공)
스타일 관리와 성능 최적화 면에서 전통적인 CSS의 강점을 유지하고, CSS-in-JS의 특징인 유연성과 확장성 제공
또 다른 특징인 Atomic CSS
- CSS 출력 최소화. 컴포넌트 수가 많아지고 크기가 커지더라도 CSS 크기가 일정하게 유지됨
특징
- 빠른 속도
- 런타임 스타일 인젝션*이 없음
- 모든 스타일은 컴파일 시 정적 css 파일에 번들로 제공됨
*런타임 스타일 인젝션: 웹 애플리케이션이 런타임 중에 스타일을 동적으로 추가하거나 변경하는 기술
- 확장성
- CSS 출력 최소화. 컴포넌트 수가 많아지고 크기가 커지더라도 CSS 크기가 일정하게 유지됨
- 예측 가능성
- element의 클래스 네임이 동일한 요소에만 직접 스타일 지정 가능 ???
- ( Class names on an element can only directly style that same element.)
- 합성 가능성
- 조건부로 스타일 적용
- 컴포넌트 파일, 경계에서 임의의 스타일을 병합하고 작성할 수 있음
- 스타일 반복 가능
- 타입 안정성
- type-safe API, style, theme
- colocation
- 스타일을 로컬에서 작성, 적용, reasoning할 수 있도록 설계됨
- 저비용 추상화
- 로컬에서 생성, 적용되는 스타일
동일한 파일 내에서 스타일을 작성하고 사용할 때 styleX의 비용은 0임
스타일X가 stylex.create 호출을 컴파일하는 것 외에도, stylex.props 콜도 컴파일함
import * as stylex from 'stylex';
const styles = stylex.create({
red: {color: 'red'},
});
let a = stylex.props(styles.red);
// compiles down to:
//JS output
import * as stylex from 'stylex';
let a = {className: 'x1e2nbdu'};
// CSS output
.x1e2nbdu { color: red; }
- using styles across files
stylex.create 호출은 완전히 없어지지 않고 클래스 네임에 object mapping key를 남김. 그리고 stylex.props() 호출은 런타임에 실행됨
import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({
foo: {
color: 'red',
},
bar: {
backgroundColor: 'blue',
},
});
function MyComponent({style}) {
return <div {...stylex.props(styles.foo, styles.bar, style)} />;
}
// compiles down to
// js output
import * as stylex from '@stylexjs/stylex';
const styles = {
foo: {
color: 'x1e2nbdu',
$$css: true,
},
bar: {
backgroundColor: 'x1t391ir',
$$css: true,
},
};
function MyComponent({style}) {
return <div {...stylex.props(styles.foo, styles.bar, style)} />;
}
//css output
.x1e2nbdu { color: red; }
.x1t391ir { background-color: blue; }
사용하기
핵심 기능은 두가지:
stylex.create: 스타일 생성
stylex.props: 스타일을 element에 적용
// 1. configure the compiler
import plugin from '@stylexjs/rollup-plugin';
const config = () => ({
plugins: [
plugin({ ...options })
]
})
export default config;
---
// 2. define styles
import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({
root: {
width: '100%',
maxWidth: 800,
minHeight: 40,
},
child: {
backgroundColor: 'black',
marginBlock: '1rem',
},
});
const colorStyles = stylex.create({
red: { // namespace
backgroundColor: 'red',
borderColor: 'darkred',
},
green: { // namespace
backgroundColor: 'lightgreen',
borderColor: 'darkgreen',
},
});
// 3. use styles
// props() function으로 스타일 전달
function ReactDiv({ color, isActive, style }) {
return <div {...stylex.props(
styles.main,
// apply styles conditionally
isActive && styles.active,
// choose a style variant based on a prop
colorStyles[color],
// styles passed as props
style,
)} />;
}
- type-safe 적용: 스타일 타입화를 통해 컴포넌트 스타일 커스터마이징에 대한 정교한 규칙 적용 가능
import type {StyleXStyles} from '@stylexjs/stylex';
type Props = {
// 색상과 배경색만 허용하도록 정의 (다른 스타일은 허용하지 않음)
style?: StyleXStyles<{color?: string; backgroundColor?: string}>;
//...
};
...
import type {StyleXStylesWithout} from '@stylexjs/stylex';
type Props = {
// 속성에 여러 스타일을 허용하나, 마진만 비허용
style?: StyleXStylesWithout<{
margin: unknown;
marginBlock: unknown;
marginInline: unknown;
marginTop: unknown;
marginBottom: unknown;
marginLeft: unknown;
marginRight: unknown;
marginBlockStart: unknown;
marginBlockEnd: unknown;
marginInlineStart: unknown;
marginInlineEnd: unknown;
}>;
//...
};
- sharable constant
- stylex.create는 생성된 클래스 이름들을 완전히 추상화한다. 모호한 자바스크립트 객체들을 strong type으로 처리하여 객체가 나타내는 스타일을 표현함
- stylex.defineVars는 생성된 CSS 변수들의 이름들을 추상화한다. 상수로 가져와서 스타일 내에서 바로 사용할 수 있음
- stylex.keyframes는 키프레임 애니메이션들의 이름들을 추상화한다. 대신 상수로 선언되며 참조로 사용됨
- stylex.props
- className과 스타일 속성을 가진 객체 반환
- 다양한 프레임워크에서 작동하게 하려면 wrapper function이 필요할수도 있음
- 제약
- 컴파일 시점 이전의 컴파일에 의존하기에, 모든 스타일이 정적으로 분석할 수 있어야 한다
- = 스타일 객체가 아래만을 포함해야 한다
- 일반 객체 리터럴
- 문자열 리터럴
- 숫자 리터럴
- 배열 리터럴
- null 또는 undefined
- 상수, 간단한 표현, 내장 메서드 (예: .toString())
- 동적 스타일에 대한 화살표 함수
- 비허용 항목
- styleX 함수를 제외한 함수 호출
- 다른 모듈에서 가져온 값 (.stylex.js 파일을 사용하여 stylex로 생성된 css 변수 제외)
Installation
런타임 패키지
npm install --save @stylexjs/stylex
컴파일러: 빌드 타임 컴파일러 사용
Development: 스타일이 컴파일 타임에 처리되도록 Babel plugin 구성
npm install --save-dev @stylexjs/babel-plugin
// babel.config.js
// Next.js는 기본적으로 babel 제공함.
import styleXPlugin from '@stylexjs/babel-plugin';
const config = {
plugins: [
[
styleXPlugin,
{
dev: true,
// Set this to true for snapshot testing
// default: false
test: false,
// Required for CSS variable support
unstable_moduleResolution: {
// type: 'commonJS' | 'haste'
// default: 'commonJS'
type: 'commonJS',
// The absolute path to the root directory of your project
rootDir: __dirname,
},
},
],
],
};
export default config;
styleX vs TailwindCSS
styleX Tailwind
자바스크립트 중심의 스타일링 방식 | 마크업에 직접 적용되는 유틸리티 클래스 활용 |
JavaScript, CSS-in-JS 개념에 대한 이해 필요 | 초보자도 쉽게 사용 가능 |
확장 가능한 솔루션이 필요한 대규모 애플리케이션에 이상적 | 사용자 정의 가능, 반응성이 뛰어남 |
성능과 유지보수가 필요한 대규모 애플리케이션에 이상적 | 빠른 ui 개발, 소규모 프로젝트에 적합 |
런타임 오버헤드가 없음 | 사용 편의성, 빠른 개발 |
난관
stylex.create should never be called. It should be compiled away
- 컴파일러가 올바르게 구성되지 않아 발생한 오류: 컴파일 타임 접근 방식으로 작동하지 않는 동적인 패턴인 경우
stylex는 컴파일러이므로, stylex.create 호출은 컴파일된다. 즉, stylex.create 호출 내의 스타일은 정적으로 분석할 수 있어야 하며 다른 모듈에서 값을 임의로 가져와서 사용할 수 없다.
동적 스타일에는 stylex.create 내의 함수만 사용해야 한다. 스타일은 컴파일 시점에 알 수 없으므로 런타임에 생성해야 하는데, 이런 경우 값만 동적일 수 있다(키는 정적이어야 함). 또한 함수 구문은 { } 본문을 포함할 수 없고 객체를 직접 반환해야 함
- import { stylex } from "@stylexjs/stylex” → import * as stylex from "@stylexjs/stylex”