12周年記念パーティ開催! 2024/5/10(金) 19:00

試してみよう、MDX 第1回 MDXの主要な機能の概要

Markdown記法とJSXの両方を混在させることができるMDXフォーマットを解説します。まず、前編ではMDXフォーマットの概要を押さえましょう。

発行

著者 渡辺 由 フロントエンド・エンジニア
試してみよう、MDX シリーズの記事一覧

はじめに

MDXは、Markdownをコンポーネントとして扱うためのフォーマットです。MDXフォーマットでは、Markdown記法とJSXの両方を利用できます。Next.jsGatsbyAstroなどのMarkdownファイルからWebサイトを構築できるフレームワークとも非常に相性の良い技術です。

本記事では、MDXの概要や機能と、フレームワークへの導入方法についてお話しします。

MDXでできること

MDXを利用すると、Markdownのみでは困難なリッチな表現が可能になります。たとえば、ドキュメントの中にインタラクティブなグラフやチャートを入れる、複雑なマークアップやスタイルを適用したコンテンツを挿入する、などです。

MDXの公式サイトのトップページにも、チャートを挿入する例が載っています。

昨年の降雪量のチャートを挿入するMDX

import {Chart} from './snowfall.js'
export const year = 2018

# Last year’s snowfall

In {year}, the snowfall was above average.
It was followed by a warm spring which caused
flood conditions in many of the nearby rivers.

<Chart year={year} color="#fcb32c" />

この短いコードからも、Markdown記法とJSXを混ぜて記述できる、というイメージが何となく伝わるでしょうか。MDXではこのように、MarkdownとJSXを自由にミックスして記述できます。また、そのようにしてできたMDX自体もコンポーネントなので、再利用したり入れ子にしたり、ということが可能です。

JSXとコンポーネントが使えるということ

もう少し掘り下げてみます。JSXやコンポーネントが扱えることのメリットとして大きいのは次のような点です。

  • JavaScriptが使える
  • コンポーネントにはpropsが渡せる
  • コンポーネント単位での再利用ができる

JSが使えれば、変数を定義したり、マウスイベントを扱ったり、といったことが可能になります。

また、MDXでは、JSXだけでなく、Markdownで書かれた文書も「コンポーネント」として扱えます。Markdownフォーマットのみで複雑なマークアップやスタイリングを実現するには、HTMLで記述する、Markdownを独自拡張する、といった方法もあります。が、MDXであれば、コンポーネントとして扱うことで同じマークアップの再利用ができたり、propsによって一部のスタイルを変化させる、中身のテキストだけを変える、ということも容易にできます。

デメリットはないの?

もちろんデメリットがないわけではありません。MDXの変換処理はMarkdownの変換処理よりも複雑なものですし(詳細は後述します)、エスケープが必要な文字があるなど、文法上、気をつかう点もMarkdownよりは増えてしまう印象です。また、JSXやコンポーネントを多用する必要があるのであれば、そもそもMDXではなく、すべてJSXにしてしまったほうが扱いやすいかもしれません。

MarkdownとJSXの合体は強力な機能ですが、コンテンツを作成する上で本当にコンポーネントやJSXが必要か、Markdown記法主体で記述できるかどうかなどを考慮した上で導入するのが良いでしょう。

MDXの仕様とバージョン

MDXが登場したのは2018年で、その後2019年に安定版のバージョン1がリリースされ、2022年にはバージョン2になりました。バージョン1から2へのメジャーアップデートでは、記述ルールの整理やコンパイラの改善により、パフォーマンスが向上しています。これからMDXを導入するプロジェクトではバージョン2を採用するのが良いでしょう。

逆に、すでにバージョン1を利用しているケースでは、既存のコンテンツの書き換えや、ライブラリの追加が必要になる可能性がありますので、アップデートには留意が必要です。

バージョン2での変更点

