2. 스토리북, 시작하기.

Storybook을 사용할 때 create-react-app으로 만든 프로젝트에서 Storybook 설정을 하여 사용할 수도 있고 아예 새로운 Storybook 전용 프로젝트를 만들어서 사용할 수도 있습니다.

우리는 먼저 Storybook 전용 프로젝트를 만드는 방법을 배우고 나중에 CRA로 만든 프로젝트에 Storybook을 사용하는 방법도 알아보겠습니다.

이 프로젝트에서 사용중인 코드는 이 링크에서 확인할 수 있습니다.

프로젝트를 생성하기 위하여 다음 명령어를 입력해주세요.

mkdir storybook-tutorial
cd storybook-tutorial
yarn init -y # 또는 npm init -y
npx -p @storybook/cli sb init --type react

이 강의에서는 주로 yarn을 사용하며, 이를 사용하는 것을 권장드립니다. 설치되어 있지 않다면 이 링크를 참고하여 설치를 하세요. (npm만 사용해도 큰 지장은 없습니다. )

npx는 글로벌 패키지 설치 없이 특정 명령어를 바로 실행 할 수 있게 해주는 도구이며 npm 5.2.0 이상 버전에서 사용할 수 있습니다. 작동하지 않는다면 npm -v 명령어를 입력하여 버전을 확인해보시고 새 버전으로 업데이트 해주세요.

명령어를 실행하시고 난 다음에는 yarn storybook 또는 npm run storybook 명령어를 실행하시고 나면 Storybook 서버가 가동됩니다.

image.png

스토리 경로 바꾸기

현재 디렉터리 구조가 다음과 같이 구성이 되어있습니다.

image.png

한번 이 구조를 좀 변경해보도록 하겠습니다. 우리는 src 라는 디렉터리를 만들어서 그 안에서 컴포넌트 코드 및 스토리들의 코드를 관리할 것입니다.

src 디렉터리를 생성하시구요, stories 디렉터리를 src 안으로 옮겨주세요.

그 다음에는, .storybook 디렉터를 열으시면 config.js 라는 파일이 다음과 같이 적혀있습니다.

.storybook/config.js

import { configure } from '@storybook/react';

// automatically import all files ending in *.stories.js
configure(require.context('../stories', true, /\.stories\.js$/), module);

여기서, '../stories' 부분이 어디서부터 스토리들을 불러올 지 알려주는 경로인데요, 이 경로를 '../src' 로 변경해보세요.

configure(require.context('../src', true, /\.stories\.js$/), module);

이제 Storybook 서버를 종료 (터미널에서 Ctrl + C) 후 다시 시작해주세요. 그리고, 이전과 똑같이 잘 나타나고 있는 것을 확인하셨다면, src/stories 경로는 이제 불필요하니 지우셔도 됩니다.

첫번째 컴포넌트와 첫번째 스토리 만들기

자, 우리의 첫번째 컴포넌트 Hello를 만들어보겠습니다. src 경로에 Hello 디렉터리를 만들고, 그 안에 Hello.js 파일을 다음과 같이 작성해보세요.

참고로, 우리는 먼저 JavaScript만을 사용하여 스토리북을 활용해보고 나중에 TypeScript를 적용해서 쓰는 방법도 다뤄보도록 하겠습니다.

src/Hello/Hello.js

import React from 'react';

const Hello = ({ name, big }) => {
  if (big) {
    return <h1>안녕하세요, {name}!</h1>;
  }
  return <p>안녕하세요, {name}!</p>;
};

export default Hello;

big 이라는 props 가 true 로 주어지면 h1 태그를 렌더링하고 그렇지 않으면 p 태그를 렌더링하는 컴포넌트를 만들어보았습니다.

이제, 스토리를 만들어봅시다. 스토리를 작성 할 때에는 .stories.js 확장자로 작성하시면 됩니다.

Storybook v5.2 부터는 Component Story Format (CSF) 형식을 사용하여 문서를 작성합니다. (현재 기준 최신 버전 v5.2.3)

옛날에 스토리북을 사용해보셨거나, 또는 구글에 검색해보면 많은 가이드들이 storiesOf 라는 API 를 사용합니다. 예시 코드를 잠깐 확인해볼까요?

import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import Button from '../components/Button';

