pandacss로 디자인시스템 만들기 - workspace 설정

jh·2024년 8월 9일

디자인 시스템

목록 보기
5/14

panda-css.com/docs/guides/component-library

pnpm workspace 사용하기

사실 이런 소규모 프로젝트에서 workspace를 사용하고, 패키지매니저를 변경하는 것 자체가 오버엔지니어링이라고 볼 수도 있다
그럼에도 도입한 이유는 나중을 생각해서 이다.

최종적으로 이 디자인시스템을 배포해서 사용하는 방식으로 가고 싶은데, 패키지 전체를 다운받아 사용하는 것보다 @/Button , @/Input 이런식으로 필요한 패키지만 받아서 사용하도록 하고 싶다는 생각이 들었다.

또한 각 UI를 버전별로 관리하기에도 workspace를 이용해서 나누어 놓으면 좋을 것 같다는 생각이 들어 조금 과하지만 선택했다

npm, yarn 대신 pnpm을 선택한 이유

  • 일단 세가지 PM 모두 workspace 기능이 존재한다

일단 저번 프로젝트에서 npm의 유령 의존성 문제로 인해 고통을 겪은 적이 있다
그래서 npm보다는 다른 쪽으로 관심이 갔는데

workspace, monorepo를 검색하면 yarn berry +(turborepo) 관련한 글이 많이 나온다

  • npm trends에 검색해보면 pnpm이 거의 2배가량 다운수가 더 많다

특히 이 글을 되게 흥미롭게 봤었는데
토스 기술블로그
이 글을 보고 당장 도입해봐야지! 하곤 이미 npm으로 사용하고 있던 프로젝트를 yarn berry로 바꿨다가 포기한 적이 있다..

실패의 원인을 찾아보면

  1. 보통 검색 상단에 위치하는 글들은 처음 프로젝트를 세팅할 때를 예시로 많이 든다. 이미 수많은 패키지를 설치하고, 많은 소스코드가 쌓여있는 프로젝트를 마이그레이션 한 예시는 그리 많지 않다

  2. pnp 관련해서 아직 호환이 되지 않거나, 호환 계획이 아예 없는 라이브러리들도 존재한다

  • 라이브러리 github issue에 가면 관련해서 문제를 호소하는 사람들을 볼 수 있다

물론 정말 좋은 기능이고, 앞으로 더 많이 사용이 될 거라고 생각한다.
하지만 node_modules로 관리 안한다고? 그럼 무조건 좋은거 아니야? 하고 설치할만큼의 단계는 아니라는 생각이 든다

그래서 자동으로 pnpm으로 결정했다

pnpm은 node_modules를 직접 설치하는 대신, 전역 저장소(Virtual Store)에서 패키지를 공유하는 구조를 사용한다. pnpm이 패키지를 설치할 때, package.json에 명시된 패키지를 읽은 후 node_modules에 Symbolic Link(symlinks)를 생성하여 전역 저장소의 해당 패키지를 참조한다

pnpm vs yarn vs npm

패키지 구조

app

ui
generated
token

총 4개의 workspace가 존재한다

  • 사실 디자인 시스템만을 관리하는 프로젝트이기에 app 의 존재이유가 없지만
    만든 UI를 실제 사용하는 곳으로 가정하고 관련 테스트를 진행하려는 목적으로 만들었다.
    storybook을 메인으로 test를 하고 있긴 하지만, css-in-js 방식이 아니다 보니 실제 환경에서도 잘 돌아가는지 확인이 필요했다

token

현재 figma tokens 를 통해서 토큰을 관리하고 있고, 토큰 데이터가 담긴 tokens.json 파일이 push되면, token-transformer 라는 라이브러리를 이용하여 토큰을 각 theme에 맞게 파일별로 분리해주는 역할을 하는 곳

  • figma에서 json파일을 packages/token 폴더 안에 push해주는 걸 원했으나, 그런 기능까지는 없는 것 같다. Folder path를 등록하는 곳이 있길래 해봤는데 해당 파일이 존재하는데도 불구하고 새로운 폴더를 만들어 버린다

  • 그래서 root 경로로 tokens.json파일이 push가 되면 github action을 통해 로직이 담긴 js파일을 실행시키면서, 결과물을 packages/token 안에 생성되게끔 자동화해놓았다

토큰 데이터를 관리하는 역할을 피그마에 위임해버리니까 훨씬 편하다

ui

  • 생성된 토큰을 데이터를 가공하여 panda.config 파일에 적용 -> css파일 생성
  • 컴포넌트 구현
  • 빌드

제일 먼저 하는 일은, 토큰 데이터를 pandacss에서 요구하는 타입에 맞게 변환한 뒤, panda.config 파일에 적용시킨 후 명령어를 실행해야만 css파일이 생성되고, 사용할 수 있다

  • 이 방식을 거쳐야 type-safe한 개발을 할 수 있다