バージョン2での主だった変更点は以下のようなものです。

  • HTMLコメント(<!-- -->)ではなくJSXのコメント({/* */})を使う
  • インラインスタイル(<Component style="background-color: #000;">...</Component>)はJSオブジェクト(style={{backgroundColor: '#000'}})で記述する
  • 文字として{}を記述する場合にはエスケープする(\{\}
  • デフォルトではGFM(GitHub Flavored Markdown)が利用できなくなった
    • 利用する場合には、別途remark-gfmパッケージが必要
    • テーブル記法などもGFMに含まれる

コメントやスタイルは元からJSX記法で書いている、テーブルも使っていない、といった場合は、そのままバージョン2でもコンパイルできるかもしれません。バージョン2では、いわば「記法がバージョン1より厳密になった」というイメージで捉えると良いです。

詳しくは、公式のマイグレーションガイドを参照ください。

MDXを書いてみる

それでは、さっそくMDXを書いてみましょう。次のようなコンテンツを作ります。ランキングのテーブル部分は独立したコンポーネントにして、再利用可能にします。

お気に入り数ランキング

CodeGridでは毎月末に、月間のお気に入り数ランキングをツイートしています。

まずはテーブル部分のコンポーネントのコードです。JSXで記述しています。1位、2位、3位はそれぞれ文字に色をつけました。

RankingTable.jsx

export const RankingTable = ({month, rankingData}) => {
  const fontColor = {1: 'gold', 2: 'silver', 3: 'brown'}
  return (
    <table>
      <caption>{month}のお気に入り数ランキング</caption>
      <tbody>
        {rankingData.map((item, index) => {
          return (
            <tr key={item.slug}>
              <th style={{color: fontColor[index + 1] || 'currentColor'}}>
                {index + 1}位
              </th>
              <td>{item.title}</td>
            </tr>
          )
        })}
      </tbody>
    </table>
  )
}

続いてこのコンポーネントをMDXに埋め込みます。

OctoberRanking.mdx

import {RankingTable} from './RankingTable.jsx'

export const rankingData = [
  {slug: '2022-ogp-image', title: '攻略、OGP画像作成'},
  {slug: '2022-css-full-width-background', title: '画面幅いっぱいに背景色を敷くCSS実装'},
  {slug: '2022-astro', title: '今すぐ始めるAstro入門'},
  {slug: '2021-github-actions', title: 'GitHub Actionsで効率的な作業を目指す'},
  {slug: '2021-nextjs', title: 'これから始める、Next.js'}
]

## 今月の注目記事の紹介

今月、お気に入り追加数の多かった記事ランキングの発表です。

<RankingTable month="2022年10月" rankingData={rankingData} />

### 新登場の記事
今月新登場だった記事は...(続)

JavaScriptのimport文や変数の宣言、JSXで書かれたコンポーネント、それに日本語の文章が入り乱れて書かれています。MDXを使い始めた頃は、筆者も何やら落ち着かない気分になりましたが、これで大丈夫です。

こうしてできたMDXのコードは、実際にはWebフレームワークなどでビルドをして、Webページとして生成されるわけですが、公式サイトのPlaygroundではそのような手間をかけずにレンダリング結果を確認できます。次節で使い方を紹介します。

Playgroundの使い方

Playground上ではJSXファイルをimportすることはできないので、「Editor」タブにJSXとMDXの両方を入力します。まずJSXをペーストし、続けてimport {...の行を除いたMDXもペーストしてください。問題がなければEditorの下の「Run」のタブにレンダリング結果が表示されます。背景色などはPlaygroundサイトデフォルトのCSSが効いていますが、前節の画像のような表示になったでしょうか?

コンパイルのしくみ

MDXはどのように変換されているのでしょうか。「Run」の隣の「Compile」タブを覗いてみると、少し雰囲気がわかるかもしれません。中身はJavaScriptのコードです。プレーンなMarkdownと異なり、JSXを含んでいますので、いったんMDXコンテンツ全体をJavaScriptのコードに変換しているのです。

propsとカスタムコンポーネント

MDXファイルは、それ自体もコンポーネントとして扱えるようになっています。先ほどのOctoberRanking.mdxファイルのコンテンツを別のファイルから呼び出せるのです。

Page.mdx

import OctoberRanking from 'OctoberRanking.mdx'

<OctoberRanking />

これだけだと、importしたものをそのまま表示するだけですが、この<OctoberRanking />はコンポーネントなのでpropsを渡せます。たとえば、何位まで表示するかを<OctoberRanking maxRank={3} />のように指定するといった実装も可能です。

もう一点、MDXコンポーネントはcomponentsという特別なpropsを受け取ることができます。componentspropsにはオブジェクトを指定します。

components propsの利用

<OctoberRanking components={{
    h2: 'h3',
    strong: (props) => <strong style={{color: 'red'}} {...props} />
  }}
/>

何が起きるか想像できるでしょうか? こうすると、h2としてマークアップしたもの(つまり## 見出しのようなMarkdown)は最終的に<h3>見出し</h3>に変換されます。<strong>に変換される**太字**も、インラインのスタイルが適用され、赤い文字になるでしょう。

この機能は非常に強力です。フレームワークと組み合わせれば、すべてのコンテンツのすべてのh2にアイコンをつける、というような処理も容易にできます。この機能に関しては、次回、詳しく取り挙げる予定です。

プラグインの利用

MDXは、内部的にMarkdownを処理する実装として、remarkを採用しています。そのため、公開されているremarkプラグインを導入することもできますし、様式に従って自作することもできます。remarkプラグインを利用することで、MDXコンテンツ内でfrontmatterを扱えるようにしたり、Markdown部分を任意に加工したりできます。

これらの機能についても、次回取り挙げます。

ここまでのまとめ

MDXの概要と、コンテンツの作成・コンパイルについて見てきました。実際にサイト制作でMDXを利用する場合には、なんらかのフレームワーク(Next.js、Gatsby、AstroなどがMDXをサポートしています)とともに使うことになります。次回はNext.jsを例に、より実践的な利用方法を紹介します。