ghdtjgus

이번 3주차 과제는 css 라이브러리 분석이었다.

우리가 기술 스택을 정함에 있어 이유가 있어야 한다고 생각했다.

남들이 많이 쓰니까 말고 이 라이브러리를 써야 하는 이유가 있어야 한다고 생각했기 때문에 그에 초점을 맞춰서 과제를 진행했던 것 같다.

한 명이 한 라이브러리를 깊게 알아오는 방식으로 이번 과제가 진행되었다.

 

tailwind css, stylex, stitches, panda css 이렇게 네 가지 라이브러리를 알아오기로 했었는데, 그 이유는 우리가 추후 이 디자인 시스템 컴포넌트들을 사용해서 Next.js 페이지를 만들 것이기 때문에 제로 런타임 css 라이브러리들로 선택했었다.

이와 관련된 얘기들은 본문에 추후 작성할 예정이다.

 

나는 이전부터 관심이 있었던 panda css를 알아보기로 했다.

사실, 디프만에 있는 10분만이라는 좋은 레퍼런스를 구경하다가 panda css에 대해서 알게 되었는데, 언젠가는 한 번 분석해 보자고 해놓고 이제서야 하게 되었다.

 

emotion, styled-components를 쓸 수 없는 이유

우리는 이때까지 emotion, styled-components를 활용해서 많이 개발해왔고, 현재도 그렇다.

여러 기업에서도 현재 상당히 많이 쓰는 라이브러리인 것을 보면 알 수 있을 것이다.

 

하지만, next.js 14 버전에서 서버 컴포넌트와 emotion, styled-components와 같은 런타임 css 라이브러리를 함께 쓰면 상당히 많은 에러도 발생하고 문제가 많다고 한다.

styled-components의 단점부터 자세히 알아보도록 하자.

 

styled-components의 단점을 찾아보니 생각보다 좀 많았다.

먼저, styled-components의 스타일은 js이고, 사용하지 않는, 즉 공통 컴포넌트의 css도 번들에 포함된다고 한다.

그래서, 프롬 팀에서는 폰트나 다른 부분 최적화를 하고 나니 styled-components가 차지하는 스타일 번들이 크기가 커서 난감했던 적이 있었다고 한다.

두 번째로, styled-components는 스타일이 조금이라도 변경되면, 브라우저 입장에서 매번 새로운 css를 파싱하고, 평가하고, 렌더하는데 병목이 생길 수 있다고 한다.

특히나 조건부 스타일링을 많이 쓰는 경우 그렇다고 하는데, 프롬 팀은 styled-component가 제공하지 않는 방식들로 우회해서 해결했다고 한다.

이는 css variable을 사용하거나 aria attributte를 활용했다고 한다.

// 개선 전
${ArrowIcon} {    
  ${({ isOpen }) => isOpen && 'transform:rotateZ(180deg);'}
}
	
// 개선 후
&[aria-expanded='true'] ${ArrowIcon} {    
  transform: rotateZ(180deg);
}

 

 

세 번째 이유는 약간 우리의 방향성과 관련된 이유이기도 한데, 디자인 시스템을 위한 기능을 별도로 제공하지 않아서 복잡한 컴포넌트 타입을 만들기 쉽지 않았고, 조건부 스타일을 통해서 해결해야 해서 확장성에도 좋지 않았다고 한다.

export type ButtonRole = 'button' | 'link' | 'checkbox' | 'radio' | 'option' | 'tab';
export type ButtonThemeT = 'key' | 'mono' | 'error';
export type ButtonVariantT = 'primary' | 'secondary' | 'tertiary' | 'outline' | 'quiet';
export type ButtonSizeT = 'h28' | 'h32' | 'h40' | 'h48' | 'h56';

export interface ButtonProps {
  role?: ButtonRole;
  theme?: ButtonThemeT;
  variant?: ButtonVariantT;
  size?: ButtonSizeT;
  disabled?: boolean;
  // ... 
}

 

마지막으로, next.js 14 버전, 서버 컴포넌트에서 사용하면 상당히 문제가 많다고 한다.

next.js 14 버전에서는 app router를 사용하고, 내부 컴포넌트들은 기본적으로 서버 컴포넌트로 인식한다.

리액트 서버 컴포넌트는 경량화를 위해 상태를 제거했고, context를 사용할 수가 없다고 한다.

emotion은 React Context를 사용해서 스타일을 주입하기 때문에 사용이 불가능하다고 한다.

 

또한, 런타임 css-in-js는 클라이언트가 런타임일 때 스타일 시트를 생성하고, <style /> 요소로 DOM에 주입하는데, 이는 프로젝트 실행 중 스타일링이 DOM 요소에 주입된다는 것이다.

