[Frontend] 프로젝트일지(3) - storybook 이모저모

eunniverse·2024년 6월 11일
0

글 쓰게된 계기

프로젝트를 진행하던 중 storybook 수정이 필요해서 storybook을 실행시켰는데, 갑자기 안됐다 ㅠㅠ.. 왜 안되지 하면서 천천히 살펴보고 서치하다가 해결 방법을 발견했고 '오 이건 다른 사람도 발생할지도..?!' 라는 생각이 들어 도움이 되기 위해 작성했다!

storybook 을 실행했더니...

갑자기 이런 오류가 떴다.. process is not defined ?? 원래 잘 돌아갔는데, 왜 안될까?

우선 The component failed to render properly, likely due to a configuration issue in Storybook. Here are some common causes and how you can address them 라는 메시지를 번역해봤다.

이 메시지의 의미는 Storybook의 구성 문제로 인해 구성 요소가 제대로 렌더링되지 못했습니다. 다음은 몇 가지 일반적인 원인과 해결할 수 있는 방법입니다 였다.
'오 해결방법을 알려주잖아?!' 하면서 써있던 3가지 방법을 찬찬히 살펴봤다!

오류 해결 방법

⚡️오류가 발생하는 원인 3가지

  1. Missing Context/Providers
    이 오류는 decorator를 사용하여 특정 context나 provider를 제공했을 때 관련 설정이 누락되면 발생한다는 의미였다. 일단 이 때는 decorator 사용을 안했기 때문에 이 오류와 상관없겠다 싶어서 패스!

  2. Misconfigured Webpack or Vite
    이 오류는 vite나 webpack을 사용하여 storybook을 구성하는 설정이 없을 때 발생한다는 의미였다. 난 vite 를 사용하고 있고, esbuild 를 사용하는 storybook이니만큼, 특정 설정을 빼먹었을 수도 있겠다.. 싶어서 이 오류를 주의깊게 봤다!

  3. Missing Environment Variables
    이 오류는 특정 환경변수가 필요한데, 누락되었을 때 발생한다는 의미였다. 오?! 이건가 싶어서 일단 keep...

⚡️그래서 해결 방법은?

import type { StorybookConfig } from "@storybook/react-vite";
import {mergeConfig} from "vite";
// @ts-ignore
import path from "path";

// @ts-ignore
// @ts-ignore
// @ts-ignore
const config: StorybookConfig = {
  stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
  addons: [
   ...
  ],
  core: {
    builder: '@storybook/builder-vite',
  },
  framework: {
    name: "@storybook/react-vite",
    options: {},
  },
  async viteFinal(config) {
    ...
    // Merge custom configuration into the default config
    return mergeConfig(config, {
      // ** storybook (process is not defined) 라는 오류 발생 시 해결하는 방법 **
      define: {
        "process.env": {},
      },
      ...
    });
  },
};
export default config;

storybook의 vite 설정하는 viteFinal 함수에서 define > process.env를 추가하였더니 정상 동작했다!! 해결방법은 git에서 찾았고, 오류가 발생한 이유는 vite와 webpack의 혼합 사용에 따른 문제라고 볼 수 있었다. Next.js는 Webpack을 기반으로 구축되었으므로 이와 같은 불일치가 발생할 수 있으므로 Storybook에 Vite 빌더를 사용하지 않는 것이 좋다고 한다..😰

💭 process.env?

Node.js 환경에서 .env 파일과 같은 환경 변수 파일에 접근하기 위해 사용하는 자바스크립트 내장 객체

💭 browserify 에서도 비슷한 문제가 발생하는 중...

https://github.com/browserify/node-util/issues/43 여기서도 비슷한 문제가 발생했다. 그런데 여기서는 polyfill을 제공해주고 있었다! (물론 설정도 해야하는 부분이 있었지만..)

+) Material UI를 얹은 storybook 만들기

프로젝트를 하면서 기존에 MUI를 사용하고 있었다. 그래서 MUI + Storybook 은 안될까? 고민하며 찾아봤는데, addon을 사용해서 적용한 케이스가 있었다. 그래서 나도 적용했고, 이것은 내가 적용한 설정 파일들이다.

  • .storybook/main.ts
