【Astro】rehype (remark) プラグインを自作してMarkdown内の内部リンクをカードに変換する

AstroでMarkdown・MDXを扱う際、remark及びrehypeのサードパーティプラグイン1のお世話になることが多いが、今回は自前で簡易的なrehypeプラグインを作ってみる。

やりたいこと

mdファイル内に埋め込んだ内部リンク (自サイトの別記事へのテキストリンク) をリンクカードに変換したい2

Before
[](/some-page)

上記のように/から始まる相対パスの場合に内部リンクとみなす。

After
<a href="/some-page" class="internal-link">
<span>title</span>
<time datetime="2000-01-01T00:00:00.000Z">2000-01-01</time>
</a>

<a>には任意のクラスを指定し、タイトルと投稿日をリンク内に挿入する。

解決法

1. rehypeプラグインを実装

Terminal window
npm i unist-util-visit

hastをwalkするために、unistのユーティリティモジュールunist-util-visitをインストールする。

rehype-internal-link.ts
import type { ElementContent, Root } from 'hast';
import { visit } from 'unist-util-visit';
export default function rehypeInternalLink() {
return (tree: Root) => {
visit(tree, 'element', (node) => {
if (node.tagName !== 'a') return;
const href = node.properties.href;
if (typeof href !== 'string' || !href.startsWith('/')) return;
const { title, createdAt } = getPost(href); // 何らかの方法で投稿情報を取得
const titleElm = {
type: 'element',
tagName: 'span',
children: [
{
type: 'text',
value: title,
},
],
} satisfies ElementContent;
const timeElm = {
type: 'element',
tagName: 'time',
properties: { dateTime: createdAt.toJSON() },
children: [
{
type: 'text',
value: formatDate(createdAt),
},
],
} satisfies ElementContent;
node.children = [titleElm, timeElm];
node.properties.className = 'internal-link';
});
};
}

hast (Node) をvisitし、<a>かつhrefが相対パスであれば任意のElementを挿入する3

rehype-internal-link.ts
import type { ElementContent, Root } from 'hast';
import { visit } from 'unist-util-visit';
import styles from './index.module.css';
export default function rehypeInternalLink() {
return (tree: Root) => {
visit(tree, 'element', (node) => {
...
node.children = [titleElm, timeElm];
node.properties.className = 'internal-link';
node.properties.className = styles.card;
});
};
}

もしCSS Modulesを利用している場合はclassNameに直接指定しスタイリングすることもできる。

2. 設定ファイルに追記

astro.config.ts
import rehypeInternalLink from './rehype-internal-link';
export default defineConfig({
markdown: {
rehypePlugins: [rehypeInternalLink],
},
});

あとはAstroの設定ファイルのrehypePluginsに指定すれば完成。リンクカードに変換されるようになった。

参考
  1. remark/doc/plugins.mdrehype/doc/plugins.mdから一覧を参照できる

  2. 外部リンクをリンクカードに変換する場合はremark-link-cardを利用するのが楽そう。OGP画像も表示してくれる

  3. プラグイン実装の雛形はREADMEに記載されている