OTTER-LOG

tsdoc, storybook으로 문서화하기

tsdoc, storybook으로 문서화하기
by otter2023년 2월 7일에 최종수정되었습니다.
잘못된 내용이 있으면 댓글을 달아주세요.

얼마전부터 진행하고 있는 over-ui 의 프로젝트는 거의 끝나가고 있습니다. over-ui 는 예를 들면 Select 와 같은 컴포넌트들을 모아둔 React ui component 라이브러리 입니다. 이러한 라이브러리를 사용자들이 사용할 때에, 쉽게 이해할 수 있으려면 문서화의 과정이 중요하다고 생각했습니다.

그런데 문서화를 진행하던 중 커다란 문제점을 만났습니다. 😂

지금까지의 문서화

지금까지 문서화를 작업했던 부분은 http://README.MD파일에 위와 같은 usage 를 개별적으로 적어두는 방식이었습니다. 그런데, 이와 같은 부분은 다음과 같은 문제점이 있었습니다.

  • 컴포넌트의 모든 api 를 일일이 적어주어야 했습니다. 그런데 위의 함수를 보셔도 아시겠지만, props 의 양이 상당합니다. 또, 컴포넌트가 여러개로 나뉜 경우가 많아 직접 쓰는 것은 비효율적이라 생각했습니다.

  • usage 가 너무 많고, 이를 사용자가 보기 힘들게 느껴졌습니다.

    한 컴포넌트에서 5개 이상의 usage 가 존재했고, 이러한 usage 를 코드로만 보여주고 있었습니다. 이러한 상황에서 목차나, 쉽게 갈 수 있는 하이퍼링크 또한 존재하지 않았습니다.

    모든 api를 일일이 적어주는 부분에서, 이를 작성하는 피로감이 상당할 것으로 예상했습니다. 컴포넌트의 props 의 양이 상당했기 때문입니다.

  • 사용자가 이를 직접 테스트해볼 수 있는 환경이 제공되지 않습니다.

    코드 샌드박스등을 이용할 수도 있었지만, MD 파일에서는 이를 한꺼번에 제공하는 것이 아니고, 링크 등으로 개별적으로 연결해 주어야 했습니다.

tsdoc으로 api 문서 생성하기

저희는 tsdoc 을 통해, 해당 컴포넌트의 타입들에 대한 설명을 아래와 같이 적용하고 있었습니다.

export type ToggleRenderProps = { /** * children을 render할때 사용할 수 있는 renderProps 입니다. @example * ```tsx * <Toggle>{({ pressed }) => (pressed ? 'pressed' : 'notPressed')}</Toggle> * // pressed 값에 따라, 다른 컴포넌트, string, 아이콘을 조건부 렌더링할 수 있습니다. * ``` */ pressed: boolean; disabled: boolean; }; export type ToggleProps = { /** * 외부에서 선언한 상태를 이용할때 사용합니다. * `defaultPressed`와 함께 사용할 수 없습니다. * `onPressedChange` 를 함께 사용해야합니다. * * @example * ```tsx * const [state, setState] = React.useState(false); * return <Toggle pressed={state} onPressedChange={setState}/> * ``` */ pressed?: boolean; /** * `Toggle`의 `pressed` 상태의 초기값을 지정합니다. * `pressed`와 함께 사용할 수 없습니다. * `onPressedChange`와 함께 사용할 수 있습니다. * @defaultValue false */ defaultPressed?: boolean; /** * `Toggle`의 상태가 바뀔때 실행되는 함수입니다. */ onPressedChange?: (pressed: boolean) => void; /** * 토글을 `disabled`하는 prop입니다. * @defaultValue false */ disabled?: boolean; children?: React.ReactNode | ((props: ToggleRenderProps) => React.ReactNode); };

typedoc-plugin-markdown 플러그인을 통해, 다음과 같은 markdown 문서를 추출해 낼 수 있습니다.

markdown 형태로 추출되기 때문에, 이를 그대로 복사해서 사용할 수 있었습니다. 사실 typedoc 만을 통해 그대로 문서화된 파일을 배포할 수도 있지만, 저희는 실제 코드의 사용례를 같이 보여주고 싶어 typedocapi 를 추출해 내는 형태로만 이용했습니다.

React docgen을 사용하지 않은 이유

"react-docgen-typescript-plugin": "^1.0.5", "react-remove-scroll": "^2.5.5", "rollup": "^3.3.0", "rollup-plugin-peer-deps-external": "^2.2.4", "storybook-addon-react-docgen": "^1.2.43",