storiesOf('Button', module)
  .add('with text', () => (
    <Button onClick={action('clicked')}>Hello Button</Button>
  ))
  .add('with some emoji', () => (
    <Button onClick={action('clicked')}>
      <span role="img" aria-label="so cool">
        😀 😎 👍 💯
      </span>
    </Button>
  ));

이 방식이 아직도 작동하고있기 때문에 이 방식이 deprecated 된 것은 아닙니다. 다만, 최신버전의 Storybook에서는 이 API 말고 CSF 형식으로 작성되는 것이 권장됩니다. 왜냐구요? 그냥 훨씬 깔끔하고 편합니다!

CSF를 사용할 땐 export default { } 코드를 사용하여 어떤 컴포넌트의 문서인지, 그리고 또 어떤 설정을 적용 할 건지 정의합니다. 그리고 export const storyName = ... 코드를 사용하여 새로운 스토리를 만듭니다.

src/Hello 경로에 Hello.stories.js 파일을 생성하여 다음과 같이 코드를 작성해보세요.

src/Hello/Hello.stories.js

import React from 'react';
import Hello from './Hello';

export default {
  title: 'components|basic/Hello', // 스토리북에서 보여질 그룹과 경로를 명시
  component: Hello // 어떤 컴포넌트를 문서화 할지 명시
};

export const standard = () => <Hello name="Storybook" />;
export const big = () => <Hello name="Storybook" big />;

그러면, Storybook에 다음과 같이 스토리가 보여질 것입니다.

image.png

export default 를 통하여 객체를 내보낼 때, title 값은 스토리북에서 보여지는 그룹과 경로를 명시합니다. 위 Hello 컴포넌트의 경우 그룹은 COMPONENTS 가 되어있고, 경로는 basic으로 설정되었죠.

이렇게 스토리를 만들고, 각 스토리를 브라우저 상에서 확인 하는 것은 StoryBook의 가장 기본적인 기능입니다. 여기에 Storybook의 애드온(Addon)들을 적용하면 더욱 다양하고 편리한 기능들을 붙여줄 수 있습니다.

2-1. Knobs 애드온 적용하기

우리가 첫번째로 다뤄볼 애드온은 Knobs 입니다. Knobs 애드온은 컴포넌트의 props 를 스토리북 화면에서 바꿔서 바로 반영시켜줄 수 있는 애드온입니다. 먼저 애드온을 설치해주세요.

$ yarn add --dev @storybook/addon-knobs
# 또는 npm install --save-dev @storybook/addon-knobs

그 다음에는, .storybook 경로에 있는 addon.js 파일을 열어서 다음 코드를 추가해주세요.

.storybook/addons.js

import '@storybook/addon-knobs/register';

이제 우리가 만들었던 Hello 스토리에 withKnobs라는 함수를 사용하여 해당 애드온을 적용하면 됩니다. Knobs 공식 문서에서는 다음과 같이 사용 하라고 안내하고 있습니다.

import { storiesOf } from '@storybook/react';
import { withKnobs, text, boolean, number } from '@storybook/addon-knobs';

const stories = storiesOf('Storybook Knobs', module);

// Add the `withKnobs` decorator to add knobs support to your stories.
// You can also configure `withKnobs` as a global decorator.
stories.addDecorator(withKnobs);

애드온으로 사용하는 withKnob과 같은 함수는 데코레이터(decorator) 라고 부릅니다. 위 코드 포맷은 예전의 방식인 storiesOf API 를 사용하는 형태입니다.

만약에 CSF를 사용한다면 addDecorator를 사용하는 대신에 export default로 내보내는 객체 안에 decorators 라는 배열을 만들어서 그 안에 withKnobs 를 넣어주시면 됩니다.

다음과 같이 말이죠!

src/Hello/Hello.stories.js

import React from 'react';
import Hello from './Hello';
import { withKnobs } from '@storybook/addon-knobs';

export default {
  title: 'components|basic/Hello', // 스토리북에서 보여질 그룹과 경로를 명시
  component: Hello, // 어떤 컴포넌트를 문서화 할지 명시
  decorators: [withKnobs] // 애드온 적용
};

export const standard = () => <Hello name="Storybook" />;
export const big = () => <Hello name="Storybook" big />;

