Build/Testing

Storybook 사용기

iam102 2024. 1. 19. 22:58

작업 중인 프로젝트에서 새롭게 디자인 시스템을 도입하여 공통 컴포넌트를 작업하게 되었습니다. 디자인팀과의 협업과 개발팀 내부에서 공통 컴포넌트 작업 내용 공유를 위해 Storybook의 도입을 고려하게 되었습니다. 이전 회사에서 Storybook을 사용하며 공통 컴포넌트 개발 내역과 지속적으로 수정되는 사안들을 체크하기에 좋은 경험을 가지고 있었기 때문입니다. 그래서 이번 기회에 간단하게 Storybook에 대해 정리하고자 합니다.

 

Storybook?

Storybook은 UI 컴포넌트 개발을 돕는 도구로 Story라는 단위로 UI 컴포넌트의 상태를 렌더링 합니다. 각 UI 컴포넌트를 스토리 단위로 구성하여 구축한다면 컴포넌트 개발 시 확인을 위해 따로 코드를 작성할 필요가 없고 컴포넌트의 상태에 따른 UI 변화를 시각적으로 바로 확인하며 개발과 테스트를 진행할 수 있습니다. 그리고 UI 컴포넌트를 색인화된 문서로 개발팀 내에 공유할 수 있으며 디자인 시스템을 도입하여 개발 시에도 용이합니다.

 

Storybook 설정

Storybook을 사용할 프로젝트에서 설치합니다.

npx storybook@latest init

# or

yarn dlx storybook@latest init

 

해당 command를 통해 프로젝트의 dependencies에 맞춘 Storybook 라이브러리 구성으로 설치가 됩니다. 그리고 package.json scripts에 자동으로 Storybook 관련 command도 추가됩니다.

...

  "scripts": {
    ...
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build"
  },
  
  ...

 

루트 디렉토리에 .storybook 디렉토리가 생성되며 main.js | ts, preview.js | ts 파일이 생성됩니다.

main.js | ts는 Storybook의 config를 설정하는 파일로 UI 컴포넌트의 story 파일이 위치한 경로를 명시하는 stories와 사용할 addon을 추가하는 addons 및 기타 프로젝트를 설정하는 옵션이 있습니다.

// /.storybook/main.js|ts
// 사용 중인 프레임워크 환경에 따라 설치된 storybook 라이브러리로 대체
import type { StorybookConfig } from "@storybook/<your-framework>";

const config: StorybookConfig = {
  // story 파일이 위치한 경로를 명시
  stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
  // 사용할 addon 등록
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-onboarding",
    "@storybook/addon-interactions",
  ],
  // 사용 중인 프레임워크 환경 기반으로 storybook 구성
  framework: {
    name: "@storybook/react-vite",
    options: {},
  },
  // Storybook Autodocs 설정(story 문서 자동 생성 옵션) 
  docs: {
    autodocs: "tag",
  },
  // 추가로 설정할 옵션이 있으면 추가(https://storybook.js.org/docs/configure)
};
export default config;

 

preview.js | ts는 Storybook을 사용할 프로젝트에서 global 하게 공통으로 사용할 story의 옵션(decorators, parameters, globalTypes)을 설정합니다. 해당 설정을 통해 story의 UI 컴포넌트를 그리는 canvas를 추가 설정을 하거나 story에 CSS imports와 같이 전역적으로 적용되어야 할 코드를 설정할 수 있습니다.

// /.storybook/preview.js|ts
// 사용 중인 프레임워크에 따라 설치된 storybook 라이브러리로 대체
import type { Preview } from "@storybook/<your-framework>";

const preview: Preview = {
  // 프로젝트의 모든 story 공통 parameter 지정
  parameters: {
    actions: { argTypesRegex: "^on[A-Z].*" },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
  },
  // story의 UI 컴포넌트가 렌더링 되는 canvas에 추가적으로 렌더링을 감쌀 수 있는 옵션
  // style 관련 라이브러리 Provider를 story의 root에 렌더링 되도록 설정할 수 있음
  decorators: [],
  // story toolbar 옵션
  globalTypes: {},
};