그런데, 사실 react-docgen-typescript-plugin 이라는 스토리북 addon 이 이미 존재합니다. 이를 간단히 storybook 설정 파일에 적용해주면, 아래와 같이 proptable 을 추출할 수 있습니다.

그런데, 이 라이브러리는 실제 컴포넌트 이름과 설정해주는 디스플레이 이름이 다를 경우 props 들을 가지고 오지 못하는 문제점이 있었습니다. 저희의 컴포넌트들은 대다수 합성 컴포넌트 형태를 취하고 있었고 실제 컴포넌트 이름과, display 이름이 다른 경우가 많았습니다. 해당 라이브러의 깃허브 이슈를 확인해 보니 해당 부분의 문제점이 해결되지 않아 사용하지 못했습니다.

( 수정 시점에 확인해 보니 해당 이슈가 해결되었습니다! )

Storybook 에 문서화하기

storybook 을 통해 문서화를 진행하면서 저희는 mdx 포맷을 이용했습니다. mdx 포맷은 jsx 를 가지고 와서 사용할 수 있다는 장점이 존재했기 때문입니다.

// toggle.stories.tsx // import mdx from './toggle.mdx'; export default { title: 'over-ui/Toggle', parameters: { docs: { page: mdx, // mdx 문서를 이용하는 설정을 진행해줍니다. }, controls: { expanded: true }, }, } as Meta;

그리고 mdx 문서를 아래와 같이 적용해 주었습니다.

## with emotion `emotion`을 사용한다면 다음과 같이 스타일링할 수 있습니다. `data-pressed`, `data-disabled`를 통해 스타일링을 간편히 할 수 있습니다. <Canvas> <Story id="over-ui-toggle--styled" /> // 작성된 story를 불러오는 기능을 추가했습니다. </Canvas> ```tsx const StyledToggle = styled(Toggle)` padding: 10px 15px; &[data-pressed='on'] { background-color: green; } &[data-disabled='true'] { background-color: red; } &:focus { border: 2px solid blue; } border: none; font-size: 12px; `;


위와 같이 진행하면 아래와 같이 문서화가 완료됩니다.


![](http://res.cloudinary.com/ddzuhs646/image/upload/v1675772623/blog/8936e902-a5db-4030-9a46-7db7a96d53ef/8936e902a5db40309a467db7a96d53ef.png)


사용자는 이 문서에서 `show code` 를 통해 실제 코드를 바로 확인해 볼 수 있습니다.  그리고 **이 문서에서 바로 클릭을 통해 인터렉션을 통해 어떤 점이 바뀌는지 테스트**해볼 수 있습니다.


작성하는 사람의 입장에서도 생각보다 시간이 오래 걸리지 않았습니다. 이미, `storybook` 을 통해 `ui` 테스트를 진행하고 있었기도 했으며, 시간이 없다면 `storybook` 의 `actions` 등의 기능은 적용하지 않아도 위와 같은 문서화가 가능했기 때문입니다.


```typescript
export const Styled = () => {
  return (
    <div style={{ display: 'flex', gap: '10px' }}>
      <StyledToggle>styled Toggle</StyledToggle>
      <StyledToggle disabled>disabled Toggle</StyledToggle>
      <StyledToggle defaultPressed={true}>pressed Toggle</StyledToggle>
    </div>
  );
};

const StyledToggle = styled(Toggle)`
  padding: 10px 15px;

  &[data-pressed='on'] {
    background-color: green;
  }
  &[data-disabled='true'] {
    background-color: red;
  }
  &:focus {
    border: 2px solid blue;
  }
  border: none;
  font-size: 12px;
`;

// 위의 문서화를 거치기 위해 쓴 코드는 이정도 입니다.
// 이 부분이 스타일링을 적용하는 부분이어서 크기가 가장 큰 편이었습니다.

마무리

문서화를 하는 것은 중요하다고 생각합니다. 이번에도, 팀원이 작성한 컴포넌트를 제가 사용할 수도 있었고 제가 작성한 코드를 팀원이 사용할 수도 있었습니다. 문서화가 없었다면, 해당 컴포넌트를 제대로 이용하기 힘들 것이라는 생각이 들었습니다.

다만, 문서화를 편하게 하는 방법을 생각하는 것도 좋다고 생각합니다. 사실 이 과정을 거치면서 그냥 api 손으로 일일이 써줄걸.. 하는 생각도 들었지만 마무리하고 나니 이후의 문서작업을 간단히 마무리할 수 있었기 때문입니다.

Ref