하지만, 서버 사이드 렌더링의 경우 서버에서 HTML 문서가 생성돼서 클라이언트로 전달되기 때문에 그 시간 동안 스타일링이 적용되지 않은 문서가 잠깐 보이다가 스타일링이 적용된 문서가 보여서 깜빡임 또한 존재한다고 한다.

 

그래서 우리는 현재로써는 많이들 사용하는 emotion, styled-components 대신 다른 라이브러리를 찾아보기로 했다.

 

panda css

panda css는 생각보다 아는 사람이 많지는 않은 라이브러리일 거 같다.

하지만, 내가 생각하기에 추후 emotion, styled-component만큼의 유행을 가져올 거 같은 라이브러리인 거 같다.

그 이유를 지금부터 알아보도록 하자.

 

일단, panda css는 Chakra UI가 SSR, next 서버 컴포넌트 관련해서 emotion을 사용하면서 겪어온 이슈들을 해결하기 위해서 만든 css 라이브러리이다.

 

주요 특징으로는 런타임 오버헤드가 없는 css-in-js 방식을 사용한다.

빌드 타임에 정적으로 코드를 생성하는데, AST 파싱과 스캐닝을 통해서 소스 코드에서 유틸리티 클래스와 패턴 추출 후 이를 정적으로 css 파일로 생성한다고 보면 된다.

이때 js는 className을 생성해 주는 역할만 하게 된다.

이런 특징들로, 런타임에 스타일을 계산하거나 주입할 필요가 없고, 번들 크기도 감소시킬 수 있게 된다.

동적으로 생성되는 스타일들도 빌드 타임에 미리 클래스로 생성된다.

 

당연히 next의 서버 컴포넌트를 공식적으로 지원한다.

그리고 무엇보다도 내가 panda를 사용하고 싶었던 이유는 디자이너와 공유하는 디자인 토큰을 쉽게 정의할 수 있다는 특징 때문이기도 했다.

아래 코드를 보면 알겠지만 styled-components를 사용할 때보다 코드가 상당히 깔끔하다.

// styled-components 사용
export const SectionTitle = styled.h3`
    ${Typography.Display2_600}
    ${BreakPoint.Tablet} {
        ${Typography.W_Display3_600}
    }
    color: ${ColorVar.text.strong};
    margin-bottom: "1.5rem",

`;

// panda css 사용
export const SectionTitle = styled.h3({
    base: {
        textStyle: { base: "Display2_600", tablet: "W_Display3_600" },
        color: "text.strong",
        mb: "1.5rem",
    }
});

 

두 번째로 내가 이 라이브러리를 좋아하는 이유는 조건부 스타일링이 쉽다는 특징 때문이었다.

디자인 시스템을 만들게 되면 컴포넌트별로 여러 props들을 사용해서 조건을 적용해 줄 일이 많을 것인데, 이런 특징들 때문에 수월하게 스타일링을 할 수 있지 않을까라는 생각을 했었다.

export const buttonRecipe = defineRecipe({
  className: 'button',
  description: 'The styles for the Button component',
  base: {
    display: 'flex'
  },
  variants: {
    visual: {
      funky: { bg: 'red.200', color: 'white' },
      edgy: { border: '1px solid {colors.red.500}' }
    },
    size: {
      sm: { padding: '4', fontSize: '12px' },
      lg: { padding: '8', fontSize: '40px' }
    },
    shape: {
      square: { borderRadius: '0' },
      circle: { borderRadius: 'full' }
    }
  },
  defaultVariants: {
    visual: 'funky',
    size: 'sm',
    shape: 'circle'
  }
})

 

panda는 atomic css도 지원한다.

atomic css는 하나의 클래스는 한 가지 역할만 수행하게 해주는 스타일링 접근 방식이다.

사용하는 클래스들을 미리 지정해놓아서 프로젝트가 거대해져도 css 파일은 일정 파일 크기 이상으로 커지지 않는다고 한다.

아래 코드를 참고하면 각 클래스 당 한 가지 역할만 수행하게 해 준다는 말을 이해할 수 있을 것 같다.

const styles = css({
  backgroundColor: 'gainsboro',
  borderRadius: '9999px',
  fontSize: '13px',
  padding: '10px 15px'
})
@layer utilities {
  .bg_gainsboro {
    background-color: gainsboro;
  }
 
  .rounded_9999px {
    border-radius: 9999px;
  }
 
  .fs_13px {
    font-size: 13px;
  }
 
  .p_10px_15px {
    padding: 10px 15px;
  }
}

 

하지만, 이렇게 장점이 많은 panda에도 단점은 존재했다.

 

