-
Notifications
You must be signed in to change notification settings - Fork 1
[Init] Storybook 초기 세팅 #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
🚀 빌드 결과✅ 린트 검사 완료 |
odukong
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
세팅하면서 처음이라 생각할 게 많으셨을 것 같은데 너무너무 수고하셨습니다 💞
특히 Introduction.mdx를 통해 스토리북 도입 배경과 함께 추가적으로 스토리북 파일 컨벤션 문서화해주신 부분이 너무 좋습니다(^///^) 제안해주신 대로 variant 등의 상세 컨벤션은 개발하며 맞춰가면 충분할 것 같아요!
추가적으로 preview.ts파일에 Vanilla Extract 테마 클래스(themeVars)적용이나 Query Provider설정을 위해 decorators를 활용해 스토리를 감싸주는 작업이 필요할 것 같아요. 현재는 디자인 시스템 구축과 tanstack-query설정이 아직 진행되지 않기도 해서 다음 작업 시 이 부분도 고려해 주시면 좋을 것 같아요! [참조링크1] [참조링크2]
만약 공통 컴포넌트만 스토리북으로 작업하게 된다면 queryProvider로 감싸는게 의미가 있을까..? 생각이 들기도 하지만,..? 추후 스토리북 컴포넌트에서 API 호출 작업을 진행할 때 필요할 수도 있으니 참고해주세요!!
| viteFinal: async (storybookConfig) => | ||
| mergeConfig(storybookConfig, { | ||
| resolve: { | ||
| alias: { | ||
| '@': resolve(__dirname, '../src'), | ||
| '@app': resolve(__dirname, '../src/app'), | ||
| '@pages': resolve(__dirname, '../src/pages'), | ||
| '@widgets': resolve(__dirname, '../src/widgets'), | ||
| '@features': resolve(__dirname, '../src/features'), | ||
| '@shared': resolve(__dirname, '../src/shared'), | ||
| '@images': resolve(__dirname, '../src/shared/assets/images'), | ||
| '@icons': resolve(__dirname, '../src/shared/assets/icons'), | ||
| '@api': resolve(__dirname, '../src/shared/api'), | ||
| '@config': resolve(__dirname, '../src/shared/config'), | ||
| '@model': resolve(__dirname, '../src/shared/model'), | ||
| '@styles': resolve(__dirname, '../src/shared/styles'), | ||
| '@types': resolve(__dirname, '../src/shared/types'), | ||
| '@ui': resolve(__dirname, '../src/shared/ui'), | ||
| '@lib': resolve(__dirname, '../src/shared/lib'), | ||
| }, | ||
| }, | ||
| }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
스토리북 파일 내부에서도 path alias형태로 가져올 수 있게 정의해준 점 너무 좋은 것 같아요!
vite.config.ts, tsconfig.app.json에서 새로운 path가 추가될 때마다 스토리북 내부에서도 이를 같이 동기화시킬 필요가 있을 것 같아서 vite-tsconfig-paths 플러그인으로 싱크 맞춰주는 방법도 한 번 제안드려봐요 🎵
| viteFinal: async (storybookConfig) => | |
| mergeConfig(storybookConfig, { | |
| resolve: { | |
| alias: { | |
| '@': resolve(__dirname, '../src'), | |
| '@app': resolve(__dirname, '../src/app'), | |
| '@pages': resolve(__dirname, '../src/pages'), | |
| '@widgets': resolve(__dirname, '../src/widgets'), | |
| '@features': resolve(__dirname, '../src/features'), | |
| '@shared': resolve(__dirname, '../src/shared'), | |
| '@images': resolve(__dirname, '../src/shared/assets/images'), | |
| '@icons': resolve(__dirname, '../src/shared/assets/icons'), | |
| '@api': resolve(__dirname, '../src/shared/api'), | |
| '@config': resolve(__dirname, '../src/shared/config'), | |
| '@model': resolve(__dirname, '../src/shared/model'), | |
| '@styles': resolve(__dirname, '../src/shared/styles'), | |
| '@types': resolve(__dirname, '../src/shared/types'), | |
| '@ui': resolve(__dirname, '../src/shared/ui'), | |
| '@lib': resolve(__dirname, '../src/shared/lib'), | |
| }, | |
| }, | |
| }), | |
| import tsconfigPaths from 'vite-tsconfig-paths'; // 별도 설치 | |
| export default { | |
| viteFinal: async (config) => { | |
| /* 생략 */ | |
| return mergeConfig(config, { | |
| plugins: [tsconfigPaths()], | |
| }); | |
| }, | |
| }; |
| reactDocgenTypescriptOptions: { | ||
| shouldExtractLiteralValuesFromEnum: true, | ||
| propFilter: (prop) => !/node_modules/.test(prop.parent?.fileName || ''), | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
옵셔널 타입의 경우(ex. disabled?: boolean), storybook상에 생성될 문서에 boolean(true, false) 뿐만 아니라 undefined까지 함께 보여진다고 해요.
현재 Button예제는 react-docgen-typescript이 기본적으로 undefined를 제거하는 방향으로 동작하고 있지만, 더 복잡한 타입의 경우에는 undefined가 그대로 노출되는 경우도 있다고 해서, undefined는 제거하여 docs를 만들어주는 옵션인 shouldRemoveUndefinedFromOptional : true도 함께 정의해주면 좀 더 보기 좋은 storybook docs가 만들어질 것 같아요🥰💞
| controls: { | ||
| matchers: { | ||
| color: /(background|color)$/i, | ||
| date: /Date$/i, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
스토리북의 대부분 컴포넌트에서 docs 파일이 생성될 거라면, preview.ts파일에 tags: ["autodocs"] 옵션을 미리 추가해주는 것도 좋을 것 같아요🙌🏻
| controls: { | |
| matchers: { | |
| color: /(background|color)$/i, | |
| date: /Date$/i, | |
| }, | |
| }, | |
| controls: { | |
| matchers: { | |
| color: /(background|color)$/i, | |
| date: /Date$/i, | |
| }, | |
| }, | |
| tags: ["autodocs"], |
| ### 📁 Story 위치 | ||
|
|
||
| Story 파일은 **컴포넌트와 동일한 디렉토리**에 작성합니다. | ||
|
|
||
| ```txt | ||
| src/shared/ui/button/ | ||
| ├── Button.tsx | ||
| └── Button.stories.tsx | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
물론 스토리북 파일의 위치에 대한 내용은 추후에 논의를 통해 변경될 수 있는 사항이겠지만,
컴포넌트 파일과 관련 스타일 파일을 최대한 가까운 위치에 두는 것이 응집도를 높이는데 좋은 방식이라는 의견이 많기 때문에 현재 작성해주신 컨벤션대로 스토리북 파일을 위치시키면 좋을 것 같아요💟
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
동의합니다~
hummingbbird
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Storybook 세팅 고생많았습니다! storybook을 많이 사용해본 팀원이 없어서 부담이 컸을텐데 너무 잘해주셨어요 최고다 유진이 .. 👊 storybook 도입 목적, 도입 분기점, 작성 규칙 등을 근거있게 PR에 잘 정리해주셔서 빠르게 이해하고 코리 남길 수 있었던 거 같아요 👍
유진님 말씀대로 바익 초기세팅하면서 토큰 기반 스타일 구조로 작성하는 틀을 함께 잡아보면 좋을 거 같아요! (1주차에서 제가 바익 세팅해보면서 작성해놓은 아티클이 있는데 거기에 관련 코드 붙여두겠습니다😀)
shared 레이어 우선 문서화 기준 적합하다고 생각하고, 카테고리 구분 역시 적절하다고 생각합니다~ 빠르게 스타일 초기화 세팅 후에 그거에 맞게 좀 더 보완하면 좋을 거 같아요~
코드 상 문제는 없는 거 같아 어푸하겠습니다 수빈이 리뷰만 한 번 확인해주세요!
| import type { Meta, StoryObj } from '@storybook/react-vite'; | ||
| import { Button } from '@shared/ui/button/Button'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import type을 가장 하단에 작성하기로 한 컨벤션 지켜서 수정해주세요! (후다닥 노션에 추가해두겠습니다 💨)
| ### 📁 Story 위치 | ||
|
|
||
| Story 파일은 **컴포넌트와 동일한 디렉토리**에 작성합니다. | ||
|
|
||
| ```txt | ||
| src/shared/ui/button/ | ||
| ├── Button.tsx | ||
| └── Button.stories.tsx | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
동의합니다~
.storybook/preview.ts
Outdated
| parameters: { | ||
| layout: 'padded', | ||
|
|
||
| actions: { argTypesRegex: '^on[A-Z].*' }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
추가적으로 합세 스토리북 세팅할 때 발견했던 부분인데,
{ argTypesRegex: '^on[A-Z].*' }옵션은 on으로 시작하고 그 다음에는 대문자로 시작하는 이벤트 핸들러 props(ex. onClick, onChange)에 대해 자동으로 actions으로 연결해주는 방식이에요.
하지만 v8버전 이상부터는 { argTypesRegex: '^on[A-Z].*' } 옵션을 권장하고 있지 않다고 합니다! (실제로 합세 때 해당 옵션을 사용했을 때 경고 콘솔이 뜨더라구요)
https://storybook.js.org/docs/essentials/actions#automatically-matching-args
대신 @storybook/test에서 제공하는 fn()을 사용하여 스토리북 단에서 명시적으로 이벤트 핸들러를 정의하는 방식을 권장하고 있다고 해요!
args: {
onClick: fn(),
}현재는 별도 경고는 안 떠서 괜찮아 보이긴 합니다!
다만 공식 문서에서 권장하는 fn()은 주로 인터랙션 테스트(@storybook/test) 를 위한 기능이라, 저희가 당장 테스트 자동화를 도입할 게 아니라면 굳이 라이브러리를 추가로 설치해서 무겁게 갈 필요는 없을 것 같습니다.
작성해주신 Button 스토리처럼 argTypes에 action: 'clicked'를 명시하면 로그 확인이 충분히 가능하니, 우선은 추가 설치 없이 preview.ts의 argTypesRegex 설정만 제거하는 방향으로 가볍게 유지해도 좋을 것 같아요!👍🏻
// 나중에 입력값 등을 상세히 보고 싶을 땐 action 함수를 사용하는 방식도 있다고 하네요!
import { action } from '@storybook/addon-actions';
const meta = {
// ...
args: {
onClick: action('clicked'),
onChange: (e) => action('onChange')(e.target.value),
},
};저도 스토리북을 제대로 활용해본 경험은 아직 없어서😭 같이 배워가면서 한 번 열심히 스토리북 채워봅시다!!
|
리뷰에서 남겨주신 의견들 모두 확인하고 반영했습니다 수빈님 말씀처럼 추후 작업 시에 앞으로 차근차근 컨벤션 확정해가며 작업해보도록 해요!! 다들 감사합니다 😽 |
qowjdals23
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Storybook 제대로 써본 적이 거의 없어서 잘 몰랐는데, 자세한 PR과 꼼꼼한 리뷰 보면서 많이 배워 갑니다 😍
Button 예시로 variant/size/disabled 상태를 Story로 확인 가능하게 해둔 것도 , Docs/A11y 신경 쓴 부분도 너무 좋네요 !!
리뷰 반영도 깔끔하게 잘하신 것 같고, 유진님 덕분에 이후 공용 컴포넌트들 Storybook으로 차근차근 쌓아가기 쉬울 것 같아요 !!! 수고 많으셨습니다!! ❤️🔥
- pnpm-lock.yaml 중복키 제거를 위한 삭제, 재설치
✏️ Summary
close [Init] Storybook 초기 세팅 #11
Comfit 서비스에 Storybook 초기 세팅을 진행했습니다.
shared레이어 공용 UI부터 문서화/검증할 수 있도록 FSD 구조 기반으로 Story 작성 규칙을 정리했습니다.첫 예시 컴포넌트로
Button을 등록하여 Docs(Props 문서화), A11y(접근성 검사) 를 바로 확인할 수 있도록 테스트해봤습니다.기본 템플릿(온보딩/예제 스토리/테스트 통합 등) 중 아직 클라 팀 내부에서 합의되지 않거나 프로젝트에 적용이 이르다고 판단되는 항목은 제거하였습니다.
📑 Tasks
✔️ Comfit이 Storybook을 도입한 이유
Comfit 서비스는 공용 UI(Shared) 컴포넌트가 여러 페이지/기능에서 재사용되며, 작은 UI 변경이 전체 화면에 영향을 줄 수 있다고 판단했습니다. 따라서 Storybook을 도입하면 다음을 해결할 수 있습니다.
variant,size,disabled같은 주요 상태를 Story로 관리해 UI 변경에 따른 사이드 이펙트를 빠르게 확인할 수 있습니다.✔️ 실행방법
로컬 실행
기본 포트
빌드
✔️ 폴더 구조 및 문서화 범위 (FSD 기준)
지금까지 합의된 Comfit의 Storybook 문서화 대상 우선순위
✔️ 이번 PR에서 추가/변경된 내용
Storybook config 추가
.storybook/main.ts.storybook/preview.tsStorybook에서 경로 alias 및 Docs/A11y 동작을 위한 최소 설정을 구성했습니다.
Button 컴포넌트 + 스토리 추가 (첫 문서화 대상)
src/shared/ui/button/Button.tsxsrc/shared/ui/button/Button.stories.tsx왜 Button을 첫 대상으로 선택했나요?
✔️ Story 작성 규칙
Story 위치
Story 파일은 컴포넌트와 동일한 디렉토리에 작성합니다.
Story 네이밍 컨벤션
Shared / Feature / Widget기준으로 카테고리를 구분합니다.✔️ Button.tsx는 왜 이렇게 구성했을까요?
HTML button 기본 속성 확장
onClick,disabled등)을 그대로 사용 가능합니다.스타일은 “상태 차이가 명확하게 보이도록” 구성
primary/secondary,sm/md/lg,disabled가 시각적으로 잘 구분되도록 구성했습니다.✔️ Button.stories.tsx는 왜 이렇게 구성했을까요?
argTypes로 Controls 제공
onClick: { action: 'clicked' }를 명시해서 Actions 패널에서 클릭 이벤트가 로깅되는 것을 한눈에 확인할 수 있습니다. (preview의 regex로 자동 수집도 가능하지만, 컴포넌트 사용자가 “클릭 가능한 컴포넌트”임을 더 명확히 인지할 수 있도록 명시했습니다.)✔️ addons 구성 (왜 docs/a11y만 포함했을까요?)
이번 PR은 “초기 세팅”이 목적이라 판단하여 실제로 바로 필요한 addons만 선택해서 진행했습니다.
포함한 addon
@storybook/addon-docs@storybook/addon-a11y제외한 addon (삭제 / 비활성화 이유)
@chromatic-com/storybook@storybook/addon-vitest(및 Vitest 관련 설정)✔️ 추후 수정 계획
이번 PR은 Storybook 초기 도입에 집중하고 있어서, 아래 항목들은 추후 단계적으로 확장해보면 좋을 것 같습니다!
vanilla-extract 도입 이후
globalStyle세팅하도록 하겠습니다.문서화 범위 / 규칙 정리
Chromatic 등 도입
👀 To Reviewer
shared레이어 우선 문서화 기준과title: 'Shared/...카테고리 구분이 적절한지 의견 부탁드립니다!📸 Screenshot
🔔 ETC
frozen-lockfile옵션이 적용되어 있어,package.json이 변경되었는데pnpm-lock.yaml이 함께 업데이트되지 않으면 빌드가 실패할 수 있습니다.pnpm-lock.yaml을 함께 커밋해야 합니다.