그 다음에는, default 라는 이름으로 Knobs 를 사용하는 새로운 스토리를 만들어보겠습니다. 기본적으로는, 스토리를 만들 때 export const를 사용하여 default 라는 이름으로 내보낼 수 없습니다. 키워드가 충돌되기 때문이죠. 그 대신에, 스토리를 만들고, 해당 스토리의 멤버 변수로 story 객체를 설정하면 이름을 변경 할 수 있습니다.

src/Hello/Hello.stories.js

import React from 'react';
import Hello from './Hello';
import { withKnobs, text, boolean } from '@storybook/addon-knobs';

export default {
  title: 'components|basic/Hello', // 스토리북에서 보여질 그룹과 경로를 명시
  component: Hello, // 어떤 컴포넌트를 문서화 할지 명시
  decorators: [withKnobs] // 애드온 적용
};

export const hello = () => {
  // knobs 만들기
  const big = boolean('big', false);
  const name = text('name', 'Storybook');
  return <Hello name={name} big={big} />;
};
hello.story = {
  name: 'Default'
};

export const standard = () => <Hello name="Storybook" />;
export const big = () => <Hello name="Storybook" big />;

참고: export const default 가 아닌 export const Default 라고 하면 작동하기는 합니다.

Default 스토리에서 우리는 두개의 Knobs를 만들었습니다. 사용할 수 있는 Knobs 의 종류는 다음과 같습니다.

  • text: 텍스트를 입력 할 수 있습니다.
  • boolean: true/false 값을 체크박스로 설정 할 수 있습니다.
  • number: 숫자를 입력 할 수 있습니다. 1~10과 같이 간격을 설정 할 수도 있습니다.
  • color: 컬러 팔레트를 통해 색상을 설정 할 수 있습니다.
  • object: JSON 형태로 객체 또는 배열을 설정 할 수 있습니다.
  • array: 쉼표로 구분된 텍스트 형태로 배열을 설정 할 수 있습니다.
  • select: 셀렉트 박스를 통하여 여러가지 옵션 중에 하나를 선택 할 수 있습니다.
  • radios: Radio 버튼을 통하여 여러가지 옵션 중에 하나를 선택 할 수 있습니다.
  • options: 여러가지 옵션을 선택 하는 UI 를 커스터마이징 할 수 있습니다 (radio, inline-radio, check, inline-check, select, multi-select)
  • files: 파일을 선택 할 수 있습니다.
  • date: 날짜를 선택 할 수 있습니다.
  • button: 특정 함수를 실행하게 하는 버튼을 만들 수 있습니다.

위 Knobs 들의 자세한 스펙은 https://www.npmjs.com/package/@storybook/addon-knobs#available-knobs 에서 확인 할 수 있습니다.

Knobs 를 사용 할 때 넣어주어야 하는 주요 인자로는 Knobs 의 이름, 기본값 그리고 GROUP ID 가 있습니다. GROUP ID 의 경우 생략해도 됩니다.

만약 다음과 같이 그룹을 설정하게 된다면

const big = boolean('big', false, 'Group 1');

화면에서는 다음과 같이 그룹이 분류되어 나타나게 된답니다.

image.png

props 에 따라 어떤 결과물을 보여주는지 바로바로 확인하고 싶을 때, 이렇게 Knobs 를 사용하면 매우 유용합니다!

2-2. Actions 애드온 적용하기

이번에 배워볼 애드온은 Actions 애드온입니다. 이 애드온은 컴포넌트를 통하여 특정 함수가 호출됐을 때 어떤 함수가 호출됐는지, 그리고 함수에 어떤 파라미터를 넣어서 호출했는지에 대한 정보를 확인 할 수 있게 해줍니다.

간단한 함수 호출부터 시작해서, 나중에는 리액트 라우터의 주소가 변경될 때를 확인하거나 리덕스 스토어의 dispatch를 mocking하여 디스패치 되는 액션의 정보를 볼 수 도 있습니다.

링크에서 하단에 있는 액션탭을 열고 컴포넌트에서 항목을 선택해보시면 이 Actions 애드온이 어떤 역할을 하는지 더욱 쉽게 이해 할 수 있을 것입니다.

image.png

참고로 이 애드온은 우리가 Storybook CLI로 만든 프로젝트에 기본적으로 적용이 되어있기 때문에 별도로 설치하실 필요가 없습니다.