바로, static nature로 인한 tracking 이슈가 있었다.

이에 대해서 설명해보자면, 런타임이 아니라 빌드 타임에 정적으로 코드를 생성하기 때문에 토큰이나 레시피 수정 시 명령어를 통해 다시 생성해주어야 한다.

하지만, 이는 코드가 수정될 때마다 재생성하게 하는 방법도 있어 큰 문제는 아니었다.

또, 테일윈드처럼 variants, className만 추적해서 사용하지 않는 스타일은 번들과 스타일 시트에 포함시키지 않아 간혹 스타일이 적용되지 않는 경우가 있다고 했다.

이는 공식 문서에 해결법이 나와있었는데, recipe config에 추가해 주면 해결된다고 했다.

그리고, 이게 아마 좀 큰 문제이지 않나 싶은데 구형 브라우저 지원을 위해서는 postcss plugin도 추가하고, 여러 설정이 필요한 것 같았다.

해결 방법은 있겠지만, 조금 번거로울 것 같긴 하다.

 

panda css 작동 방식

panda css는 빌드 타임에 정적 분석을 통해 css를 만들지만 런타임에 css-in-js 문법들을 클래스명으로 변환해 주는 것이 필요하다.

 

가장 먼저, config 파일을 찾아서 분석하고, panda context를 생성하는데, 분석한 config를 통해 코드 생성기를 준비하고 사용자들이 작성한 파일을 AST(추상 구문 트리)로 분석한다.

다음으로는 아티펙트 생성 단계이다.

경량화된 js 런타임과 타입들을 output 디렉터리에 작성하고, 모든 토큰, 패턴, 레시피, 유틸리티 등이 포함된 구성을 사용해서 styled-system 폴더를 생성한다.

그 후, app code에서 사용하는 스타일 추출 단계인데, 각 사용자 파일에서 파서를 실행해서 스타일을 식별 및 추출하고 css 계산 후 styles.css에 작성하는 과정이 이어진다.

 

다른 네 라이브러리 중 panda css를 선택한 이유

나는 앞에서 언급했던 네 라이브러리, stylex, panda css, stitches, tailwind css 중 panda css를 쓰자고 강력 추천했다.

먼저, stylex를 선택하지 않았던 이유부터 설명하자면, 내가 느끼기에 stylex가 가지고 있는 장점들은 주로 제로 런타임 관련 속성 및 타입 안정성 관련 내용들이었다.

그런데 이는 stitches, panda css에 존재하는 것 같은 점들이 대부분이었다고 느꼈고, 디자인 시스템 프로젝트에서는 두 라이브러리에서 추가적으로 가지고 있는 장점들을 더 활용할 수 있을 것 같다는 생각에 후보에서 제외됐다.

두 번째, tailwind css를 선택하지 않은 이유는, 가독성 이슈도 있었고, 타입 안정성이 크게 보장되지 않는다고 생각했다.

어쨌든 타입 안정성이 보장되는 디자인 시스템을 만들고 싶었기 때문에 이 또한 후보에서 제외되었던 것 같다.

마지막으로, stitches 내용 발표를 한 친구가 진행해줬는데, stitches와 panda css가 상당히 유사한 부분이 많다고 느꼈다.

그럼에도 panda css가 선정된 이유는, 계속해서 버전 업데이트가 진행되고 PR, issue가 활발히 open, close 되는 것을 확인했기 때문이었다.

stitches는 마지막 업데이트가 2년 전이었고, 그 이후로 활발하게 업데이트 및 코드 개선이 이루어지지 않는 것 같다고 느꼈다.

 

마지막으로, tailwind를 강력히 주장한 분의 의견도 말해보자면, 사용자 수도 어느정도 중요하지 않냐고 의견을 제시했었다.

나도 그 부분은 정말 동의하는 부분이지만, 현재까지는 emotion, styled-components가 주류였고, 이제는 이를 사용하지 못하는 시기가 와서 새로 사용할 라이브러리를 선택해야 하는 과도기에 놓여있다고 생각한다.

그런 관점에서 panda css는 추후 이 둘을 뛰어넘을 정도의 위력을 가진 라이브러리라고 생각하고, 지금은 대세는 아니지만, 추후 사용자 수도 막대하게 증가시킬만한 장점들을 가졌다고 생각한다.

그래서 결국 이 부분들이 받아들여져 panda css를 사용하기로 했다.

 

참고 자료

https://github.com/depromeet/10mm-client-web

https://velog.io/@jay/Panda-CSS

https://tech.wonderwall.kr/articles/pandacss/

https://medium.com/mildang/about-panda-css-21ff38a90070

profile

ghdtjgus

@gugu76

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그