panda-css.com/docs/guides/component-library
사실 이런 소규모 프로젝트에서 workspace를 사용하고, 패키지매니저를 변경하는 것 자체가 오버엔지니어링이라고 볼 수도 있다
그럼에도 도입한 이유는 나중을 생각해서 이다.
최종적으로 이 디자인시스템을 배포해서 사용하는 방식으로 가고 싶은데, 패키지 전체를 다운받아 사용하는 것보다 @/Button , @/Input 이런식으로 필요한 패키지만 받아서 사용하도록 하고 싶다는 생각이 들었다.
또한 각 UI를 버전별로 관리하기에도 workspace를 이용해서 나누어 놓으면 좋을 것 같다는 생각이 들어 조금 과하지만 선택했다
npm, yarn 대신 pnpm을 선택한 이유
일단 저번 프로젝트에서 npm의 유령 의존성 문제로 인해 고통을 겪은 적이 있다
그래서 npm보다는 다른 쪽으로 관심이 갔는데
workspace, monorepo를 검색하면 yarn berry +(turborepo) 관련한 글이 많이 나온다
특히 이 글을 되게 흥미롭게 봤었는데
토스 기술블로그
이 글을 보고 당장 도입해봐야지! 하곤 이미 npm으로 사용하고 있던 프로젝트를 yarn berry로 바꿨다가 포기한 적이 있다..
실패의 원인을 찾아보면
보통 검색 상단에 위치하는 글들은 처음 프로젝트를 세팅할 때를 예시로 많이 든다. 이미 수많은 패키지를 설치하고, 많은 소스코드가 쌓여있는 프로젝트를 마이그레이션 한 예시는 그리 많지 않다
pnp 관련해서 아직 호환이 되지 않거나, 호환 계획이 아예 없는 라이브러리들도 존재한다
물론 정말 좋은 기능이고, 앞으로 더 많이 사용이 될 거라고 생각한다.
하지만 node_modules로 관리 안한다고? 그럼 무조건 좋은거 아니야? 하고 설치할만큼의 단계는 아니라는 생각이 든다
그래서 자동으로 pnpm으로 결정했다
pnpm은 node_modules를 직접 설치하는 대신, 전역 저장소(Virtual Store)에서 패키지를 공유하는 구조를 사용한다. pnpm이 패키지를 설치할 때, package.json에 명시된 패키지를 읽은 후 node_modules에 Symbolic Link(symlinks)를 생성하여 전역 저장소의 해당 패키지를 참조한다

app
ui
generated
token
총 4개의 workspace가 존재한다
app 의 존재이유가 없지만현재 figma tokens 를 통해서 토큰을 관리하고 있고, 토큰 데이터가 담긴 tokens.json 파일이 push되면, token-transformer 라는 라이브러리를 이용하여 토큰을 각 theme에 맞게 파일별로 분리해주는 역할을 하는 곳
figma에서 json파일을 packages/token 폴더 안에 push해주는 걸 원했으나, 그런 기능까지는 없는 것 같다. Folder path를 등록하는 곳이 있길래 해봤는데 해당 파일이 존재하는데도 불구하고 새로운 폴더를 만들어 버린다
그래서 root 경로로 tokens.json파일이 push가 되면 github action을 통해 로직이 담긴 js파일을 실행시키면서, 결과물을 packages/token 안에 생성되게끔 자동화해놓았다
토큰 데이터를 관리하는 역할을 피그마에 위임해버리니까 훨씬 편하다
제일 먼저 하는 일은, 토큰 데이터를 pandacss에서 요구하는 타입에 맞게 변환한 뒤, panda.config 파일에 적용시킨 후 명령어를 실행해야만 css파일이 생성되고, 사용할 수 있다
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에서 수정사항이 반영 안되는 건 빌드 문제는 아닌 것 같아 찾아보니 cva 와 recipe 간에 약간의 차이가 존재한다고 한다
예시에서도 recipe를 사용하고 있어서, 안전하게 recipe로 변경했더니 수정사항이 잘 반영된다
빌드는 vite를 사용했다.
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파일에 해당 스타일이 정의되어 있다면 적용이 될 것이다