v1.6.0リリースノート: MDXコンポーネントの強化

この記事はNext.js版の内容です。現在はAstroで構築し直したため、情報が古い可能性があります。 当時のリポジトリは こちらあるので参考にしてください。

今回のアップデートではなくても良いけど、あったら嬉しい機能を追加した。

このブログは MDX使って書かれており、いくらでもJavaScriptのコードを埋め込める。 便利な機能ではあるのだが、GitHubでコンテンツを保存しているという背景もあり、できるだけ素のマークダウンファイルか GFM形式を崩さない程度に実装してきた。 そうすればいつの日かホスティングを止めてもGitHubから自然言語に近い形で記事を閲覧できる。

GitHubで見ると このような感じ表示される。

ただ、それらの形式で表現するには限界があるのでJSXコンポーネントを意味的に理解しやすい形で実装することにした。 意味的に理解しやすいの定義はコンポーネント名と属性を見るだけで何をしているのかがレンダリングされる前の状態でも推測できるということ。

Google Maps

index.mdx
<GoogleMaps placeId='ChIJ2-L1uEhbFkcR-Zek84Ap7zI' caption='Google Maps の下にはキャプションを付けられる' />

これから旅行関係の記事も記録として書いていきたいので一目で場所を理解できるGoogle Mapsは取り入れたかった。

幸いにもNext.jsがGoogle Mapsの埋め込みをサポートする ライブラリ作成していたためそれをラップする形で利用した。

GCPのAPIキーが必要とのことで、しかしてAPIの呼び出し制限がある?と思ったが、Maps Embed APIは無料かつ無制限で呼び出せたソース)。 他のAPIはリクエスト数に応じた従量課金制であるため、もしAPIキーが流出した場合を考えて他のAPIは使えないように設定した。

地図のマーカーを一意に設定するためにplaceIdいうGoogle Maps固有のIDを使用している。 旧オスカー・シンドラーのホーロー工場のように名称を入れてもマーカーを設定できるが、それがユニークである保証はないため、少し面倒だがplaceId入れるようにしてある。

placeId取得は下記のページから行える。

また、captionプロパティに文字列を入れるとコンポーネントの下に説明文を挿入できる。 もちろん設定しなくてもよし。

YouTube

index.mdx
https://www.youtube.com/watch?v=eZCSOdi19jQ

YouTubeを記事に埋め込むことで、文章だけでは伝えきれない内容を視覚的に訴えられるので好きだ。 また、読者が動画を再生したら必然的に滞在時間が長くなり検索エンジンからの評価も上がる。 基本的にメリットしかないので実装するしかない。

これもGoogle Mapsと同様にNext.jsの 便利なライブラリあったためラップして使用している。 本来YouTubeの埋め込みは大量のデータを読み込むため、かなりLCPスコアを悪化させてしまう。

Next.jsのライブラリでは内部的に lite-youtube-embed使うことで通常の画像読み込みと遜色ないパフォーマンスを実現している。

lite-youtube-embedがやっていることは非常にシンプルで、動画の代わりにサムネイル画像を最初に読み込む。 動画の再生はユーザがサムネイル画像をクリックしたときのみ開始されるため余計なリクエストが行われないという訳だ。

ただ、デメリットとしてはサムネイル画像が角丸だったりするとUIと合わずに違和感が出てしまう。 これは避けようがない問題なので、そういった動画に当たらないことを願うしかない。

MDX内ではYouTubeのURLを入力するだけでビルド時にvideoId抜き出してコンポーネントに属性として渡している。 便利な方法なのだが、キャプションを追加できないのが少し引っかかっているためGoogle Mapsと同じように<YouTube />のようなコンポーネントに変えるかもしれない。

あと、動画再生時は必ずミュートでスタートするようにしてある(大事なポイント)。

X (Twitter)

index.mdx
https://twitter.com/jack/status/20

X(前Twitter)ではよくIT技術関係のポストを見ており、稀に興味深くて記事で言及したいものがあったりする。 そのときに引用する形で埋め込めたら便利だなと思ったため実装してみた。

内部的には react-tweetいうVercel製のライブラリを使用している。 Vercelが作っているだけあって非常に使い勝手が良く、ポストを埋め込みたい場合はおすすめだ。

特にライトモードとダークモードのスタイリングの切り替えを勝手にしてくれたのは驚いた。

脚注

