はみ出るMarkdownのテーブルを改善!Rehypeプラグイン実装

Astro 産のブログで MySQL の実行計画の表を書いていたときの話…
テーブルを使いたく Markdown でいつも通りテーブルを書き、実行計画の表を完成させました
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | SIMPLE | question | | ref | PRIMARY,user_id,user_id_created | user_id_created | 5 | const | 3 | 100 | Backward index scan; Using index |
| 1 | SIMPLE | answers | | ref | question_id | question_id | 5 | question.id | 3 | 100 | |
しかし、完成した表を見てみるとテーブル要素が親要素よりも大きくなってしまったことで UI が崩れてしまっていました…
本記事ではそんな課題を解決するために Rehype プラグインを作成しました
Rehypeとは
Rehype とは HTMLを unist を拡張した hast と呼ばれる抽象構文木として扱うプラグインのエコシステムのことです
unist や hast などについて詳しくは別記事で触れているのでそちらをご覧ください!

課題にどう対応するか
Astro では unified で Markdown から HTML への変換を実現しています
先ほど記事の上部で書いた以下の Markdown 記法を用いると…
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | SIMPLE | question | | ref | PRIMARY,user_id,user_id_created | user_id_created | 5 | const | 3 | 100 | Backward index scan; Using index |
| 1 | SIMPLE | answers | | ref | question_id | question_id | 5 | question.id | 3 | 100 | |
以下のような HTML に変換されて表示されます
<table>
<thead>
<tr>
<th>id</th>
<th>select_type</th>
<th>table</th>
<th>partitions</th>
<th>type</th>
<th>possible_keys</th>
<th>key</th>
<th>key_len</th>
<th>ref</th>
<th>rows</th>
<th>filtered</th>
<th>Extra</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>SIMPLE</td>
<td>question</td>
<td></td>
<td>ref</td>
<td>PRIMARY,user_id,user_id_created</td>
...
</tr>
</tbody>
</table>
本来はこの table タグの親要素に overflow-x: auto; のスタイルを当てるだけでスクロールできるようになります
そしてブログの場合はこの table タグの親要素は article タグになることが多いと思います
しかし、その場合 article タグ内のコンテンツ全体がスクロール可能になってしまうため、親要素にスタイルを当てることができません
その課題に対応するために、今回は table タグを overflow-x: auto; のスタイルが当たった親タグで囲う Rehype プラグインを作成しました
Rehypeプラグインを実装する
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | SIMPLE | question | | ref | PRIMARY,user_id,user_id_created | user_id_created | 5 | const | 3 | 100 | Backward index scan; Using index |
| 1 | SIMPLE | answers | | ref | question_id | question_id | 5 | question.id | 3 | 100 | |
上記のような Markdown が mdast に変換され mdast が hast に変換されることで、以下のような抽象構文木として表現されます
{
type: 'element',
tagName: 'table',
properties: {},
children: [
{
type: 'element',
tagName: 'thead',
properties: {},
children: [Array],
position: [Object]
},
{
type: 'element',
tagName: 'tbody',
properties: {},
children: [Array],
position: [Object]
}
],
...
}
今回作成したプラグインは、上記の hast が HTML に変換される前に overflow-x: auto; のスタイルが当たったdivタグを親要素として追加することで、table タグをスクローラブルにしています
プラグインの実装自体は以下のようになっています
import { h } from 'hastscript'
import { visit } from 'unist-util-visit'
export function rehypeTableWrap() {
return function (tree) {
visit(tree, 'element', (node, index, parent) => {
if (node.tagName === 'table') {
const wrapper = h('div', { class: 'table-wrapper' }, [node])
parent.children[index] = wrapper
}
})
}
}
.table-wrapper {
overflow-x: auto;
}
- hastscript は hast をコードで生成したいときに使えるパッケージです
- unist-util-visit は unist をウォークしたい時に使うパッケージです
rehypeTableWrap プラグインでは tagName が table の hast の場合、hastscript で table-wrapper クラスが付与された div タグを生成して、その子要素として元の table タグの hast を代入し、その要素を元の要素を上書きする処理をしています
wrapper 変数は以下のような hast になっています
{
type: 'element',
tagName: 'div',
properties: { className: [ 'table-wrapper' ] },
children: [
{
type: 'element',
tagName: 'table',
properties: {},
children: [Array],
position: [Object]
}
]
}
これを HTML に変換すると、overflow-x: auto; のスタイルが当たった div タグで元の table タグを囲ってあげることができます
Astroでプラグインを導入する
Astro でプラグインを導入するには astro.config.mjs でプラグインの読み込みを行う必要があります
今回作成したのは Rehype のプラグインなので markdown.rehypePlugins オプションにプラグインを指定してあげたら導入は完了です
import { rehypeTableWrap } from './src/plugins/rehypeTableWrap'
export default defineConfig({
markdown: {
rehypePlugins: [
rehypeTableWrap,
],
},
});
あとは自分好みでスタイルを当てたらスクロールできるテーブルの完成です!