(마지막에 컴포넌트 공유함)
MDX 확장자는 마크다운(MD)과 JSX가 결합되어 마크다운 컨텐츠를 리액트 내에서 컴포넌트 형태로 export하거나, JSX컴포넌트를 MDX파일 내에 import 할 수 있도록한다. 정적 컨텐츠를 컴포넌트화 시키는 특성 때문에 SSG를 지원하는 프레임워크(Gatsby, Next)에서는 MDX를 위한 플러그인이 잘 지원되고 있다.
이 글에서는 그 중 NextJS의 MDX 플러그인을 통해 마크다운 컨텐츠를 스타일링 하는 법을 다룬다.
npm install @next/mdx @mdx-js/loader @mdx-js/react
NextJS의 기본적인 MDX 플러그인들을 설치 후
// next.config.js
const withMDX = require('@next/mdx')({
extension: /\.mdx?$/,
options: {
// If you use remark-gfm, you'll need to use next.config.mjs
// as the package is ESM only
// https://github.com/remarkjs/remark-gfm#install
remarkPlugins: [],
rehypePlugins: [],
// If you use `MDXProvider`, uncomment the following line.
// providerImportSource: "@mdx-js/react",
},
})
/** @type {import('next').NextConfig} */
const nextConfig = {
// Configure pageExtensions to include md and mdx
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
// Optionally, add any other Next.js config below
reactStrictMode: true,
}
// Merge MDX config with Next.js config
module.exports = withMDX(nextConfig)
next config 를 다음과 같이 설정해주면, mdx 확장자의 마크다운 구문을 html태그(JSX)로 변환하는 작업이 가능해진다.
# h1
## h2
같은 구문이 있다면
<h1>h1</h1>
<h2>h2</h2>
로 변환되는 식이다.
이런 변환과정에 디테일하게 관여하기 위해 remark(md to html) rehype(html to md) 플러그인을 사용할 수 있다. NextJS로 만들어진 tailwindCSS 홈페이지의 소스코드를 보면 직접 작성된 플러그인 예시를 볼 수 있다. 솔직히 너무 복잡하여 참고만...ㅠㅠ
mdx 확장자는 별도의 export문 없이도 하나의 mdx컴포넌트로 default export된다. JSX 내에서 JSX 구문처럼 선언할 수 있다. 이는 'mdx/types' 모듈에서 살펴보면 MDXContent로 정의되고 있다.
/**
* The props that may be passed to an MDX component.
*/
export interface MDXProps {
/**
* Which props exactly may be passed into the component depends on the contents of the MDX
* file.
*/
[key: string]: unknown;
/**
* This prop may be used to customize how certain components are rendered.
*/
components?: MDXComponents;
}
/**
* The type of the default export of an MDX module.
*/
export type MDXContent = (props: MDXProps) => JSX.Element;
MDXContent 타입은 props로 components라는 객체를 받아 mdx확장자에서 변환되는 태그와 매핑할 수 있다.
// index.mdx
# h1
## h2
같은 파일을 JSX에서 다음과 같이 불러와
import Content from 'index.mdx';
const components = {
h1: //jsx component
h2: //jsx component
}
function page(){
return(
<Content components ={components}/>
)
}
h1, h2 태그에 대응할 컴포넌트를 직접 매핑해줄 수 있다는 것이다. 하지만 import하는 mdx 컨텐츠가 여러개인 경우 일일이 components를 props로 집어넣어 매핑해줘야하는 불편함 때문에 context api를 통해 구현된 MDXProvider를 사용한다.
모든 페이지 컨텐츠에 MDX가 사용된다면 _app.tsx 파일 하나에 MDXProvider사용을 생각해볼 수도 있겠으나, 범용성을 위해 따로 MDXProvider가 적용된 layout 컴포넌트를 만드는게 좋을 것이다.
마크다운을 위해 css를 별도로 작성하는 건 부담스러운 일일 수 있다. 깃허브의 마크다운 스타일과 동일하게 제공하는 듯한 npm 패키지가 있어 추천한다. (github-markdown-css)
npm install github-markdown-css
설치 후,
상위 컴포넌트의 className으로 "markdown-body"만 넣어주면 나머지 태그들은 깃허브 스타일로 깔끔하게 정돈된다. MDXProvider의 아래 div태그를 넣고 거기 달아주면 될 것 같다.
MDXProvider에 제공할 수 있는 컴포넌트는 img, h1, h2, p, pre, code 등 다양하다. img에 제공할 컴포넌트는 NextJS Docs에서 이미 제공중이고, 문제가 되는건 코드블럭이다. 백틱 세 개(```)로 표현하는 코드블럭을 위한 컴포넌트는 아마 github-markdown-css 만으로는 만족스러운 스타일을 내기 힘들 것이다.
그래서
const components = {
code: // 여기 들어갈 컴포넌트
}
저기 들어갈 컴포넌트를 하나 만들어주려 한다.
여기서 code syntax Highlighting을 위해 prism-react-renderer 라이브러리를 활용했다.
npm install --save prism-react-renderer
라이브러리를 활용해 codeBlock 컴포넌트를 만드는법은 다양하다. 해당사이트에 여러가지 예시가 있으니 참고하면 좋다.
필자가 최종적으로 코드블럭을 위해 만든 컴포넌트는 다음과 같다. 파일명이나, 라인넘버는 없지만 추가하려면 적절히 응용하면 된다.
// components/mdxViewer/codeBlock.tsx
/* eslint-disable react/no-array-index-key */
import React from 'react';
import Highlight, { defaultProps, Language } from 'prism-react-renderer';
import duotoneLight from 'prism-react-renderer/themes/duotoneLight';
interface CodeBlockProps{
children: string;
className: string;
}
export default ({ children, className }:CodeBlockProps) => {
const language = className.replace(/language-/, '');
return (
<Highlight
{...defaultProps}
theme={duotoneLight}
code={children}
language={language as Language}
>
{({
// eslint-disable-next-line no-shadow
className, style, tokens, getLineProps, getTokenProps,
}) => (
<pre className={className} style={{ ...style }}>
{tokens.map((line, i) => (
<div key={i} {...getLineProps({ line, key: i })}>
{line.map((token, key) => (
<span key={key} {...getTokenProps({ token, key })} />
))}
</div>
))}
</pre>
)}
</Highlight>
);
};
// components/mdxViewer/index.tsx
import React from 'react';
import { MDXProvider } from '@mdx-js/react';
import { MDXComponents } from 'mdx/types';
import CodeBlock from './codeBlock';
import 'github-markdown-css';
interface MDXProps{
children: React.ReactNode;
}
const components = {
code: CodeBlock,
img: // 이미지는 생략합니다.
};
export default function MDXLayout({ children }:MDXProps) {
return (
<>
<style jsx>
{`
.markdown-body{
padding: 20px;
}
`}
</style>
<MDXProvider components={components as MDXComponents}>
<div className="markdown-body">
{children}
</div>
</MDXProvider>
</>
);
}
다음 MDXLayout 컴포넌트를
import React from 'react';
import MDXLayout from '@/components/mdxViewer';
import Content from '@/contents/progressbar.mdx';
export default () => (
<>
<MDXLayout>
<Content />
</MDXLayout>
<>
);
사용 시 둘러싸 사용하거나, mdx파일에서 직접 둘러싸 export 하는 등의 방법이 있다. 굳이 따지면 후자가 좀 더 좋을 것 같다.
어쨌든 어느정도 완성도 있는 마크다운 뷰어를 직접(?) 구현했지만 여러 석연치 않은 점은 있다.
github-markdown-css를 사용한다면, .markdown-body라는 클래스 선택자의 자식으로 태그 선택자를 이용하기 때문에 mdx컨텐츠와 mdx컨텐츠 사이에 JSX 컴포넌트가 들어오려면
export default () => (
<>
<MDXLayout>
<Content />
</MDXLayout>
<MyJSXComponent>
<h1>아</h1>
<h2>이럴수가</h1>
</MyJSXComponent>
<MDXLayout>
<Content2 />
</MDXLayout>
<>
);
다음과 같이 Provider를 남발해야 할 수도 있다.
하지만 마크다운 뷰어의 직접 구현에 대한 자료가 너무 없는 것 같아 나의 해결법을 공유하고자 했다.
좋은 방법이 있다면 공유해주십사!
Pretty! This was a really wonderful post. Thank you for providing these details.
https://infocampus.co.in/ui-development-training-in-bangalore.html
https://infocampus.co.in/web-development-training-in-bangalore.html
https://infocampus.co.in/reactjs-training-in-marathahalli-bangalore.html
https://infocampus.co.in/mern-stack-training-in-bangalore.html
https://infocampus.co.in/javascript-jquery-training-in-bangalore.html
https://infocampus.co.in/data-structure-algorithms-training-in-bangalore.html
https://infocampus.co.in/angularjs-training-in-bangalore.html
https://infocampus.co.in/front-end-development-course-in-bangalore.html
너무 도움이 많이 됬습니다 😄 그리고 저는 prism-react-renderer 라이브러리도 좋지만, Bright도 추천드립니다!