이 애드온을 한번 사용해보기 위하여, Hello 컴포넌트에 onHello 와 onBye 라는 함수를 props 로 받아오게 해서 버튼을 클릭 했을 때 각 함수를 호출하도록 구현을 해보겠습니다.

src/Hello/Hello.js

import React from 'react';

const Hello = ({ name, big, onHello, onBye }) => {
  return (
    <div>
      {big ? <h1>안녕하세요, {name}!</h1> : <p>안녕하세요, {name}!</p>}
      <div>
        <button onClick={onHello}>Hello</button>
        <button onClick={onBye}>Bye</button>
      </div>
    </div>
  );
};

export default Hello;

이제 Hello 컴포넌트의 스토리 파일을 다음과 같이 수정해보세요.

import React from 'react';
import Hello from './Hello';
import { withKnobs, text, boolean } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';

export default {
  title: 'components|basic/Hello', // 스토리북에서 보여질 그룹과 경로를 명시
  component: Hello, // 어떤 컴포넌트를 문서화 할지 명시
  decorators: [withKnobs] // 애드온 적용
};

export const hello = () => {
  // knobs 만들기
  const big = boolean('big', false);
  const name = text('name', 'Storybook');
  return (
    <Hello
      name={name}
      big={big}
      onHello={action('onHello')}
      onBye={action('onBye')}
    />
  );
};
hello.story = {
  name: 'Default'
};

export const standard = () => <Hello name="Storybook" />;
export const big = () => <Hello name="Storybook" big />;

새로운 액션을 만들 땐 위 코드와 같이 action('액션 이름') 이라고 작성을 하시면 됩니다.

이제 Storybook페이지를 열어서 Hello 및 Bye 버튼들을 눌러보세요. Actions 탭에 발생한 액션들이 잘 나타나고 있나요?

image.png

2-3. Docs 애드온

Docs 애드온은 MDX 형식으로 문서를 작성 할 수 있게 해주고, 컴포넌트의 props와 주석에 기반하여 자동으로 아주 멋진 문서를 자동생성해줍니다.

먼저, 이 애드온을 설치해주세요.

yarn add --dev @storybook/addon-docs
# 또는 npm install --save-dev @storybook/addon-docs

그 다음에는 .storybook 경로에 presets.js 파일을 만드시고 다음과 같이 코드를 작성해주세요.

module.exports = ['@storybook/addon-docs/react/preset'];

Storybook의 preset 기능은 웹팩설정을 비롯한 여러 설정들을 하나의 그룹으로 만들어 설정을 더욱 쉽게 해줄 수 있는 기능입니다.

이 preset 을 적용하고 나서 .storybook/config.js 파일을 열어서 Storybook에서 .mdx 확장자도 처리하도록 정규식을 수정하세요.

.storybook/presets.js

import { configure } from '@storybook/react';

// automatically import all files ending in *.stories.js
configure(require.context('../src', true, /\.stories\.(js|mdx)$/), module);

이제 적용이 끝났습니다! Storybook 서버를 종료 후 다시 실행하면 Hello 스토리를 열었을 때 다음과 같이 상단에 Docs라는 탭이 보여질 것입니다.

image.png

Props 문서화 하기

지금은 Props 부분이 "No props found for this component" 라고 보여지면서 비어있습니다. 이 부분을 채우려면 prop-types 또는 TypeScript를 사용해야합니다. 아직 우리 프로젝트에 TypeScript 설정을 하진 않았으니까, prop-types를 사용해보도록 하겠습니다. 추가적으로, defaultProps도 지정해주면 이를 추출해서 보여줄 수 있습니다. defaultProps 도 설정을 해보세요.

src/Hello/Hello.js

import React from 'react';
import PropTypes from 'prop-types';

const Hello = ({ name, big, onHello, onBye }) => {
  return (
    <div>
      {big ? <h1>안녕하세요, {name}!</h1> : <p>안녕하세요, {name}!</p>}
      <div>
        <button onClick={onHello}>Hello</button>
        <button onClick={onBye}>Bye</button>
      </div>
    </div>
  );
};

