
웹 개발자라면 누구나 CSS를 사용해 본 경험이 있을 것이다. 그리고 대부분은 CSS를 작성하면서 여러 가지 불편함을 느꼈을 것이다. 클래스 이름 충돌, 스타일 유지보수의 어려움, JS와의 연동 불가,그리고 타입 안전성 부재 등 다양한 문제점들이 존재한다.
(👨🏻🏫 : 이를 해결하기 위해, module.css를 적용하거나, BEM 방식 등을 적용해 작성하면 클래스 이름 충돌은 없었답니다. 하지만 그 긴 클래스 이름들... 정말 지겹지 않나요?)
이러한 문제를 해결하기 위해 다양한 CSS-in-JS 라이브러리들이 등장했다. Styled Components, Emotion 등이 대표적인 예시이다. 그러나 이러한 라이브러리들은 런타임 오버헤드라는 또 다른 문제를 가지고 있었다.
그리고 이제 우리에게는 Vanilla Extract CSS라는 새로운 대안이 있다. 타입스크립트로 작성하는 제로 런타임 스타일시트, 이것이 바로 Vanilla Extract CSS의 핵심이다.
CSS-in-JS는 2014년 Facebook 개발자인 Christopher Chedeau(일명 Vjeux)의 발표에서 처음 소개됐다. 이 개념은 Facebook이 겪던 CSS 관리의 어려움을 해결한 사례를 바탕으로 발전하게 됐다.
웹의 초기 시절을 살펴보면 CSS-in-JS의 등장 배경을 이해할 수 있다:

Vjeux는 CSS를 작성할 때 다음과 같은 어려움을 지적했다:
CSS-in-JS는 이러한 문제들을 해결하기 위해 JavaScript 코드에서 CSS를 작성하는 방식으로 등장했다. 이후 개념이 발전하면서 Styled Components, Emotion 등 다양한 라이브러리가 등장했다.
CSS-in-JS의 주된 선택 요소는 "스타일을 얼마나 동적으로 작성할 수 있는가"였다. 즉, JavaScript 변수를 사용할 수 있는지, 사용할 수 있다면 그 범위는 어디까지인지가 중요한 기준이 됐다.
이러한 방식은 React, Vue, Angular와 같은 모던 자바스크립트 라이브러리의 인기와 함께 컴포넌트 기반 개발 방법론이 주류가 되면서 더욱 발전하게 됐다.
Vanilla Extract는 타입스크립트 또는 자바스크립트로 CSS를 작성할 수 있게 해주는 라이브러리이다. 이 라이브러리의 가장 큰 특징은 빌드 타임에 정적 CSS 파일을 생성한다는 점이다. 이는 런타임에 스타일을 평가하고 주입하는 다른 CSS-in-JS 라이브러리들과 차별화되는 부분이다.
(👨🏻🏫 : 빌드 타임에 CSS 파일을 생성한다고요? 그럼 그냥 CSS 파일을 직접 작성하는 것과 뭐가 다른가요? 🤔 이제부터 그 차이점을 알아봅시다!)
Vanilla Extract를 사용하기 위해서는 먼저 패키지를 설치해야 한다:
npm install @vanilla-extract/css
스타일은 .css.ts 또는 .css.js 파일에 정의한다:
*// styles.css.ts*
import { style } from '@vanilla-extract/css';
export const buttonStyle = style({
backgroundColor: 'blue',
color: 'white',
padding: '10px 15px',
borderRadius: '5px'
});
그리고 이 스타일을 컴포넌트에서 다음과 같이 사용할 수 있다:
*// Button.tsx*
import { buttonStyle } from './styles.css';
const Button = ({ children }) => (
<button className={buttonStyle}>{children}</button>
);
(👨🏻🏫 : 와! 정말 간단하죠? CSS-in-JS의 장점과 일반 CSS의 성능을 모두 가져갈 수 있네요!)
웹 개발 생태계에서 CSS 작성 방식은 계속해서 진화해왔다. 초기에는 단순한 CSS 파일을 작성했지만, 프로젝트가 복잡해지면서 Sass, Less와 같은 전처리기가 등장했다. 그리고 컴포넌트 기반 개발이 대세가 되면서 CSS-in-JS 라이브러리들이 인기를 끌기 시작했다.
Styled Components나 Emotion과 같은 CSS-in-JS 라이브러리들은 컴포넌트와 스타일을 함께 관리할 수 있는 편리함을 제공했다. 하지만 이러한 라이브러리들은 런타임에 스타일을 평가하고 DOM에 주입하는 방식으로 작동한다. 이는 다음과 같은 문제를 야기한다:
Vanilla Extract는 다음과 같은 최근 웹 개발 트렌드를 활용하여 등장했다 :
이러한 배경에서 Vanilla Extract는 타입 안전성, 제로 런타임, 그리고 강력한 테마 시스템을 제공하는 새로운 대안으로 등장하게 되었다.
CSS-in-JS 라이브러리는 크게 두 가지 유형으로 나눌 수 있다: 런타임 라이브러리와 제로 런타임 라이브러리. 이 둘의 가장 큰 차이점은 스타일이 언제, 어떻게 처리되는지에 있다.

