Intro
디프만 15기에 이름을 올린지 벌써 두 달이 지났다.
우리 팀은 치열한 아이데이션과 기획 단계를 거쳐 수영 기록 서비스를 목표로 1차 MVP를 개발하고 있다.
개발 경험이 적은 나에게는 11명의 팀원들과 합을 맞추는 것도 큰 챌린지지만,
이번 프로젝트는 무엇보다 기술적으로 챌린지가 많이 있다. (그만큼 성장도 많이 하고 있다!)
현재 기술 스택은 크게 NEXT, PandaCSS, pnpm, jotai, Storybook인데 제대로 써본 것이 단 1개도 없다! 🙃
게다가 이후 애플워치 연동과 React Native 또는 PWA로 앱화 하는 작업도 필요해서 첩첩산중이다.
메인 기술부터 러닝 커브가 있었기에 스타일링만은 제발 익숙한 스택으로 추진하고 싶었지만,
내게 익숙한 것은 Styled-components와 css module, 기껏해야 tailwind 뿐...
하지만 1번의 경우 runtime css in js로, 프레임워크와 핏하지 않고 별도의 설정을 해야 하는 게 번거로움을 알게 됐다.
NEXT에서도 지원하는 2번, 3번의 경우 2번은 DX가 별로인 것 같고, 3번은 팀원이 결사 반대를 하는 스택이었다.
대안책으로 emotion도 고려해봤지만, NEXT에서 공식적으로 지원하지도 않고 RSC에서는 여러 이슈들이 있었다.
따라서 어느 것도 고르지 못하는 진퇴양난의 상황에 봉착하고 말았다.
그러다 파트장 브라이언이 이름도 생소한 PandaCSS를 제안했는데......
우린 몰랐다, PandaCSS(aka 푸바오)가 쏘아 올린 커다란 공을...
PandaCSS
Panda CSS - Build modern websites using build time and type-safe CSS-in-JS
Build modern websites using build time and type-safe CSS-in-JS
panda-css.com
공식 문서에서 주장하는 PandaCSS의 장점을 알아보자.
먼저 runtime css-in-js와 style props는 여러 장점이 있지만 성능과 런타임에서 트레이드 오프가 있다.
언급한대로 서버 컴포넌트가 출시됨에 따라 기존의 runtime css in js 스타일링(ex. emotion, styled-components)는 여러 에러가 발생하게 됐다. 이외에도 css in js는 사용하지 않는 스타일링까지 포함해 번들 크기가 크다거나, 런타임에 스타일을 생성하는 특성 상 페이지 로드 시간 증가 및 새로고침 시 스타일링이 풀리는 현상, 동적으로 생성되는 스타일링과 정적인 NEXT의 특성이 상충되는 등 React에서도 그렇지만 NEXT와는 아쉬운 궁합을 보이고 있다.
PandaCSS는 아래와 같은 장점을 갖고 있다.
1. 정적 분석: 빌드 시 스타일을 분석해 모든 JS 프레임워크에서 사용 가능한 CSS 파일을 생성해 런타임 상승 향상을 이끈다.
2. PostCSS 호환성: PostCSS를 지원하는 모든 프레임워크와 호환된다.
3. 코드 생성: 경량화된 JS 코드를 생성해 스타일 작성에 사용한다. (브라우저에서 스타일 생성 X -> 성능 향상)
4. 타입 안정: CSS 속성과 디자인 토큰에 대한 타입 안정성을 제공한다.
5. 성능 최적화
6. DX 개선: 레시피, 패턴, 디자인 토큰 등 다양한 기능을 제공한다.
현재까지 체감한 것으로는 4번과 6번이 압도적이다.
import { css } from '../styled-system/css'
import { circle, stack } from '../styled-system/patterns'
function App() {
return (
<div
className={stack({
direction: 'row',
p: '4',
rounded: 'md',
shadow: 'lg',
bg: 'white'
})}
>
<div className={circle({ size: '5rem', overflow: 'hidden' })}>
<img src="https://via.placeholder.com/150" alt="avatar" />
</div>
<div className={css({ mt: '4', fontSize: 'xl', fontWeight: 'semibold' })}>
John Doe
</div>
<div className={css({ mt: '2', fontSize: 'sm', color: 'gray.600' })}>
john@doe.com
</div>
</div>
)
}
이런식으로 문법은 거의 styled-components와 유사한데
CSS를 객체로 표현하고, 약어가 많아서 tailwind 1방울 섞인 느낌이다.
동적 스타일링도 가능하고, 특히 TS와 같이 쓰면 타입 체크와 자동 완성이 되는 점이 최고다.
디자인 시스템에 찰떡
또 디자이너가 있는 팀이라면 상당히 유용한 스타일링인게, 디자인 토큰에 대해 오피셜 최고 수준의 지원을 제공한다.
디자인 토큰이란 색상, 타이포그래피, 폰트 크기 등 디자인 요소를 나타내는 변수로 디자인-개발 사이의 공통 언어 역할을 한다.
이 디자인 토큰을 통해서 디자이너-개발자 간의 원활한 소통은 물론, 기본적인 제품의 일관적인 스타일링을 유지하기 용이하다.
(나는 이번에 디자이너와 처음 협업을 해보아서 이런 것에 대해 무지했는데 너무 좋은 기회였다.)
디자이너 분들이 figma에 variables 기능으로 디자인 토큰을 작성해두면
개발자는 이를 코드로 관리해 전역적으로 사용하면 된다.
// tokens.js
export const tokens = {
colors: {
primary: { value: '#0066cc' },
secondary: { value: '#f0f0f0' },
},
spacing: {
sm: { value: '8px' },
md: { value: '16px' },
lg: { value: '24px' },
},
// ... 기타 토큰들
}
// 사용 예
import { css } from '../styled-system/css'
const Button = () => (
<button className={css({
bg: 'primary',
padding: 'md',
color: 'white'
})}>
Click me
</button>
)
// 우리팀 코드
import { defineTextStyles } from '@pandacss/dev';
export const textStyles = defineTextStyles({
display1: {
value: {
fontSize: '56px',
lineHeight: '1.286',
letterSpacing: '-0.0319',
},
},
display2: {
value: {
fontSize: '40px',
lineHeight: '1.3',
letterSpacing: '-0.0282',
},
},
display3: {
value: {
fontSize: '36px',
lineHeight: '1.334',
letterSpacing: '-0.027',
},
},
// 사용 예
const buttonTextStyles = {
textStyle: 'body1.normal',
fontWeight: 'bold',
};
like this...
기존에는 내가 디자인도 하고 개발도 해서 근본 없이 했었던 터라,
디자인팀이 정해준 스펙대로 따라가는 것도 뭐가 뭔지 몰라 한참 헤맸었다.
다행히 우리 팀원들이 솔선수범으로 세팅을 너무 잘 해주셔서 난... 그저 주워 먹었다.
panda codegen을 해주면 reset, base, token, recipes, 유틸리티 등을 css로 생성해준다.
storybook에서도 pandaCSS를 사용해 만든 컴포넌트를 테스트할 때 요것을 사용해 CSS를 적용한다.
(storybook 코드 작성했는데 스타일링이 계속 안 먹어서 왜 이러나 했는데... 역시 공식 문서에 잘 나와 있다.)
조합해서 먹는 스타일링 recipe
이번 글을 쓰다 보니 recipes를 사용하진 않았어서 이게 뭔지 알아봤는데 치트키 같다.
약간 storybook에서 여러 스타일링을 테스트하는 것처럼, 변수 값을 통해 멀티 변수 스타일링(?)을 만들 수 있다.
recipe는 4가지 프로퍼티로 구성된다.
컴포넌트를 위한 베이스 스타일링인 base,
컴포넌트에 각각 다른 비주얼 스타일인 variant,
컴포넌트의 다른 변수들을 조합하는 compoundVariants,
마지막으로 컴포넌트의 디폴트 변수 값인 defaultVariants가 있다.
참고로 여기서 사용되는 cva는 class variance authority가 아니라 판다에서 특별히 제작한 함수다.
import { cva } from '../styled-system/css'
const button = cva({
base: {
padding: '8px 16px',
borderRadius: '4px',
fontSize: '16px',
fontWeight: 'bold'
},
variants: {
size: {
small: {
fontSize: '14px',
padding: '4px 8px'
},
medium: {
fontSize: '16px',
padding: '8px 16px'
},
large: {
fontSize: '18px',
padding: '12px 24px'
}
},
color: {
primary: {
backgroundColor: 'blue',
color: 'white'
},
secondary: {
backgroundColor: 'gray',
color: 'black'
}
},
disabled: {
true: {
opacity: 0.5,
cursor: 'not-allowed'
}
}
},
// compound variants
compoundVariants: [
// apply when both small size and primary color are selected
{
size: 'small',
color: 'primary',
css: {
border: '2px solid blue'
}
},
// apply when both large size and secondary color are selected and the button is disabled
{
size: 'large',
color: 'secondary',
disabled: true,
css: {
backgroundColor: 'lightgray',
color: 'darkgray',
border: 'none'
}
},
// apply when both small or medium size, and secondary color variants are applied
{
size: ['small', ' medium'],
color: 'secondary',
css: {
fontWeight: 'extrabold'
}
}
]
})
// 사용 예
import { button } from './button'
const Button = () => {
// will apply size: small, color: primary, css: { border: '2px solid blue' }
return (
<button className={button({ size: 'small', color: 'primary' })}>
Click Me
</button>
)
}
위 사용 예시와 같이 cva 함수로부터 리턴 받은 값은 컴포넌트에 레시피로 적용된다.
기본적으로는 base와 variants만 있어도 돌아가는 것 같다.
경량 런타임 그거 어떻게 하는 건데
또 공식 문서를 뒤적이다 보니 3번에 대해서도 약간 이건가? 싶은 내용을 발견했다.
PandaCSS는 빌드 타임에 정적 추출을 사용해서 CSS를 생성하지만,
css in js(객체 또는 템플릿 리터럴)을 클래스 이름 문자열로 반환하려면 경량 런타임이 필요하다고 한다.
(번들러 플러그인에 의존하지 않아 컴파일 시 css in js를 클래스 이름 문자열로 변환하기 위한 코드 변환이 이뤄지지 않기 때문)
여기서 styled-system 폴더가 등장하는데 위에서 언급했던 panda codegen 명령어를 실행하면 이것들이 생성된다.
css({ color: 'blue.300' }) // => "text_blue_300"
이런 식으로 기존에 css in js로 작성했던 스타일링이 클래스 이름 문자열로 반환된다.
그래서 개발자 도구를 켜서 확인해보면 className으로 (테일윈드마냥;) 들어가 있는 것을 확인할 수 있다.
PandaCSS의 단점
방금 팀원들과 잠깐 논의해보면서 모두가 비슷하게 느낀다는 걸 알 수 있었다.
1. 레퍼런스가 적다.
아직 국내에서는 많이 사용하지 않는 스타일링 도구라 그런지 에러가 발생해도 참고할 풀이 매우 적다.
우리 팀원 모두 해당 스타일링 도구를 처음 써보기 때문에 의지할 게 서로와 구글링 또는 깃허브 이슈뿐이다... ㅠㅠ
2. 러닝커브가 크다.
1번과 유사한 맥락으로 러닝 커브가 체감 상 좀 컸다.
내가 못하는 것도 있지만 비슷비슷하다 생각했던 기존 스타일링 도구와 좀 다른 패턴이라서? 익숙지 않았다.
기능이 많아서 좋은데 이걸 100% 잘 활용하기엔 아직 멀었다는 생각이 든다.
누군가 사용해봤거나 멱살을 잡는 게 아니라면,
팀원 모두가 처음 쓰는 상황이라면 1번과 2번이 약간 크리티컬할 듯하다.
그리고 왜인진 모르겠지만 우리 팀원 모두(윈도우 유저 제외) 이상한 스타일링 캐싱; 현상으로 곤욕을 치뤘었다.
분명 아까 잘 돼서 영상까지 찍었는데 브랜치에 올리고 나니 적용이 안되는 기이한 현상들...
토큰이나 레시피 같은 걸 수정하면 styles.css를 재생성해줘야 해서 panda codegen 명령어를 쓰는 점은 인지했는데,
토큰이나 레시피를 수정한 게 아니라 그냥 기존 스타일링을 변경했는데도... 잘 안될 때가 있었다.
위와 관련해 공식 문서에서는
Sometimes Next.js caches PostCSS generated styles and when that happens you need to clear the cache. To do that, delete the .next folder and restart your development server.
가끔 Next.js가 PostCSS에서 생성된 스타일 캐시하는 경우 있는데 .next 폴더 삭제하고 개발 서버 다시 시작해~
아니면 package.json에 "dev": "rm -rf .next && next dev"로 수정하든가~
라고 한다... (이게 맞아?)
근데 이러다 갑자기 캐시 밀면 되긴 하는 게 더 의문이다.
이 치명적이지만 간헐적인 단점만 제외한다면, 러닝 커브를 감안하고도 쓸 만한 스타일링 도구라고 생각하긴 한다.
나의 숙련도를 배제하고 협업의 관점에서는 기존 스타일링 도구를 제치고 압도적 1위!
이번에 잘만 익혀둔다면 훨씬 범용성이 높을 것 같아서, 욕은 많이 했지만 앞으로 푸바오와 친해져 보려고 한다.
끝!
'🛠️ 프로젝트 > ➕ 디프만 15기' 카테고리의 다른 글
[디프만 15기] next-auth 없이 웹에서 애플 로그인 구현하기 (0) | 2024.09.10 |
---|---|
[디프만 15기] 공통 컴포넌트 리팩토링과 UI 테스트 (feat. pandaCSS, storybook) (0) | 2024.08.20 |
[디프만 15기] Next로 소셜 로그인 구현하기 (feat: cookie, route handler, middleware) (0) | 2024.08.05 |
[디프만 15기] 서류 지원 및 면접 후기 (+ 최종 합격) (0) | 2024.05.26 |