Astroブログに吹き出し機能を追加!吹き出しコンポーネント実装

ブログを書く際に、以下のような吹き出しコンテンツを使いたくなる場面はないでしょうか?
自分は技術ブログで使うことはほぼないですが、ブログでレビュー記事などを書いている中で、吹き出しコンテンツを使いたくなる場面がありました。しかし、デフォルトの Markdown の記法の中で上記のような吹き出しコンテンツを表現する記法がなかったため独自の吹き出し記法を追加するために mdx で使えるコンポーネントを作成しました
本記事では、その吹き出しコンポーネントについてまとめています
コンポーネントではなくプラグインで吹き出しコンテンツを表示する方法も別途解説しているので、興味があれば読んでみてください!

mdx でコンポーネントを使う方法
Astro では mdx インテグレーションをインストールすると Astro コンポーネントや UI フレームワークコンポーネントを mdx ファイルで使用できるようになります。こちらについてはここでは詳しくは説明しないので、適宜 Astro Docs を参照してください
mdx ファイルにコンポーネントをインポートする方法はいくつかあって、まず公式 Doc で説明されている方法は各 mdx ファイルでコンポーネントをインポートして利用する方法です
---
layout: ../layouts/BaseLayout.astro
title: About me
---
import Button from '../components/Button.astro';
import ReactCounter from '../components/ReactCounter.jsx';
私は**火星**に住んでいますが、気軽に<Button title="お問い合わせ" />までご連絡ください。
以下は、MDXで動作するカウンターコンポーネントです。
<ReactCounter client:load />
しかし、この方法では mdx ファイルを作成するたびにコンポーネントをインポートしなければならず、少し手間です
そこで、次の選択肢に上がってくるのがコンテンツがレンダリングされる際に生成される <Content />
コンポーネントの引数にコンポーネントを渡す方法です。詳しくは Astro Docs を見ていただきたいのですが、この方法を使えば各 mdx ファイルでコンポーネントをインポートすることなくコンポーネントを使うことができるようになります
---
import { getEntry } from 'astro:content';
const entry = await getEntry('blog', 'post-1');
const { Content, headings } = await entry.render();
import Toc from "./components/Toc.astro";
---
<p>公開日: {entry.data.published.toDateString()}</p>
<Content components={{ Toc }} />
上記の方法でも良いと思いますが、個人的にお勧めしたいのは astro-auto-import という Astro のインテグレーションを使用する方法です。このインテグレーションを使うと、コンポーネントや他のモジュールを自動インポートしてくれるので、インポートせずに mdx ファイルでコンポーネントにアクセスできるようになります
設定は簡単で astro-auto-import をプロジェクトにインストールして astro.config.mjs ファイルで以下のように AutoImport をインポートして設定に追加するだけです。自分自身は imports オプションにファイル名を渡す運用をしています!
$ yarn add astro-auto-import
import { defineConfig } from 'astro/config';
import AutoImport from 'astro-auto-import';
import mdx from '@astrojs/mdx';
export default defineConfig({
integrations: [
AutoImport({
imports: [
'./src/components/Toc.astro'
],
}),
mdx(),
],
});
これで mdx ファイルでコンポーネントを利用できるようになるので、あとは吹き出しコンテンツ用のコンポーネントを実装していきます
吹き出しコンテンツの表現方法
吹き出しコンテンツは HTML に変換した際に以下のような構成になるように実装していきます
男性の吹き出しの場合は左側に男性のアイコン、右側に吹き出しを表示します。女性の吹き出しの場合は右に女性のアイコン、左側に吹き出しを表示します
<div class="bubble left">
<img src="/images/bubble-man.png" alt="bubble-man" width="80px" height="80px" loading="lazy">
<p>Astroブログに吹き出し機能を追加!吹き出しコンポーネント実装</p>
</div>
<div class="bubble right">
<p>Astroブログに吹き出し機能を追加!吹き出しコンポーネント実装</p>
<img src="/images/bubble-woman.png" alt="bubble-woman" width="80px" height="80px" loading="lazy">
</div>
吹き出しコンテンツ用のコンポーネント
今回は吹き出しコンテンツ用に1つのコンポーネントを実装しました
function Bubble({ direction, children }) {
return (
<div className={`bubble ${direction}`}>
{direction === 'left' && (
<img src="/images/bubble-man.png" alt="bubble-man" width="80px" height="80px" loading="lazy" />
)}
<p>{children}</p>
{direction === 'right' && (
<img src="/images/bubble-woman.png" alt="bubble-woman" width="80px" height="80px" loading="lazy" />
)}
</div>
);
}
export default Bubble;
mdx では Bubble コンポーネントの引数 direction に男性の場合は left 、女性の場合は right を渡して、表示したい文章をタグで囲います
<Bubble direction="left">Astroブログに吹き出し機能を追加!吹き出しコンポーネント実装</Bubble>
<Bubble direction="right">Astroブログに吹き出し機能を追加!吹き出しコンポーネント実装</Bubble>
あとは自分好みでスタイルを当てたら以下のように吹き出しコンテンツを表現することができるようになります

Astroブログに吹き出し機能を追加!吹き出しコンポーネント実装
Astroブログに吹き出し機能を追加!吹き出しコンポーネント実装