export default preview;

 

Story 생성

예시를 위해 /src/stories에서 Storybook을 설치하며 생긴 예시 파일을 정리하고 React로 Button 컴포넌트를 작성해보겠습니다.

// Button Component base React
// /src/stories/Button.tsx
import './button.scss';

export enum ButtonSize {
  SMALL = 'small',
  MEDIUM = 'medium',
  LARGE = 'large',
}

export enum ButtonColor {
  PRIMARY = 'primary',
  DARK = 'dark',
  LIGHT = 'light',
}

function Button(props: {
  size?: ButtonSize;
  color?: ButtonColor;
  children: React.ReactNode;
}) {
  return (
    <button
      className={`button btn-${props.size || ButtonSize.MEDIUM} btn-${
        props.color || ButtonColor.LIGHT
      }`}
      type="button"
    >
      {props.children}
    </button>
  );
}

export default Button;
/* /src/stories/button.scss */
.button {
  border: 1px solid;
  border-radius: 4px;

  &.btn-small {
    padding: 4px 8px;
  }
  &.btn-medium {
    padding: 8px 16px;
  }
  &.btn-large {
    padding: 12px 32px;
  }
  &.btn-primary {
    color: #FFFFFF;
    background-color: #F24141;
    border-color: #F24141;
  }
  &.btn-dark {
    color: #FFFFFF;
    background-color: #333333;
    border-color: #333333;
  }
  &.btn-light {
    color: #000000;
    background-color: #FFFFFF;
    border-color: #000000;
  }
}

 

해당 컴포넌트의 story를 작성하기 위해 파일명에 .stories를 가지도록 네이밍을 합니다(main.js | ts  참고).

Storybook에서 사용하는 포맷인  CSF(Component Story Format) 방식으로 작성되며 이전에 사용하던 storiesOf는 지원이 중단되었습니다.

default로 해당 컴포넌트 meta 정보를 담아 export 합니다. meta 정보에는 해당 컴포넌트와 Storybook 메뉴에 표기될 title, preview.js | ts에서 지정한 decorators, parameters 이외로 해당 컴포넌트에서 지정할 decorators, parameters 등 추가로 해당 컴포넌트의 story 구성 요소가 렌더링 되는 옵션을 설정할 수 있습니다.

그리고 추가로 각 arguments에 따른 컴포넌트의 story를 작성할 수 있습니다.

// /src/stories/Button.stories.ts
import type { Meta, StoryObj } from '@storybook/react';

import Button, { ButtonColor, ButtonSize } from './Button';

const meta = {
  title: 'Example/Button',
  component: Button,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],
} satisfies Meta<typeof Button>;

export default meta;
type Story = StoryObj<typeof meta>;

// component arguments 별 story
export const Default: Story = {
  args: {
    children: 'Button',
  },
};

export const Primary: Story = {
  args: {
    color: ButtonColor.PRIMARY,
    children: 'Button',
  },
};

export const Dark: Story = {
  args: {
    color: ButtonColor.DARK,
    children: 'Button',
  },
};

export const Small: Story = {
  args: {
    size: ButtonSize.SMALL,
    children: 'Button',
  },
};

export const Large: Story = {
  args: {
    size: ButtonSize.LARGE,
    children: 'Button',
  },
};

 

 

작성된 Button 컴포넌트의 story를 Storybook을 실행시켜 확인해 보겠습니다.

 

마무리

이전 프로젝트에서 Storybook을 사용할 때 설정이 까다로웠는데 이번에 Storybook을 도입하며 Storybook Cli를 사용하니 정말 간편하게 설정이 완료되어 많이 편해졌습니다. 개발팀 내부에서의 작업과 다른 팀과의 공유에서 많은 만족도를 얻을 수 있었습니다. 다만 Storybook의 기본적인 내용만 포스팅했기 때문에 실제 프로젝트에서 사용 시 추가될 addon과 옵션들을 더욱 활용해야겠습니다.

 


참고자료

https://storybook.js.org/docs/get-started/install