프로젝트를 진행하던 중 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가지 방법을 찬찬히 살펴봤다!
Missing Context/Providers
이 오류는 decorator를 사용하여 특정 context나 provider를 제공했을 때 관련 설정이 누락되면 발생한다는 의미였다. 일단 이 때는 decorator 사용을 안했기 때문에 이 오류와 상관없겠다 싶어서 패스!
Misconfigured Webpack or Vite
이 오류는 vite나 webpack을 사용하여 storybook을 구성하는 설정이 없을 때 발생한다는 의미였다. 난 vite 를 사용하고 있고, esbuild 를 사용하는 storybook이니만큼, 특정 설정을 빼먹었을 수도 있겠다..
싶어서 이 오류를 주의깊게 봤다!
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 빌더를 사용하지 않는 것이 좋다고 한다..😰
Node.js 환경에서 .env 파일과 같은 환경 변수 파일에 접근하기 위해 사용하는 자바스크립트 내장 객체
https://github.com/browserify/node-util/issues/43 여기서도 비슷한 문제가 발생했다. 그런데 여기서는 polyfill을 제공해주고 있었다! (물론 설정도 해야하는 부분이 있었지만..)
프로젝트를 하면서 기존에 MUI를 사용하고 있었다. 그래서 MUI + Storybook 은 안될까? 고민하며 찾아봤는데, addon
을 사용해서 적용한 케이스가 있었다. 그래서 나도 적용했고, 이것은 내가 적용한 설정 파일들이다.
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;
// 해당 프로젝트의 모든 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;
import { createTheme } from "@mui/material";
export const lightTheme = createTheme({
palette: {
mode: 'light'
},
});
export const darkTheme = createTheme({
palette: {
mode: 'dark'
},
});
// 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을 왜 사용하는지 더 알 것 같은 느낌이었다 ㅎㅎ.
아 그리고 자세한 설정과 사용할 스크립트는 이 링크를 통해 알아보도록 하자!!