카테고리 없음

캐치테이블에서 사용하는 Vanilla Extract이 뭘까?

klm hyeon woo 2024. 5. 17. 21:15

목차

· Runtime 시점에서의 CSS in JS

· Vanilla Extract

· Vanilla Extract 시작하기

· Vanilla Extract, 어떻게 생각해요?


Runtime 시점에서의 CSS in JS

React와 같은 SPA가 대두되면서 CSS in JS가 많은 인기를 얻고 있습니다. 이를테면 React의 경우 styled-component 또는 emotion을 예를 들 수 있는데, CSS in JS는 아래와 같은 장점을 가지고 있습니다. CSS in JS의 대두되는 장점은 아래와 같습니다.

1. CSS에서 JS 문법을 사용할 수 있어 생산성을 증가시킬 수 있다.
2. 컴포넌트 파일에 관련 코드들과 동일 선상에서 같이 위치시킬 수 있다.
  2-1. 기존에는 CSS 파일과 JS 파일을 분리해야했기 때문에 코드를 일일히 찾아야하는 불편함
  2-2. 만약 사용하지 않는 CSS 코드가 있다고 가정했을 때, 파일이 분리되어있다면 이를 찾기가 힘듬
3. 지역 스코프 스타일로 className이 겹치지 않음을 보장한다.

 

위와 같은 장점 덕에 CSS in JS를 보통 많이 사용을 하지만, 모든 기술에는 단점이 있기 마련입니다. 기존의 CSS in JS와 같은 경우 런타임에 JS 파일이 실행되면서 Style을 생성하지만, style 생성의 규모가 크고 빈번할 수록 성능이 저하될 수 있다는 단점을 가지고 있습니다.

Vanilla Extract

Runtime CSS in JS의 성능상 문제점을 해결하기 위해 Zero-runtime CSS in JS인 Vanilla Extract 등장합니다.

그 외에 유사한 라이브러리들은 Linaria, Sttitches 등이 존재하며, Vanilla Extract의 특징은 아래와 같습니다. 기본적으로 CSS Modules-inTypeScript 라고 생각을 하면 됩니다. (공식 홈페이지에서도 자랑스럽게 stylesheets in TypeScript라고,, 👀)

1. 빌드 타임에 ts 파일을 CSS 파일로 만든다 (SASS와 유사)
2. 타입스크립트를 사용하기 때문에 type-safe하게 핸들링할 수 있다.
3. 프론트엔드 프레임워크에 구애받지 않는다.
4. Tailwind 처럼 Atomic CSS를 구성할 수도 있다.

Vanilla Extract 간단하게 시작하기

build 타임에 CSS 파일로 변환되고, head 태그에 삽입되기 때문에 bundle 설정은 필수입니다.

거의 모든 번들러를 지원하고 있으며, 공식 홈페이지에서 bundler-integration 에서 사용하는 번들러에 맞게 설정을 하면 됩니다.

저는 요즘은 보통 Vite를 사용하고 있으니 Vite 환경 설정에 맞춰서 진행을 하면 될 것 같습니다.

몇 가지 스타일 만들기

몇 가지 공식 홈페이지에서 제시하는 간단한 스타일을 만들어보겠습니다. 공식 문서가 정말 친절한 편이라고 생각이 들고, 직관적인 설명들과 코드로 이루어져있어 처음 접하는 초심자도 접근하기 편한 접근성을 제시하고 있습니다.

// style.css.ts
import { style, globalStyle } from '@vanilla-extract/css';

export const myStyle = style({
	display: 'flex',
    paddingTop: '3px',
    fontSize: '42px',
});

// 이렇게 글로벌로도 설정이 가능합니다
globalStyle('body', 
	margin: 0
});
// App.tsx

<div classname={myStyle}> 컵케이크 </div>

// 결과물
// .s_myStyle__t0h71y0 {
//    display: flex;
//    padding-top: 3px;
//    font-size: 42px;
// }

위와 같은 방식이 기본적인 스타일을 만드는 방법입니다. `myStyle`은 빌드 타임에 `s_myStyle__t0h71y0`이라는 `className`으로 변경되어 사용이 되며, 또는 스타일을 조각해서 Utility Style을 만들 수도 있다. 그리고 px 단위를 생략해도 자동적으로 px로 변합니다.

export const flexCenter = style({
	padding: 10 // 10px로 계산이 되어진다.
    marginTop: 25,
    
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center,
});

만약 변수처리를 하고 싶다면 `createVar`을 사용하면 됩니다. `createVar`는 유니크한 변수 이름을 만들어줍니다.

import { style, createVar } from '@vanilla-extract/css';

const myVar = createVar();

const myStyle = style({
	vars: {
    	[myVar]: 'purple' 
        // '--my-custom-color': 'puple'과 같이 바로 작성을 해도 무관하다 
    };
});

여러 style들을 다중으로 합성하여 사용을 할 수도 있습니다.

const first = style({ padding: 12 });
const twice = style([base, { background: 'blue' }]);

다중 선택자는 아래와 같이 사용을 할 수 있습니다.

import { style } from '@vanilla-extract/css';

const link = style({
	selectors: {
    	'&:hover:not(:active)': {
        	border: '2px solid aguamarine'
        },
        'nav li > &': {
        	textDecoration: 'underline'
        }
    },
});

테마 만들기

Vanilla Extract 에서 제공하는 `createTheme`을 통해서 테마를 만들 수 있습니다.