index.mdx
Here is a simple footnote[^1].
A footnote can also have multiple lines[^2].
[^1]: My reference.
[^2]: To add line breaks within a footnote, add `<br />`.<br />This is a second line.

技術記事ではソースが非常に重要になってくる。 そのときに本文内で言及すると冗長な印象を与えてしまうため脚注を追加できるようにした。

この書き方自体はGitHubのマークダウンファイルでサポートされている1 remark-gfm脚注のHTMLは生成されているため、今回はそのスタイリングのみ行った。

アコーディオン

マルチプル

index.mdx
<Accordion collapsible>
<AccordionItem value='faq-1'>
<AccordionTrigger>
グレン・グールドはどのような病気で亡くなりましたか?
</AccordionTrigger>
<AccordionContent>
グールドはカナダのピアニスト(クラシック)。
1932 年に生まれ、1982 年に 50 才で亡くなった。
なので今年 2012 年はグレン・グールド生誕 80 年で没後 30 年にあたる。
彼は生前に発達障害と診断されることはなかったが、英文の Wikipedia にもあるように自閉スペクトラム症ではないかといわれている。
</AccordionContent>
</AccordionItem>
<AccordionItem value='faq-2'>
<AccordionTrigger>
グレングールドの特徴は?
</AccordionTrigger>
<AccordionContent>
グールドを語るうえで欠かせないのが、奇人とでも言えそうな存在感だ。
その特徴は、ピアノを聴きながら口ずさむ鼻歌(録音でもはっきり聴こえる)を筆頭に、父親手作りの極端に低い骨組みだけのピアノ椅子。
こちらは折りたたみ式で4本の足の長さが調整可能というキワモノだ。
</AccordionContent>
</AccordionItem>
<AccordionItem value='faq-3'>
<AccordionTrigger>
グレン・グールドが使用したピアノは?
</AccordionTrigger>
<AccordionContent>
グールドは一九八一年四月、三〇丁目の録音スタジオで「ゴルトベルク変奏曲」を録音した。
演奏に使ったピアノはスタインウエイ社のピアノであるはず。
これは疑う余地もないと思っていた。
</AccordionContent>
</AccordionItem>
<AccordionItem value='faq-4'>
<AccordionTrigger>
グールドの最後の録音は?
</AccordionTrigger>
<AccordionContent>
最後のピアノ録音は、リヒャルト・シュトラウス「ピアノ・ソナタOp.5」(録音日時 1982 年 7 月 2 日及び 9 月 1-3 日)であり、その後同年 9 月 8 日にはリヒャルト・ワーグナーのジークフリート牧歌をトロント交響楽団で指揮・録音しており、グールドはスタジオ録音においてピアノ奏者としてではなく、指揮者として人生を終えている。
</AccordionContent>
</AccordionItem>
</Accordion>

記事内のレイアウトを整理するのに便利そうなアコーディオンコンポーネントを実装した。 複数か単数かでUIを分けている。

シングル

index.mdx
<Details summary='バルトークの曲の特徴は?'>
バルトークは管弦楽曲、ピアノ曲、歌曲などに多くの作品を残しているが、最も有名なのは 43 年作の「管弦楽のための協奏曲」と、6 曲から成る「弦楽四重奏曲」である。
作風は異国旋法、原始主義、民族主義、新古典主義を豊かな情感でまとめ、不協和音の中にマジャール(ハンガリー)民族の民謡とリズムが感じとれる革新的な音楽になっている。
</Details>

どちらかというとシングルの方が使う頻度は高そう。

HTMLにはdetails 2いうタグがあり、JavaScriptを使わずともブラウザ側で折りたたみウィジェットを作成できる。 以下のように使用する。

<details>
<summary>バルトークの曲の特徴は?</summary>
バルトークは管弦楽曲、ピアノ曲、歌曲などに多くの作品を残しているが、最も有名なのは 43 年作の「管弦楽のための協奏曲」と、6 曲から成る「弦楽四重奏曲」である。
作風は異国旋法、原始主義、民族主義、新古典主義を豊かな情感でまとめ、不協和音の中にマジャール(ハンガリー)民族の民謡とリズムが感じとれる革新的な音楽になっている。
</details>

ただ、デフォルトだといまいち見た目が好きではないため独自のコンポーネントを実装した。 コンポーネント名と属性名は<details>タグに基づいている。

