근황 : 실무에서 Storybook 개발 도구 공부해 2주 만에 컴포넌트 문서화 도입 + 라이브러리 배포한 이야기...
React + Vite + TS 환경에서의 Storybook 세팅 방법과 Storybook 컴포넌트 구현 정보가 생각보다 많지 않았어서 여기서 찾고 저기서 찾고 많이 헤맸었다. 그래서 Storybook 적용하면서 틈틈이 기록해 두었던 것을 블로그 글로 남겨보려 한다.
이번 글에서는 React + Vite + TS 내에서의 스토리북 설치 방법과, 나중에라도 보기만 하면 따라할 수 있도록 story 컴포넌트를 만드는 방법을 정리해 보고, 이후에 docs 세팅 방법, actions 사용 방법, mdx 적용 방법...잘하면 npm 라이브러리 배포까지 이야기 해보고 싶다.
// 기본
npm create vite@latest
// npm 6.x
npm create vite@latest [프로젝트 명] --template react-ts
// npm 7+, extra double-dash is needed:
npm create vite@latest [프로젝트 명] -- --template react-ts
세 방법 중 적당한 방법으로 설치
npm i @emotion/styled @emotion/react emotion-reset
npx storybook@latest init
설치 중 npm 최신 버전 설치를 추천해 주는데, 이 부분도 같이 설치해 주었다.
설치를 마치면 storybook 페이지가 자동으로 실행되고, 이후 실행 시 아래 명령어로 storybook을 열어줄 수 있다.
npm run storybook
프로젝트 디렉토리를 보게 되면 프로젝트 루트에 .storybook
이라는 폴더와 src
폴더 내 stories
폴더가 생성되어 있는 것을 볼 수 있다.
Storybook은 Webpack 기반의 프레임워크이므로 Vite 환경에서 storybook을 작업하기 위해 vite builder를 따로 설치해준다.
npm install @storybook/builder-vite --save-dev
story 컴포넌트에서도 import 시 편하게 @/~~~
형태로 가져올 수 있는 설정을 추가해 준다.
vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import * as path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: [{ find: '@', replacement: path.resolve(__dirname, 'src') }],
},
});
tsconfig.json
{
"compilerOptions": {
//...다른 설정
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
main.ts
Vite builder 설치한 내용을 .storybook/main.js
에 세팅해 준다.
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
core: {
builder: '@storybook/builder-vite', // 👈 The builder enabled here.
},
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
'@storybook/addon-onboarding',
'@storybook/addon-links',
'@storybook/addon-essentials',
'@chromatic-com/storybook',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
};
export default config;
preview.tsx
storybook 컴포넌트에서 다음을 사용한다면 preview.ts의 세팅이 필요하다
preview.ts 파일 명을
preview.tsx`로 변경 후 아래와 같이 세팅해 준다.
preview-head.html
사용하는 폰트가 있다면 preview-head.html
을 만들어 해당 폰트 link 태그를 가져와 아래와 같이 세팅한다.
<!-- Pull in static files served from your Static directory or the internet -->
<!-- Example: `main.js|ts` is configured with staticDirs: ['../public'] and your font is located in the `fonts` directory inside your `public` directory -->
// 사용 중인 폰트 link 태그 삽입
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100..900&display=swap"
rel="stylesheet"
/>
<!-- Or you can load custom head-tag JavaScript: -->
<!-- <script src="https://use.typekit.net/xxxyyy.js"></script>
<script>
try {
Typekit.load();
} catch (e) {}
</script> -->
참고 : https://storybook.js.org/docs/configure/story-rendering#adding-to-head
스토리 컴포넌트 파일 생성 시 디렉토리 구조를 story컴포넌트만 모아놓는 방법도 있는데,
실제 작업하면서 이렇게 컴포넌트와 스타일, 그리고 story 컴포넌트를 함께 넣어놓는 방식이 컴포넌트에서 Story 컴포넌트로 Props를 받아올 수도 있고 관리하기에 수월했다.
아래는 storybook 공식 문서에 나와 있는 기본 story 컴포넌트 세팅 방식이다. 먼저 각 meta 내 props에 대해 알아보고, 아래 방식 말고도 다양하게 story 컴포넌트를 세팅할 수 있는 방법을 소개하려 한다.
// 기본 import
import type { Meta, StoryObj } from '@storybook/react';
// Story로 만들 컴포넌트 불러오기
import { Button } from './Button';
const meta = {
title: "Components/Buttons/Button",
component: Button,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
args: {}
argTypes: {}
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
primary: true,
label: 'Button',
},
};
title
- 폴더 구조 만들기 (depth 1 - 카테고리, depth 2 - 폴더, depth 3 - 컴포넌트 폴더)
component
- import한 컴포넌트 삽입
parameters - layout
- 컴포넌트가 보여지는 위치
centerd
가로 세로 중앙 fullscreen
여백 없이 padded
(기본값) - 임의의 패딩 여백이 주어짐 tags: ["autodocs"]
- 자동 Docs 생성
args
: 스토리 컴포넌트에 넣어줄 props default 값 지정
미리 type Story = StoryObj<Props>
타입 지정 후 args
에 props, decorators
(또는 render
)에 컴포넌트를 렌더링하는 방법
import { Button } from './Button';
// 기존과 동일
const meta = {
title: "Components/Buttons/Button",
component: Button,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
args: {}
argTypes: {}
} satisfies Meta<typeof Button>;
type Story = StoryObj<Props>;
export const Default: Story = {
args: {
main: "Main",
mainLink: "mainLink",
},
decorators: [
(Story) => (
<div style={{ width: "300px" }}>
<h2>Menu Button</h2>
<Story />
</div>
),
],
};
export const Default: Story = {
args: {
main: "Main",
mainLink: "mainLink",
},
render: ({ main, mainLink }) => (
<div style={{ width: "300px" }}>
<h2>Menu Button</h2>
<MenuItem main={main} mainLink={mainLink} />
</div>
),
};
리액트 컴포넌트 스타일 형식으로 만들기
export const DropDown = (args: Props) => (
<div style={{ width: "300px" }}>
<h2>Menu Dropdown</h2>
<DropDown
main="Main"
sub={[
{ link: "link1", name: "text1" },
{ link: "link2", name: "text2" },
]}
/>
</div>
);
import type { Meta } from "@storybook/react";
import Component, { Props } from ".";
const meta = {
title: "Components/Component",
component: Component,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {},
args: {},
} satisfies Meta;
export default meta;
export const Default = (args: Props) => (
<>
<Component {...args}></Component>
</>
);