CSS 변수 생성에 관련된 집합체라고 생각이 드는데, 아래의 예제들에서 해당 부분이 어떤 부분인지 이해를 할 수 있습니다.

export const [themeClass, vars] = createTheme({
  color: {
    brand: 'blue'
  },
  font: {
    body: 'arial'
  }
});

createTheme의 결과 값인 vars는 변수명들이 저장되어있는 객체라고 생각을 하면됩니다. 그리고 themeClass는 변수 값들을 매핑해놓은 스타일 조각의 className을 의미합니다. vars는 테마가 이런 변수들을 가지고 있다는 정보를 알려주는 객체입니다. 실제 blue와 같은 색상 등 스타일 값들을 실제로 적용해주는 주요 요소는 themeClass입니다. 조금 더 쉽게 이해를 해보자면 위의 코드를 조금 풀어보자면, 아래와 같이 풀 수 있습니다.

export const vars = {
  color: {
    brand: 'var(--color-brand__l520oi1)'
  },
  font: {
    body: 'var(--font-body__l520oi2)'
  }
};

export const themeClass = 'theme_themeClass__l520oi0';

// .theme_themeClass__l520oi0는 이런 스타일 조각로 정리할 수 있다
.theme_themeClass__l520oi0 {
	--color-brand__l520oi1 : blue;
    --font-body__l520oi2 : arial
}

사용은 아래와 같이 가능합니다.

// style.css.ts

export const [themeClass, vars] = createTheme({
	color: {
    	brand: "blue"
    },
    font: {
    	body: "Pretendard"
    },
});

export const CustomText = style({
	color: vars.color.brand,
    font: vars.font.body,
});
// App.tsx

function App() {
	return (
    	// 상단의 className에서 themeClass를 정의하여 변수 명들을 정의
    	<div className={themeClass}>
        	// 기존에 정의해둔 변수들을 꺼내서 그대로 적용
        	<div classname={brandText}> 컵케이크 </div>
        </div>
    )
}

위와 같이 CSS 변수가 적용되는 것을 확인할 수 있습니다

변칙적인 상황이 필요하다면 아래와 같이 변칙적인 반응형 변수에 따른 스타일 적용도 가능합니다.

export const [themeClass, vars] = createTheme({
  color: {
    brand: 'blue'
  },
  font: {
    body: 'arial'
  }
});

export const otherThemeClass = createTheme(vars, {
  color: {
    brand: 'red'
  },
  font: {
    body: 'helvetica'
  }
});

// App.tsx
function App() {
  const [isPrimary, setIsPrimary] = useState(false);

  return (
    <div className={isPrimary? themeClass : otherThemeClass}>
      <div className={brandText}>안녕하세요</div>
    </div>
  );
}

여기까지, 위의 내용들이 해당 기술 스택을 시작하면서 가장 기초적인 내용들입니다. 사실 여타 CSS in JS와 그리 크게 차이는 없지만, sprinkles, recipe, dynamic과 같은 내용들을 이용해서 다양한 패키지를 구성할 수 있기 때문에 그 외에 더 많은 내용들이 있지만, 공식 문서에서 다양한 프로퍼티들이 기다리고 있습니다. 해당 기술 스택을 사용하면서 더 중점적인 내용들을 가지고 앞으로 와볼게요

Vanila Extract, 어떻게 생각해요?

사실 포스팅에서는 특징 정도만 잡고 가려고 했는데, 공식 문서를 보다보니까 조금 재미있는 내용들이 많아서 다음 프로젝트에서는 한번 적용을 해보고 싶은 기술 스택이라는 생각이 들었습니다. 처음에는 우연히 캐치테이블에서 사용하는 기술 스택에 관련된 포스팅을 보게되면서 알게된 기술 스택이었습니다. 그러다 관련 키워드로 서칭을 진행하면서 다양한 기업들이 해당 기술 스택을 채택하는 모습을 보며 특징에 대한 부분들을 파악하게 되었는데, 매우 흥미로운 부분들이 많았습니다.

 

아직은 CSS-in-JS에서 큰 오버헤드를 경험해본 적은 없지만, 오버헤드에 대비해 해당 기술 스택을 경험해놓는 것과 더불어 여러 패키지를 사용할 수 있다는 점이 정말 큰 강점 중 하나라고 생각이 듭니다. sprinkles를 통해 tailwind처럼 atomic하게 css를 구성할 수도 있고, recipe를 활용하여 variant 기반 스타일링을 구성하거나, dynamic을 활용하여 동적으로 값을 업데이트하는 등 되게 다양하게 활용할 수 있는 방안들이 많은 것 같습니다 (atomic css를 그렇게 지향하지는 않지만,, 한번 익숙해지면 편하다는 장점이 있는 것 같아요 🫢)


레퍼런스

 

Vanilla Extract란 무엇인가

기존의 Runtime CSS in JS는 어떤 문제가 있었고 Vanilla Extract가 어떻게 해결했는지, 어떻게 사용하는지에 대해서 알아봅니다.

velog.io

 

 

캐치테이블 B2C 서비스에서 vanilla-extract를 사용하는 방법

캐치테이블 Vanilla-Extract 도입부터 활용까지!

medium.com

 

 

카카오헤어샵 vanilla-extract 도입기

카카오헤어샵 마크업 내재화 이야기 | 안녕하세요, 카카오헤어샵 플랫폼 개발팀 프론트엔드 개발자 조이입니다! 오늘은 프론트엔드 파트에서 진행한 마크업 내재화에 대한 이야기를 해보려고

brunch.co.kr