런타임 CSS-in-JS 라이브러리는 다음과 같은 특징을 가진다:
*// Styled Components 예시*
const Button = styled.button`
background-color: ${props => props.primary ? 'blue' : 'white'};
color: ${props => props.primary ? 'white' : 'blue'};
padding: 10px 15px;
border-radius: 5px;
`;
(👨🏻🏫 : 이렇게 props에 따라 스타일이 동적으로 변하는 건 정말 편리하죠! 하지만 이 편리함에는 대가가 따른답니다... 바로 성능 오버헤드죠!)

제로 런타임 CSS-in-JS 라이브러리는 다음과 같은 특징을 가진다:
*// Vanilla Extract 예시*
import { style, styleVariants } from '@vanilla-extract/css';
const baseButton = style({
padding: '10px 15px',
borderRadius: '5px'
});
export const button = styleVariants({
primary: [baseButton, { backgroundColor: 'blue', color: 'white' }],
secondary: [baseButton, { backgroundColor: 'white', color: 'blue' }]
});
런타임 오버헤드의 유무는 특히 대규모 애플리케이션에서 눈에 띄는 성능 차이를 가져온다:
(👨🏻🏫 : 특히 모바일 기기나 저사양 디바이스에서는 이런 성능 차이가 더 크게 느껴질 수 있답니다!)
Vanilla Extract 외에도 제로 런타임 CSS-in-JS 라이브러리는 여러 가지가 있다. 각각의 라이브러리는 고유한 특징과 장단점을 가지고 있다.
Linaria는 제로 런타임 CSS-in-JS 라이브러리 중 하나로, Vanilla Extract와 유사한 접근 방식을 취한다.
주요 특징:
*// Linaria 예시*
import { css } from 'linaria';
const button = css`
background-color: blue;
color: white;
padding: 10px 15px;
border-radius: 5px;
`;
Astroturf는 또 다른 제로 런타임 CSS-in-JS 라이브러리로, CSS 모듈과 유사한 접근 방식을 취한다.
주요 특징:
*// Astroturf 예시*
import React from "react";
import { css } from "astroturf";
const btn = css`
color: black;
border: 1px solid black;
background-color: white;
`;
export default function Button({ children }) {
return <button className={btn}>{children}</button>;
}
| 라이브러리 | 타입 안전성 | 스타일 정의 방식 | 테마 지원 | 빌드 도구 통합 |
|---|---|---|---|---|
| Vanilla Extract | 우수 (TypeScript 네이티브) | 객체 리터럴 | 강력한 테마 시스템 | Webpack, Vite, Next.js 등 |
| Linaria | 보통 (TypeScript 지원) | 템플릿 리터럴 | CSS 변수 기반 | Webpack, Rollup 등 |
| Astroturf | 보통 (TypeScript 지원) | 템플릿 리터럴 | 제한적 | Webpack 중심 |
| Styled Components (런타임) | 보통 (TypeScript 지원) | 템플릿 리터럴 | ThemeProvider | 대부분의 빌드 도구 |
(👨🏻🏫 : 저는 개인적으로 TypeScript를 좋아해서 Vanilla Extract를 선호한답니다. 객체 리터럴 방식이 코드 자동 완성도 잘 되고, 타입 체크도 확실히 되거든요!)
Vanilla Extract CSS는 현대 웹 개발에서 스타일링 문제를 해결하기 위한 강력한 도구이다. 타입스크립트의 타입 안전성, 빌드 타임 CSS 생성을 통한 제로 런타임, 그리고 강력한 테마 시스템을 제공함으로써 기존 CSS-in-JS 라이브러리들의 단점을 극복했다.
Vanilla Extract는 다음과 같은 상황에서 특히 유용하다:
(👨🏻🏫 : 물론 모든 프로젝트에 Vanilla Extract가 최선의 선택은 아닙니다. 작은 프로젝트나 빠르게 프로토타입을 만들어야 하는 경우에는 다른 솔루션이 더 적합할 수 있답니다. 항상 프로젝트의 요구사항과 팀의 선호도를 고려해서 결정하세요!)
웹 개발 생태계는 계속해서 진화하고 있으며, Vanilla Extract는 그 진화의 한 부분이다. 타입 안전성과 성능을 모두 챙기고 싶은 개발자들에게 Vanilla Extract는 매력적인 선택지가 될 것이다.
결론적으로, Vanilla Extract는 CSS-in-JS의 개발자 경험과 일반 CSS의 성능적 이점을 모두 제공하는 균형 잡힌 솔루션이다. 타입스크립트를 사용하는 프로젝트에서 스타일링 솔루션을 고민하고 있다면, Vanilla Extract를 고려해볼 만하다.
🙇🏻 글 내에 틀린 점, 오탈자, 비판, 공감 등 모두 적어주셔도 됩니다. 감사합니다..! 🙇🏻