Hello.propTypes = {
  /** 보여주고 싶은 이름 */
  name: PropTypes.string.isRequired,
  /** 이 값을 `true` 로 설정하면 h1 태그로 렌더링 됩니다. */
  big: PropTypes.bool,
  /** Hello 버튼 누를 때 호출할 함수 */
  onHello: PropTypes.func,
  /** Bye 버튼 누를 때 호출할 함수 */
  onBye: PropTypes.func
};

Hello.defaultProps = {
  big: false
};

export default Hello;

propTypes 를 설정 할 때 각 props 위에 /** */ 주석으로 문구를 넣어주면 이 문구가 나중에 문서에서 나타나게 됩니다.

코드를 저장하고 Docs 페이지를 확인해보세요. 다음과 같이 설명이 잘 나타났나요?

image.png

컴포넌트에 부제목 및 설명 넣기

컴포넌트에 부제목(Subtitle)을 설정해보겠습니다. 부제목을 설정 할 때에는 스토리의 parameters 부분을 설정하면 됩니다.

src/Hello/Hello.stories.js - export default 부분

export default {
  title: 'components|basic/Hello', // 스토리북에서 보여질 그룹과 경로를 명시
  component: Hello, // 어떤 컴포넌트를 문서화 할지 명시
  decorators: [withKnobs], // 애드온 적용
  parameters: {
    componentSubtitle: '"안녕하세요"라고 보여주는 컴포넌트'
  }
};

이렇게, componentSubtitle 값을 설정해주고 나면 페이지에서 다음과 부제목이 같이 보여지게 됩니다.

image.png

이번에는 설명(Description)을 추가해보겠습니다. 컴포넌트의 설명을 추가 할 때는 컴포넌트 파일에서 컴포넌트 코드 바로 윗 부분에 주석으로 작성하면 됩니다.

src/Hello/Hello.js

/**
 * 안녕하세요 라고 보여주고 싶을 땐 `Hello` 컴포넌트를 사용하세요.
 *
 * - `big` 값을 `true`로 설정하면 **크게** 나타납니다.
 * - `onHello` 와 `onBye` props로 설정하여 버튼이 클릭했을 때 호출 할 함수를 지정 할 수 있습니다.
 */