アラート

index.mdx
<Alert type='note' description='情報量の多いページで、ユーザが重要な情報を見逃さないようにするために使用する。記事全体の概要を簡単に伝えたい場合にも使用できる。' />
<Alert type='tip' description='ユーザが役立つと感じる情報を提供するために使用する。記事の内容をより深く理解するためのヒントやコツなどを提供する。' />
<Alert type='important' description='ユーザが絶対に知っておくべき重要な情報を伝えるために使用する。警告や注意喚起などにも使用できる。' />
<Alert type='warning' description='ユーザが危険な状況を回避するために必要な情報を伝えるために使用する。緊急性の高い警告などにも使用できる。' />

とりあえず4種類のアラートを実装した。 これから増える可能性もある。

GitHubがドキュメントでアラートのMarkdown拡張を最近追加した3が、最初はその構文に基づいた書き方にしようとしていた。 しかし、ブロッククォートを解析してコンポーネントに変換するのはけっこう面倒なのでシンプルにJSXで書けるようにした。

それに転載・引用元を示す構文でアラートを表示するのは意味論的に微妙な実装だと思う。

タブ

index.mdx
<Tabs defaultValue='tab-1'>
<TabsList>
<TabsTrigger value='tab-1'>タブ 1</TabsTrigger>
<TabsTrigger value='tab-2'>タブ 2</TabsTrigger>
</TabsList>
<TabsContent value='tab-1'>
コンテンツ 1
</TabsContent>
<TabsContent value='tab-2'>
コンテンツ 2
</TabsContent>
</Tabs>

この記事でも多用しているが、要素を1画面で表示してしまいたいときにタブは便利だ。

ステップ

index.mdx
<Steps>
<Step>野菜を炒める</Step>
1. 玉ねぎはみじん切り、じゃがいもは大きめの一口大、人参は乱切りにする
2. 鍋にサラダ油を熱し、玉ねぎを炒める
3. 玉ねぎがしんなりしたら、豚肉を加えて炒める
4. 豚肉に火が通ったら、じゃがいも、人参を加えて炒める
<Step>煮込む</Step>
1. 水を加えて、沸騰したらアクを取り除く
2. 弱火で 20 分ほど煮込み、野菜が柔らかくなったら火を止める
<Step>ルーを入れる</Step>
1. カレールーを割り入れて溶かし、弱火で5分ほど煮込む
2. 塩こしょうで味を調えて完成
</Steps>

何かを作るときは必ずステップが存在する。 そのプロセスを説明するときに便利なコンポーネントを追加した。

<Step>タグで囲んだ文字列は<h3>タグとしてレンダリングされる点に注意が必要。

カルーセル

index.mdx
<Carousel>
![干潮時の厳島神社](/images/tech/2024-03-16/01.jpg)
![久住高原](/images/tech/2024-03-16/02.jpg)
![桜島からの風景](/images/tech/2024-03-16/03.jpg)
</Carousel>

最後にカルーセルコンポーネントを追加した。 今のところ以下の制約がある。

  • 画像のみ対応している
  • 全ての画像を同じ比率に揃える必要がある

コードでこれらの制約を表現できていないため、まだWIPの機能となる。 とはいえ、カルーセルにこれ以上の機能を求めるべきかというのは疑問だし、ビルド時にバリデーションを追加して違反していれば例外を投げる対応が現実的かもしれない。

表示されている画像以外の不透明度を15% にしているのと現在のページ数を表示しているのがポイント。 通常の画像に比べてカルーセルだと画像サイズが小さくなるのはデメリットだが、画像の拡大表示をこれから実装すれば問題なさそう。

さいごに

独自コンポーネント満載のこのページを見返してみたら雑多な感じがしてちょっと微妙に感じてしまった。 1つ1つのコンポーネントのレイアウトやデザインを修正することで対応できれば一番良いが、研究が必要なのでしばらく時間がかかる。

そのため、記事を書く際はこれらのコンポーネントにできるだけ頼ることなくシンプルに書いていきたい。


  1. 基本的な書き方とフォーマットの構文: 脚注記載。 ただし、GitHubでは半角スペースを2つ入れることで改行できたがremark-gfmでは対応していないため<br />タグの追加が必要になる。

  2. MDNドキュメント: 詳細折りたたみ要素

  3. New Markdown extension: Alerts provide distinctive styling for significant content