import type { StorybookConfig } from "@storybook/react-vite";
import {mergeConfig} from "vite";
// @ts-ignore
import path from "path";

// @ts-ignore
// @ts-ignore
// @ts-ignore
const config: StorybookConfig = {
  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",
      /// 적용 필요 ////
    "@storybook/addon-themes",
    '@storybook/addon-styling'
  ],
  core: {
    builder: '@storybook/builder-vite', // 👈 The builder enabled here.
  },
  framework: {
    name: "@storybook/react-vite",
    options: {},
  },
  /// 적용 필요 ////
  typescript: {
    // 컴포넌트의 props 에서 사용된 TypeScript 타입들을 추출하여 문서로 만들어주는 도구
    reactDocgen: 'react-docgen-typescript',
    reactDocgenTypescriptOptions: {
      // storybook 에서 props(variant, size) 등을 select 로 표시하는 옵션
      shouldExtractLiteralValuesFromEnum: true,
      // 정의할 수 없는 string 및 bool 유형은 input or switch 로 표시하는 옵션
      shouldRemoveUndefinedFromOptional: true,
      // mui 로만 props 필터링 => props 의 속성을 mui 로 적용하기
      propFilter: (prop) =>  prop.parent ? !/node_modules\/(?!@mui)/.test(prop.parent.fileName) : true,
    }
  },
  async viteFinal(config) {
    config.resolve.alias = {
      ...config.resolve.alias,
      '@': path.resolve(__dirname, "../src"),
    };

    // Merge custom configuration into the default config
    return mergeConfig(config, {
      define: {
        "process.env": {},
      },
      optimizeDeps: {
        include: ['storybook-dark-mode'],
      },
    });
  },
};
export default config;
  • .storybook/preview.ts
// 해당 프로젝트의 모든 Story에 global하게 적용될 포맷을 세팅하는 곳

import type { Preview } from "@storybook/react";

import { ThemeProvider, CssBaseline } from '@mui/material';
import { withThemeFromJSXProvider } from '@storybook/addon-themes';

// @ts-ignore
import { lightTheme, darkTheme } from '@/theme'

import '@fontsource/roboto/300.css';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';

const preview: Preview = {
  parameters: {
    controls: {
      // 컬럼이나 description 추가
      expanded: true,
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
  },

  decorators: [withThemeFromJSXProvider({
    GlobalStyles: CssBaseline,
    Provider: ThemeProvider,
    themes: {
      // Provide your custom themes here
      light: lightTheme,
      dark: darkTheme,
    },
    defaultTheme: 'light',
  })]
};

export default preview;
  • theme.ts
    테마를 custom 할 수 있길래 일단 추가해봤다. 우선 mui에서 제공하는 테마를 따라가게끔 설정했지만 추후에 바꿀 예정!!
import { createTheme } from "@mui/material";

export const lightTheme = createTheme({
    palette: {
        mode: 'light'
    },
});

export const darkTheme = createTheme({
    palette: {
        mode: 'dark'
    },
});
  • Button.tsx
    실제로 컴포넌트를 설계하는 곳에서는 이렇게 쓰면 된다.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
import React from 'react';
import { Button as MuiButton, ButtonProps as MuiButtonProps } from '@mui/material';

export interface ButtonProps extends MuiButtonProps {
  label: string;
}

// mui 기반 버튼 생성
export const Button = ({ label, ...props }: ButtonProps) => <MuiButton {...props}>{label}</MuiButton>;

이렇게 설정을 하면 컴포넌트를 사용하는 곳에서는 mui를 통해 button을 구성한것처럼 쓰면 정상적으로 동작했다! addon으로 확장성을 늘릴 수 있다는 점을 경험해보니 storybook을 왜 사용하는지 더 알 것 같은 느낌이었다 ㅎㅎ.

아 그리고 자세한 설정과 사용할 스크립트는 이 링크를 통해 알아보도록 하자!!

profile
능력이 없는 것을 두려워 말고, 끈기 없는 것을 두려워하라

0개의 댓글