unified/remark/rehype パイプライン — Markdown を高機能 HTML に変換する仕組み
unified エコシステムを使うと、Markdown ファイルをシンタックスハイライト付き・外部リンク制御付きの高機能 HTML に変換できます。各プラグインが何をしているかを、AST(抽象構文木)の変換フローと合わせて解説します。
unified エコシステムとは
unified はテキスト処理を AST ベースのパイプラインで行うフレームワークです。
入力テキスト
↓ parse(パーサー)
AST(抽象構文木)
↓ transform(プラグイン群)
変換後 AST
↓ stringify(シリアライザー)
出力テキスト
remark は Markdown 用の unified プロセッサ、rehype は HTML 用の unified プロセッサで、両者を橋渡しする remark-rehype を介することで Markdown → HTML の変換チェーンが成立します。
登場する AST の種類
| AST 名 | 対応形式 |
|---|---|
| mdast | Markdown |
| hast | HTML |
パイプラインの全体像
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkGfm from "remark-gfm";
import remarkRehype from "remark-rehype";
import rehypeRaw from "rehype-raw";
import rehypePrism from "rehype-prism-plus";
import rehypeExternalLinks from "rehype-external-links";
import rehypeStringify from "rehype-stringify";
const processedContent = await unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeRaw)
.use(rehypePrism)
.use(rehypeExternalLinks, { target: "_blank", rel: ["nofollow"] })
.use(rehypeStringify)
.process(content);
const html = processedContent.toString();
ステップ 1: remarkParse — Markdown → mdast
.use(remarkParse)
Markdown テキストを mdast(Markdown AST)に変換するパーサーです。unified() に最初に追加する「入口」の役割を担います。
// "## 見出し" → mdast ノード
{
"type": "heading",
"depth": 2,
"children": [{ "type": "text", "value": "見出し" }]
}
ステップ 2: remarkGfm — GFM 拡張の有効化
.use(remarkGfm)
GitHub Flavored Markdown の拡張構文を mdast に追加するプラグインです。
| 構文 | 例 |
|---|---|
| テーブル | | A | B | |
| タスクリスト | - [x] 完了 |
| 打ち消し線 | ~~text~~ |
| 自動リンク | https://example.com |
ステップ 3: remarkRehype — mdast → hast
.use(remarkRehype, { allowDangerousHtml: true })
mdast を hast(HTML AST)に変換するブリッジプラグインです。allowDangerousHtml: true を渡すことで、Markdown 内に書いた生 HTML(<div>, <span> など)を hast の raw ノードとして保持します。このオプションを省略すると生 HTML がすべて除去されます。
ステップ 4: rehypeRaw — 生 HTML ノードのパース
.use(rehypeRaw)
前ステップで raw ノードとして残った生 HTML 文字列を、正式な hast ノードに変換します。remarkRehype の allowDangerousHtml: true とセットで使うのが必須パターンです。
remarkRehype({ allowDangerousHtml: true }) → raw ノードとして残す
↓
rehypeRaw → raw ノードを hast ノードに昇格させる
この 2 ステップがないと、Markdown 内の <details> や <kbd> などのカスタム HTML が消えます。
ステップ 5: rehypePrism — シンタックスハイライト
.use(rehypePrism)
rehype-prism-plus が提供するプラグインです。hast 上のコードブロックノード(<pre><code class="language-xxx">)を走査し、Prism.js のトークン分割に基づいたクラスを付与します。
<!-- 出力例 -->
<pre class="language-typescript">
<code class="language-typescript">
<span class="token keyword">const</span> x
<span class="token operator">=</span>
<span class="token number">1</span>
</code>
</pre>
CSS 側で .token.keyword などにスタイルを当てることでハイライトが実現します。
ステップ 6: rehypeExternalLinks — 外部リンク処理
.use(rehypeExternalLinks, { target: "_blank", rel: ["nofollow"] })
http:// / https:// で始まる外部リンクに対して自動で属性を付与するプラグインです。
| オプション | 付与される属性 | 効果 |
|---|---|---|
target: "_blank" | target="_blank" | 新しいタブで開く |
rel: ["nofollow"] | rel="nofollow" | 検索エンジンへのリンクジュース遮断 |
セキュリティのため noopener noreferrer も一緒に付与するのが推奨されます。
.use(rehypeExternalLinks, {
target: "_blank",
rel: ["nofollow", "noopener", "noreferrer"],
})
ステップ 7: rehypeStringify — hast → HTML 文字列
.use(rehypeStringify)
hast を HTML 文字列に変換する「出口」のシリアライザーです。
パイプライン全体のデータフロー
Markdown テキスト (string)
│
▼ remarkParse
mdast (Markdown AST)
│
▼ remarkGfm ← テーブル・タスクリストなどのノードを追加
mdast (拡張済み)
│
▼ remarkRehype({ allowDangerousHtml: true })
hast + raw ノード(生 HTML 断片)
│
▼ rehypeRaw ← raw ノードを正式な hast ノードに変換
hast (完全な HTML AST)
│
▼ rehypePrism ← コードブロックにハイライト用クラスを付与
hast (ハイライト済み)
│
▼ rehypeExternalLinks
hast (リンク属性付き)
│
▼ rehypeStringify
HTML 文字列 (string)
ハマりやすいポイント
allowDangerousHtml と rehypeRaw のセット忘れ
remarkRehype に allowDangerousHtml: true を渡しただけでは生 HTML は残りません。rehypeRaw を後続に追加して初めて hast ノードに変換されます。
プラグインの順序
rehypeRaw は remarkRehype の直後に置くのが安全です。raw ノードが他のプラグインで処理される前に解決しておく必要があります。
// OK: rehypeRaw は remarkRehype の直後
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeRaw) // まず raw ノードを解決
.use(rehypePrism) // その後にハイライト処理
CSS の用意を忘れない
rehype-prism-plus はクラスを付与するだけで、CSS は自分で用意する必要があります。
// src/app/layout.tsx
import "prismjs/themes/prism-tomorrow.css";
ESM 専用パッケージ
unified v11 以降、remark-* / rehype-* の主要パッケージはすべて ESM 専用です。require() で呼び出す CommonJS 環境では動作しません。
まとめ
| プラグイン | 変換内容 |
|---|---|
remarkParse | Markdown → mdast |
remarkGfm | GFM 拡張構文を mdast に追加 |
remarkRehype | mdast → hast(生 HTML は raw ノードとして保持) |
rehypeRaw | raw ノード → 正式な hast ノード |
rehypePrism | コードブロックにハイライト用クラスを付与 |
rehypeExternalLinks | 外部リンクに target / rel を付与 |
rehypeStringify | hast → HTML 文字列 |
各プラグインは AST を受け取って変換した AST を返す単純な構造なので、差し替えや追加が容易です。シンタックスハイライトを rehype-highlight(highlight.js ベース)に変えたい場合は rehypePrism をそのプラグインに置き換えるだけで済みます。