何をしたかったか?
今回やりたかったことは、フロントエンド側に文字列として渡されたMarkdownの中のReactコンポーネントをフロントエンド側でうまく表示することです。色々と検討した結果、Markdownの中にReactコンポーネントに組み込めるmdxを使用することにしました。mdxは便利なラブラリですが、文字列で表現されたMarkdownをうまく変換するには、気をつけないといけない点がいくつかあったので、ここで共有しておきます。
状況設定
自分が最近作成した個人サイトでは、バックエンドにLaravelを使用し、フロントエンドにReactを使用しています。この2つをInertiaを用いて接続しています。
MarkdownはYaml Frontmatterで管理しており、たとえば以下のような構造になっています。そして、その中にReactコンポーネン(以下ではAmazonコンポーネント)が含まれています。
---
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の情報は、別途文字列としてフロントエンド側に渡されます。
フロントエンド側に渡される文字列は以下のようになります。
## 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かと思います。しかしながら、現在、このライブラリでは、Markdownに埋め込まれたReactコンポーネントを変換する機能が備わっておらず、独自にその機能を実装する必要があります。
そこで、例えば以下のような記法が提案されています。
# title
```emoji
Specially processed stuff, use react in your custom renderer to handle it.
```
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は、Next.jsも開発しているVercelが作成したファイル拡張子、およびライブラリです。これを使用すると、Reactコンポーネントをいい感じでMarkdownに埋め込み、Reactコンポーネントを変換することができます。
使用方法は公式のガイドに色々と書かれていますし、ネットで調べればいろいろと情報は出てきます。ただし、ほとんどの情報は、Static generationを前提としたものであり、動的にReactコンポーネントに変換する情報は見つかりませんでした。
注意点は、mdxのライブラリがstatic generationを前提としていることです。そこで、ダイナミックにファイルを変換するためには、以降で解説するようにコーディングする必要があります。
コーディング方法
まずはmdxをインストールしておきます。
npm install @mdx-js/mdx
mdxの諸々のライブラリは、非同期関数になっているので、以下のようにuseStateを使用する必要があります。ちょっとめんどくさいですが、仕方ありません。
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は初心者なので、こうした方がいいなどあれば教えて欲しいです!