import { defineConfig } from "@pandacss/dev"
import { colors, radii, fontSizes, textStyles } from "./index"
import { preset } from "./src/components/preset"
export default defineConfig({
 		...
  theme: {
    semanticTokens: {
      colors: {
        ...colors,
      },
    },
    tokens: {
      radii,
      fontSizes,
    },
    textStyles,
  },
 ...
})

이런 형태로 적용을 하고 명령어를 실행시키면 코드를 파싱하고 , postcss를 통해 atomic css로 변환을 시킨다

After static analysis, Panda uses a set of PostCSS plugins to convert the parsed data to atomic css at build time. This makes Panda compatible with any framework that supports PostCSS.

변환된 토큰이 담긴 css파일 예시

:where(:root, :host):not(#\#):not(#\#) {
  --colors-bg_main: #fefefe;
  --colors-bg_elevated: #fefefe;
  --colors-border_basic: #646f6c;
  --colors-border_basic2: #f7f8f9;
  --colors-text_primary: #374553;
}

컴포넌트의 스타일은 panda의 recipe 기능을 사용하여 variant에 따른 스타일을 선언한다

export const buttonRecipe = defineRecipe({
  className: "button",
  base: {
   	//기본 스타일
    _hover: {
      boxShadow: "rgba(0, 0, 0, 0.25) 0px 3px 3px",
    },
    _disabled: {
      cursor: "not-allowed",
    },
  },
  variants: {
    size: {
      small: {
        height: "44",
        fontSize: "sm",
        paddingLeft: "8",
        paddingRight: "8",
      },
      medium: {
        height: "44",
        fontSize: "lg",
        paddingLeft: "10",
        paddingRight: "10",
      },
      large: {
        height: "48",
        fontSize: "lg",
        paddingLeft: "16",
        paddingRight: "16",
      },
    },
    br: {
      normal: {
        borderRadius: "sm",
      },
      rounded: {
        borderRadius: "rounded",
      },
    },
    variant: {
      primary: {
        backgroundColor: "blue_500",
        border: "none",
        color: "white",
        _disabled: {
          opacity: 0.5,
          backgroundColor: "grey_400",
        },
      },
      text: {
        border: "2px solid",
        borderColor: "border_basic",
        backgroundColor: "white",
        color: "text_secondary",
        _disabled: {
          opacity: 0.5,
        },
      },
    },
  },
  defaultVariants: {
    size: "medium",
    variant: "primary",
    br: "normal",
  },
})

이런식으로 하나의 객체 형태로 variant에 따른 스타일을 정의할 수 있다

  • 분기처리를 하지 않아도 된다

  • pandacss에서는 recipe 말고도 이런 기능이 여러개 존재한다.
    사실 원래는 cva 라는 기능을 사용하려고 했는데, 수정 시 storybook이나 app에서 바로 반영이 안되는 이슈가 있었다.
    app 에서 바로 반영이 안되는 건 빌드 과정에서 어떤 문제가 있겠거니 했는데, 같은 워크스페이스에서 바로 사용하는 storybook에서 수정사항이 반영 안되는 건 빌드 문제는 아닌 것 같아 찾아보니 cvarecipe 간에 약간의 차이가 존재한다고 한다

공식문서

예시에서도 recipe를 사용하고 있어서, 안전하게 recipe로 변경했더니 수정사항이 잘 반영된다

  • 항상 pandacss 쓰기 전에 dev 모드 실행시키기(nodemon같은 역할)

빌드는 vite를 사용했다.

generated

panda condegen 이라는 명령어를 실행하면, styled-system이라는 폴더가 생성되고 이 안에 여러 유틸 함수들과, css파일이 위치한다

css파일을 그대로 사용해도 되지만, 변수 등 사용하기가 조금 번거롭다. type을 지원하면서 변환을 해주는 기능이 필요한데 해당 기능들이 styled-system 안에 들어있다

While Panda generates your CSS at build-time using static extraction, we still need a lightweight runtime to transform the CSS-in-JS syntax (either object or template-literal) to class names strings. This is where the styled-system folder comes in.
When running the panda or panda codegen commands, the config.outdir will be used as output path to generate the styled-system in.
This is the core of what the styled-system does:

import { css } from "jh-generated/css"
import "./index.css"
import "ui/lib.css"
const App = () => {
  return <div className={css({ border: "1px solid black" })}>Example</div> 
}

export default App

이런식으로 생성된 css 라는 유틸함수를 사용하여 스타일을 정의하면,

<div className='border_1px_solid_black' />

이런식으로 변환이 되고, css파일에 해당 스타일이 정의되어 있다면 적용이 될 것이다

  • 여기서 많이 헷갈렸는데.. css-in-js처럼 해당 스타일을 바로 적용해주는 방식이 아니다! css파일 안에 스타일이 생성되지 않으면 적용이 안된다

0개의 댓글