LoginSignup
0
1

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-11-19

何をしたかったか?

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

状況設定

自分が最近作成した個人サイトでは、バックエンドにLaravelを使用し、フロントエンドにReactを使用しています。この2つをInertiaを用いて接続しています。
MarkdownはYaml Frontmatterで管理しており、たとえば以下のような構造になっています。そして、その中にReactコンポーネン(以下ではAmazonコンポーネント)が含まれています。

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の情報は、別途文字列としてフロントエンド側に渡されます。
フロントエンド側に渡される文字列は以下のようになります。

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かと思います。しかしながら、現在、このライブラリでは、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を使用する必要があります。ちょっとめんどくさいですが、仕方ありません。

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は初心者なので、こうした方がいいなどあれば教えて欲しいです!

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1