const Hello = ({ name, big, onHello, onBye }) => {
  return (

이렇게 멀티라인 주석을 작성하시면 되는데, 주석의 시작 부분에서 /** 와 같이, 별표를 두번 넣어주셔야 Docs 가 만들어질 때 이를 파싱합니다.

image.png

문서가 잘 만들어졌나요?

MDX로 문서를 작성하기

MDX를 사용하면 마크다운으로 리액트 컴포넌트를 더욱 쉽게 문서화 할 수 있습니다. 만약 자동 생성되는 DocsPage만 사용한다면 설명을 추가 할 수 있는 공간이 고정되어있기 때문에 설명을 함에 있어서 제한이 있을 수 있지만, MDX를 사용한다면 원하는 곳 어디든지 설명을 추가 할 수 있기 때문에 더욱 자유롭게 문서화를 할 수 있습니다.

컴포넌트 문서화 외에도, Style Guide 에 속하는 색상, Typography에 관한 문서 또는 디자인 시스템에 대한 README 를 작성 할 때 MDX 를 사용하면 유용합니다.

먼저, MDX를 사용하여 기존에 우리가 만들었던 스토리의 Docs 페이지를 커스터마이징 하는 방법을 알아보겠습니다.

우선 src/Hello 디렉터리에 Hello.mdx 라는 파일을 새로 생성하세요.

mdx 확장자는 VSCode 확장 프로그램을 설치해야 코드에 Syntax Highlighting이 됩니다. (다른 에디터를 사용하신다면 관련 확장 프로그램이 있는지 한번 따로 검색해보세요)

image.png

src/Hello/Hello.mdx

import Hello from './Hello';
import {
  Story,
  Props,
  Description,
  Preview
} from '@storybook/addon-docs/blocks';

# Hello

<Hello name="MDX" big={true} />
<Description of={Hello} />

## Props

이 컴포넌트에서 사용되는 Props 들을 볼까요?

<Props of={Hello} />

## 그냥 조그마한 Hello

<Preview>
  <Story id="components-basic-hello--standard" />
</Preview>

그냥 조그마한 Hello 입니다.

## 커다란 Hello

커다란 Hello를 보여주고 싶으면 `big` props 를 `true` 로설정하시면 됩니다.

<Preview>
  <Story id="components-basic-hello--big" />
</Preview>

MDX를 사용하면 이렇게 컴포넌트들을 마크다운 내부에서 바로 렌더링 할 수 있답니다.

'@storybook/addon-docs/blocks' 안에 들어있는 Block 컴포넌트를 사용하면 기존에 자동생성된 DocsPage 에서 보여줬었던 것 처럼 컴포넌트의 설명 및 Props 정보를 추출하여 보여줄 수 있습니다. 그리고, 우리가 사전에 만든 Story 들을 DocsPage안에서 보여줄 수도 있죠. 기존에 만들었던 Story 를 보여줄 때에는 Story 컴포넌트에 id props 를 설정해주어야 합니다. 이 id 값은 Storybook에서 Story를 선택했을 때 주소창에서 확인 할 수 있습니다.

image.png

파일을 다 작성한 다음에는 기존에 만들었던 스토리 파일에서 docs 파라미터를 다음과 같이 설정해보세요.

src/Hello/Hello.stories.js

import React from 'react';
import Hello from './Hello';
import { withKnobs, text, boolean } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import mdx from './Hello.mdx';

export default {
  title: 'components|basic/Hello', // 스토리북에서 보여질 그룹과 경로를 명시
  component: Hello, // 어떤 컴포넌트를 문서화 할지 명시
  decorators: [withKnobs], // 애드온 적용
  parameters: {
    componentSubtitle: '"안녕하세요"라고 보여주는 컴포넌트',
    docs: {
      page: mdx
    }
  }
};

mdx 파일을 불러와서 이렇게 파라미터쪽에 설정을 해주고 나면, Docs 페이지가 다음과 같이 커스터마이징 된 상태로 보여질 것입니다.

image.png

MDX로만 스토리 작성하기

이전 예시에서는 stories.js 파일도 만들고 mdx 파일도 만드는 방식으로 컴포넌트의 스토리 및 문서를 작성해주었습니다.

이번에는, stories.js 를 따로 만들지 않고 mdx 파일 내부에서 모든 작업을 하는 방법을 알아보도록 하겠습니다.

src 디렉터리에 Bye 라는 디렉터리를 새로 만들고, Bye.js 컴포넌트를 다음과 같이 작성해보세요.

src/Bye/Bye.js

import React from 'react';
import PropTypes from 'prop-types';

const Bye = ({ name }) => {
  return <p>안녕히 가세요, {name}</p>;
};

Bye.propTypes = {
  name: PropTypes.string
};

export default Bye;

그 다음에는 Bye.stories.mdx 파일을 같은 디렉터리에 만들어서 다음과 같이 작성해주세요.

src/Bye/Bye.stories.mdx

import { Meta, Story, Props, Preview } from '@storybook/addon-docs/blocks';
import Bye from './Bye';
import { withKnobs, text, boolean } from '@storybook/addon-knobs';

<Meta title="components|basic/Bye" component={Bye} decorators={[withKnobs]} />

# Button

<Preview>
  <Story name="Default">
    <Bye name={text('name', 'stories.js')} />
  </Story>
</Preview>

## Props

<Props of={Bye} />

Meta Block 을 사용하면 기존에 우리가 CSF 형태로 스토리를 작성 할 때 넣어주는 값들을 설정 할 수있습니다. 이를테면 title, component, decorators , parameters 가 있죠.

어떤 상황에 MDX를 사용해야 할까?

Docs 애드온을 통하여 자동으로 만들어진 문서에서 제공하는 정보로는 컴포넌트의 기능을 자세하게 표현하기 어려운 상황에 MDX를 사용하시면 됩니다. (대부분의 경우, 자동으로 생성되는 문서만으로도 충분 할 때가 많습니다.)

추가적으로, 컴포넌트가 아닌 문서를 작성해야 하는 상황에는 (예: Introduction, Colors, Typography 등..) MDX-only로 작성하시면 됩니다.

컴포넌트에 대한 MDX를 작성하실 때에는 MDX-only로 문서를 작성하는 것을 저는 권장하지 않습니다. 그 이유는 나중에 TypeScript를 사용하게 된다면 IDE에서 .mdx 확장자에 대한 TypeScript 지원이 제대로 이루어지지 않기 때문입니다.