
Vanilla Extract는 사실 자바스크립트 정적 분석 도구 중 하나에요. 우리가 코드를 작성할 때, 코드가 어떻게 구성되어 있는지, 어떤 부분이 문제인지 분석하는 도구를
'정적 분석 도구' 라고 합니다.
Vanilla Extract는 특히 CSS 관련 작업을 돕는 도구에요.
보통 React 같은 프레임워크를 사용할 때 스타일을 작성하는 방법 중 하나로, CSS-in-JS라는 방식이 있죠.
이 방식은 자바스크립트에서 CSS를 작성하는 것을 말해요. Vanilla Extract는 이 과정에서 더 최적화된 방식으로 CSS를 처리해주는 도구입니다.
정적 분석 도구 : 스타일 코드 를 컴파일(빌드) 단계에서 정적 CSS 파일을 생성해주는 도구
CSS-in-JS의 가장 큰 단점 중 하나는 스타일을 실행 시점에 동적으로 생성하는 거라, 성능이 떨어질 수 있다는 점이에요.
Vanilla Extract는 미리 정적인 CSS 파일로 변환해서 실행 성능을 개선해 줍니다.
자바스크립트는 동적 타입 언어라서 타입 오류를 잡아내기 쉽지 않아요. 하지만 Vanilla Extract는 TypeScript를 활용해서 스타일 작성 시에도 타입 안정성을 유지할 수 있어요.
즉, 잘못된 CSS 속성을 작성하면 에러를 미리 잡아줄 수 있는 거죠.
실행 시점에 CSS를 생성하는 것이 아니라, 빌드 단계에서 미리 CSS 파일로 변환하기 때문에 런타임에서 추가적인 성능 비용이 없어요.
CSS Modules를 사용하면, 일반적인 SCSS처럼 파일을 나눠서 스타일을 작성할 수 있습니다.
그리고 아래와 같이 className을 통해 여러 클래스를 조합해서 사용할 수 있어요.
import * as styles from './styles.scss';
import clsx from 'clsx';
const cx = clsx.bind(styles);
return (
<div className={cx('base', 'active')} />
);
이렇게 사용하면, 스타일이 런타임에서 자동으로 난독화되기 때문에, 다른 곳에서 같은 이름의 클래스가 충돌하는 걱정을 덜 수 있습니다.
CSS-in-JS
반면에, CSS-in-JS는 스타일을 컴포넌트 내부에서 템플릿 리터럴로 작성합니다. 아래와 같은 방식으로 말이죠.
const StyledBox = styled.div`
color: white;
background: ${props => props.background};
`;
이 방식의 장점은 props를 통해 스타일을 동적으로 변경할 수 있다는 겁니다. 하지만 스타일을 컴포넌트 내에서 하나씩 정의해야 하기 때문에, 재사용성이 조금 떨어질 수 있습니다.
CSS Modules는 여러 스타일을 조합해서 재사용할 수 있습니다.
반면 CSS-in-JS는 한 컴포넌트에 스타일을 하나씩 직접 적용해야 합니다.
Vanilla Extract는 타입스크립트와 깊게 연동되어 있어서, CSS 스타일에도 정적 타이핑을 적용할 수 있습니다. 즉, 스타일 작성 시 오타나 잘못된 값을 미리 잡아줄 수 있어요.
예를 들어, CSS Modules에서 오타가 생긴다면 런타임에야 오류가 드러나지만, Vanilla Extract는 컴파일 타임에 오류를 잡아줍니다. 덕분에 디버깅 시간이 줄어들고, 코드 품질이 높아집니다.
CSS-in-JS는 다양한 스타일링 방법을 제공하고, 스타일 로직을 컴포넌트 안에서 처리할 수 있어 유연성이 큽니다.
하지만, 그만큼 복잡한 설정이 필요하고, 프로젝트마다 스타일 코드 형태가 달라질 수 있다는 단점도 있습니다.
설정이 많을수록 버전 업그레이드 시 설정 충돌이 일어날 가능성도 커집니다.
css 를 타입스크립트로 쓴다...
Vanilla Extract를 사용하면, styles.css나 styles.scss 같은 파일이 아니라, styles.css.ts 같은 .css.ts 확장자 파일에 CSS 코드를 작성하게 돼요.
이 방식 덕분에 타입 추론이 가능하고, 작성한 스타일 코드에서 실수로 변수를 잘못 입력하더라도 컴파일 타임에 오류를 잡아줄 수 있어요.
import { style } from '@vanilla-extract/css';
// 사용할 수 있는 색상 팔레트 타입 선언
type ColorPalette = 'red' | 'blue' | 'green';
// 스타일 정의 시 색상은 미리 정의된 팔레트 값만 사용 가능
export const coloredBox = style<{ color: ColorPalette }>({
padding: '10px',
backgroundColor: 'red', // 타입스크립트가 'red', 'blue', 'green'만 허용
});
// 컴포넌트에서 사용
const boxStyle = coloredBox({ color: 'blue' }); // 정상 작동
const invalidBoxStyle = coloredBox({ color: 'yellow' }); // 오류 발생: 'yellow'는 허용되지 않음
코드 복사
import { style } from '@vanilla-extract/css';
// 사용할 수 있는 폰트 크기 타입 정의
type FontSize = 'small' | 'medium' | 'large';
// 폰트 크기는 미리 정의된 값만 허용
export const textStyle = style<{ fontSize: FontSize }>({
fontSize: 'medium', // 'small', 'medium', 'large' 중 하나만 허용
});
// 사용 예시
const smallText = textStyle({ fontSize: 'small' }); // 정상 작동
const invalidText = textStyle({ fontSize: 'extra-large' }); // 오류 발생: 'extra-large'는 허용되지 않음
미디어 쿼리에서도 특정 브레이크포인트만 허용하도록 타입을 선언할 수 있습니다.
코드 복사
import { style } from '@vanilla-extract/css';
// 사용할 수 있는 브레이크포인트 타입 선언
type Breakpoint = 'mobile' | 'tablet' | 'desktop';
export const responsiveStyle = style<{ breakpoint: Breakpoint }>({
display: 'block',
'@media': {
'(min-width: 600px)': {
display: 'flex', // 모바일에서는 block, tablet에서는 flex
},
'(min-width: 900px)': {
display: 'grid', // 데스크탑에서는 grid
},
},
});
// 사용 예시
const mobileStyle = responsiveStyle({ breakpoint: 'mobile' }); // 정상 작동
const invalidBreakpointStyle = responsiveStyle({ breakpoint: 'wide-screen' }); // 오류 발생: 'wide-screen'은
나머지 2가지 특성과 다양한 API 에 대해서는 다음 시간에 알아보도록 하겠습니다!