mdxを用いて文字列として表現されたMarkdownに埋め込まれたreactコンポーネントをダイナミックに変換する

## 何をしたかったか？

今回やりたかったことは、フロントエンド側に文字列として渡されたMarkdownの中のReactコンポーネントをフロントエンド側でうまく表示することです。色々と検討した結果、Markdownの中にReactコンポーネントに組み込める[mdx](https://mdxjs.com/)を使用することにしました。mdxは便利なラブラリですが、文字列で表現されたMarkdownをうまく変換するには、気をつけないといけない点がいくつかあったので、ここで共有しておきます。

## 状況設定

[自分が最近作成した個人サイト](https://reirev.net/blog)では、バックエンドに[Laravel](https://laravel.com/)を使用し、フロントエンドに[React](https://reactjs.org/)を使用しています。この2つを[Inertia](https://inertiajs.com/)を用いて接続しています。
Markdownは[Yaml Frontmatter](https://jekyllrb.com/docs/front-matter/)で管理しており、たとえば以下のような構造になっています。そして、その中にReactコンポーネン(以下ではAmazonコンポーネント)が含まれています。
```markdown:example.md
---
title: これはタイトルです
excerpt: これは要約です。
date: 2022-11-20
---
## Lorem Ipsum
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 
<Amazon/>
```
Yaml Frontmatterの情報は、バックエンド側で処理し、フロントエンドには以下の文字列のみを渡します。Yaml Frontmatterの情報は、別途文字列としてフロントエンド側に渡されます。
フロントエンド側に渡される文字列は以下のようになります。
```markdown:example.md(フロントエンドに渡される文字列)
## Lorem Ipsum
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 
<Amazon/>
```

## なぜmdxを使うのが良いか？

markdownに埋め込まれたReactコンポーネントを処理する方法はいろいろあります。たとえば、Markdownをhtmlに変換するライブラリとして最も使い勝手がいいのは、[react-markdown](https://github.com/remarkjs/react-markdown)かと思います。しかしながら、現在、このライブラリでは、Markdownに埋め込まれたReactコンポーネントを変換する機能が備わっておらず、独自にその機能を実装する必要があります。
そこで、例えば[以下のような記法](https://github.com/remarkjs/react-markdown/issues/218)が提案されています。

>\# title
>
>\`\`\`emoji
>Specially processed stuff, use react in your custom renderer to handle it.
>\`\`\`

```react
const renderers = {
  code: ({ language, value }) => {
    if (language === 'emoji') {
      return <Emoji text={value} />
    }
    const className = language && `language-${language}`
    const code = React.createElement('code', className ? { className: className } : null, value)
    return React.createElement('pre', {}, code)
  }
}

const Text = ({ text, className }) => (
  <ReactMarkdown source={text} renderers={renderers} />
);
```

要するに、\`\`\`の横にコンポーネント名を書いておくという方法です。ただ、わざわざ\`\`\`の横にコンポーネント名を書いておくのって、気持ち悪いですよね。。。なので、この方法は採用したくありませんでした。

## mdxとは何か？何に注意する必要があるか？

[mdx](https://mdxjs.com/)は、Next.jsも開発しているVercelが作成したファイル拡張子、およびライブラリです。これを使用すると、Reactコンポーネントをいい感じでMarkdownに埋め込み、Reactコンポーネントを変換することができます。

使用方法は[公式のガイド](https://mdxjs.com/guides/)に色々と書かれていますし、ネットで調べればいろいろと情報は出てきます。ただし、ほとんどの情報は、Static generationを前提としたものであり、動的にReactコンポーネントに変換する情報は見つかりませんでした。

注意点は、mdxのライブラリがstatic generationを前提としていることです。そこで、ダイナミックにファイルを変換するためには、以降で解説するようにコーディングする必要があります。

## コーディング方法

まずはmdxをインストールしておきます。

```
npm install @mdx-js/mdx
```

mdxの諸々のライブラリは、非同期関数になっているので、以下のようにuseStateを使用する必要があります。ちょっとめんどくさいですが、仕方ありません。

```react:Post.jsx
import { useState, useEffect } from "react";
import { run, compile } from "@mdx-js/mdx";
import * as runtime from "react/jsx-runtime";
// ここではAmazonコンポーネントが埋め込まれているとする。
import Amazon from "Amazon";

const components = {
    Amazon,
};

export default function Post({ body }) {
    const [state, setState] = useState();
    useEffect(() => {
        compile(body, {
            format: "mdx",
            outputFormat: "function-body",
        }).then((content) => {
            run(content, runtime).then((content) => {
                const { default: Content } = content;
                setState(
                   <Content components={components}></Content>
                );
            });
        });
    }, []);

    return state;
}

```

## 最後に

javascriptは初心者なので、こうした方がいいなどあれば教えて欲しいです！