Common UI Component Package
에 Storybook 을 접목시켜 컴포넌트를 독립적으로 스토리를 짜서 테스트 및 개발 할 수 있는 환경을 구축해봤습니다.
Emotion 과 MUI 를 활용해 작업할 수 있도록 해당 라이브러리를 설치 했고 (@emotion/styled
, @emotion/react
, @mui/material
, @mui/icon-material
), 기타 다른 라이브러리에 의존성이 있는 컴포넌트를 위해서 다른 third-party-library 를 추후 설치할 예정입니다. (예를 들면 react-table
등 ..)
Common UI Component Package 는 기존에 만들어 놓았던 프로젝트를 활용했습니다. 해당 프로젝트에 Storybook
을 접목해보겠습니다.
Storybook 을 활용하기 위해서 Storybook
을 프로젝트에 설치했습니다.
npx -p @storybook/cli sb init
npm install dotenv-webpack --save-dev
dotenv
란 .env
파일에 선언한 변수를 process.env
에 로드 해주는 “무 의존성“ 모듈입니다.
npm run storybook
설치 후 src
폴더 밑에 stories
폴더가 생겼습니다.
해당 라이브러리는 src/lib
폴더의 Typscript 파일들을 컴파일 하여서 패키징 하고 있습니다. Storybook 을 활용하기 위해서 컴포넌트를 stories
폴더 밑에서 생성할것이기 때문에 Typescript 컴파일 대상들을 변경 해줍니다.
아래 코드 중 include
와 exclude
를 변경했습니다.
//tsconfig.json
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": false,
"jsx": "react-jsx",
"declaration": true,
"outDir": "./dist"
},
// "include": ["./src/lib/**/*.tsx", "./src/lib/**/*.ts"]
"include": [
"./src/stories/**/*.tsx",
"./src/stories/**/*.ts",
"src/index.ts",
"src/stories/index.js"
],
"exclude": ["./src/stories/**/*.stories.tsx", "./src/stories/Template/**"]
}
테스트를 위해서 RoRo 에서 사용하고 있는 <Button>
, <BlankLink>
, <Spinner>
를 패키지에 담아봤습니다.
디렉토리는 index.ts
, Component.tsx
, Component.stories.tsx
를 담고있는 Component
폴더로 구성되어 있습니다.
//Button.tsx
import React, { ReactElement } from "react";
import {
Button as MUIButton,
ButtonProps as MUIButtonProps,
} from "@mui/material";
/**
* Button Docs
* MUI Component 활용
*/
export interface ButtonProps extends MUIButtonProps {
text: string;
}
function Button(props: ButtonProps): ReactElement {
return <MUIButton {...props}>{props.text}</MUIButton>;
}
Button.defaultProps = {
variant: "outlined",
};
export default Button;
//Button.stories.tsx
import React from "react";
import Button, { ButtonProps } from "./Button";
import { ComponentMeta, Story } from "@storybook/react";
export default {
title: "Component/Button",
component: Button,
} as ComponentMeta<typeof Button>;
const Templete: Story<ButtonProps> = (args) => {
const { text = "default" } = args;
return <Button text={text} {...args} />;
};
export const Basic = Templete.bind({});
마지막으로 export
를 위한 index.ts
입니다.
//index.ts
export { default } from "./Button";
Storybook
에서 docs 주석을 남기려면 Button.tsx
에 담겨있는 구조의 주석을 활용해야 합니다.
간단하게 컴포넌트를 작성하고, stories
에 작성한 컴포넌트를 매칭해줍니다.
ComponentMeta
타입에 제네릭으로 해당 컴포넌트를 연결시켜 줍니다. 프로퍼티 중 title
은 Storybook 에 나타낼 컴포넌트의 이름, component
는 Storybook 에 나타낼 컴포넌트를 매칭시켜 줍니다.
Templete
은 여러개 작성할 수 있습니다. Templete
을 통해서 해당 컴포넌트의 여러 스토리를 작성할 수 있습니다.
import React from "react";
import Button, { ButtonProps } from "./Button";
import { ComponentMeta, Story } from "@storybook/react";
export default {
title: "Component/Button",
component: Button,
} as ComponentMeta<typeof Button>;
const firstTemplete: Story<ButtonProps> = (args) => {
const { text = "first" } = args;
return <Button text={text} {...args} />;
};
const secondTemplete: Story<ButtonProps> = (args) => {
const { text = "second" } = args;
return <Button text={text} {...args} />;
};
const thirdTemplete: Story<ButtonProps> = (args) => {
const { text = "third" } = args;
return <Button text={text} {...args} />;
};
export const First = firstTemplete.bind({});
export const Second = secondTemplete.bind({});
export const Third = thirdTemplete.bind({});
컴포넌트를 작성한 후 패키지를 퍼블리싱 하기 위해 npm run prepare
실행 해줍니다.
타입스크립트 컴파일 된 폴더의 모습입니다. stories
파일을 제외한 파일들이 컴파일 되었습니다.
UI Component Package
프로젝트에 Storybook 을 접목시켜 코드를 작성해보니, 독립된 컴포넌트 단위로 개발을 하는데 실시간으로 개발되는 컴포넌트를 시각적으로 체크할 수 있고 MUI
등 기타 라이브러리와 연동되는 Storybook 덕분에 개발 경험이 향상될 것 같다는 생각이 들었습니다.
해당 패키지를 다른 프로젝트에서 사용하려면 빌드하고 다른 프로젝트에서 install 해야 할 텐데, 해당 프로젝트를 npm 을 통해 관리하지 않는다면 어떻게 관리할 수 있을지 생각해봐야겠습니다.