技術ドキュメントやブログ記事では、読者に注意を促す「注意書き」や「ヒント」「警告」などのビジュアルコールアウトが役立ちます。
この記事では、Astro プロジェクトで remark プラグイン を利用してカスタムアドモニションを実装する方法を紹介します。
Adminitionsの必要性
注意事項、ヒント、重要な情報、警告など、特定の情報を強調するために使います。たとえば、Markdown 内で以下のように記述することで表現できます。
:::important[重要]これはとても重要な情報です。:::
この記法により、後述するプラグインがこの部分を解析し、HTML の <aside>
要素に変換して、適切なクラスと属性(例: data-admonition-type="important"
)を付与します。最終的に、SCSS で設定したスタイルにより「重要」なアドモニションは紫色で表示されます。
ライブラリのインストール
remark-directive を使用してプラグインの作成を行います。
yarn add remark-directive
実装
1. remarkAdmonitions プラグインの作成
まず、カスタムの remark プラグインを作成します。このプラグインは Markdown の AST(抽象構文木)を走査し、Admonition用のディレクティブを検出して、以下の処理を行います。
-
ディレクティブの検出
Markdown 内で :::tip
や :::important
などのディレクティブを検出します。
-
タイトルの抽出
ディレクティブの最初の子ノードにカスタムタイトルが指定されている場合、そのタイトルを抽出します。例:
:::important[重要なお知らせ]
と記述すると、タイトルに「重要なお知らせ」が表示されます。
-
HTML 要素への変換
ディレクティブを <aside>
要素に変換し、class="admonition"
と data-admonition-type
属性を付与します。また、タイトル部分は <p class="admonition-title">
、本文は <div class="admonition-content">
にラップします。
import type { AdmonitionType } from "src/types";import type { Node, Paragraph as P, Parent, Root, PhrasingContent } from "mdast";import type { Directives, LeafDirective, TextDirective } from "mdast-util-directive";import { directiveToMarkdown } from "mdast-util-directive";import { toMarkdown } from "mdast-util-to-markdown";import type { Plugin } from "unified";import { visit } from "unist-util-visit";import { type Properties, h as _h } from "hastscript";import { toString as mdastToString } from "mdast-util-to-string";
const Admonitions = new Set<AdmonitionType>(["tip", "note", "important", "caution", "warning"]);
function isAdmonition(s: string): s is AdmonitionType { return Admonitions.has(s as AdmonitionType);}
function isNodeDirective(node: Node): node is Directives { return node.type === "containerDirective" || node.type === "leafDirective" || node.type === "textDirective";}
// textDirective や leafDirective の場合の処理(ディレクティブを Markdown に変換してテキストノードへ置き換える)function transformUnhandledDirective(node: LeafDirective | TextDirective, index: number, parent: Parent) { const textNode = { type: "text", value: toMarkdown(node, { extensions: [directiveToMarkdown()] }), } as const;
if (node.type === "textDirective") { parent.children[index] = textNode; } else { parent.children[index] = { children: [textNode], type: "paragraph", }; }}
// HAST ノードを生成するためのヘルパー関数function h(el: string, attrs: Properties = {}, children: any[] = []): P { const { properties, tagName } = _h(el, attrs); return { children, data: { hName: tagName, hProperties: properties }, type: "paragraph", };}
export const remarkAdmonitions: Plugin<[], Root> = () => tree => { // AST を走査 visit(tree, (node, index, parent) => { if (!parent || index === undefined || !isNodeDirective(node)) return;
// textDirective や leafDirective は別処理 if (node.type === "textDirective" || node.type === "leafDirective") { transformUnhandledDirective(node, index, parent); return; }
// ここから containerDirective の処理 const admonitionType = node.name; if (!isAdmonition(admonitionType)) return;
// デフォルトではタイトルにアドモニションの種類をそのまま使用 let title: string = admonitionType; let titleNode: PhrasingContent[] = [{ type: "text", value: title }];
// カスタムタイトルが指定されているかチェック const firstChild = node.children[0]; if ( firstChild?.type === "paragraph" && firstChild.data && "directiveLabel" in firstChild.data && firstChild.children.length > 0 ) { titleNode = firstChild.children; title = mdastToString(firstChild.children); node.children.splice(0, 1); }
// <aside> 要素を生成(class と data 属性のみ付与) const admonition = h( "aside", { "aria-label": title, class: "admonition", "data-admonition-type": admonitionType, }, [ h("p", { class: "admonition-title", "aria-hidden": "true" }, [...titleNode]), h("div", { class: "admonition-content" }, node.children), ] );
parent.children[index] = admonition; });};
2. Astro の設定にプラグインを追加
次に、astro.config.mjs
に先ほどのプラグインをインポートして設定します。
import { defineConfig } from 'astro/config';import { remarkAdmonitions } from "./src/plugins/remark-admonitions";
export default defineConfig({ markdown: { remarkPlugins: [remarkAdmonitions], },});
これで、Astro は Markdown の変換時に remarkAdmonitions プラグインを実行し、Admonitionを生成します
3. SCSS でアドモニションのスタイルを定義
生成された <aside class="admonition" data-admonition-type="important">
などの要素に対して、SCSS でスタイルを適用します。以下は、タイトル部分の左側に SVG アイコンを配置し、タイトルテキストとアイコンの色を border と同じにする例です。
/* --- Admonition の基本スタイル --- */.admonition { border-left: 5px solid; padding: 16px; margin: 1em 0;
p { margin: 0; margin-top: 8px; }}
/* admonition-title の左側にアイコンを配置するためのスタイル */.admonition .admonition-title { position: relative; padding-left: 32px; font-size: 20px; font-weight: bold; margin: 0;}.admonition .admonition-title::before { content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 24px; height: 24px; display: inline-block; /* mask プロパティで SVG のシルエットを currentColor で表示 */ mask-size: contain; mask-repeat: no-repeat; mask-position: center; -webkit-mask-size: contain; -webkit-mask-repeat: no-repeat; -webkit-mask-position: center; background-color: currentColor;}
/* --- 各 admonition タイプごとのスタイル --- *//* Tip */.admonition[data-admonition-type="tip"] { border-color: #28a745; background-color: #f3fff3;
.admonition-title { color: #28a745; }}.admonition[data-admonition-type="tip"] .admonition-title::before { mask-image: url("/icons/tip.svg"); -webkit-mask-image: url("/icons/tip.svg");}
/* Note */.admonition[data-admonition-type="note"] { border-color: #007acc; background-color: #f4faff;
.admonition-title { color: #007acc; }}.admonition[data-admonition-type="note"] .admonition-title::before { mask-image: url("/icons/note.svg"); -webkit-mask-image: url("/icons/note.svg");}
/* Important */.admonition[data-admonition-type="important"] { border-color: #800080; background-color: #fef6ff;
.admonition-title { color: #800080; }}.admonition[data-admonition-type="important"] .admonition-title::before { mask-image: url("/icons/important.svg"); -webkit-mask-image: url("/icons/important.svg");}
/* Caution */.admonition[data-admonition-type="caution"] { border-color: #fd7e14; background-color: #fff9f0;
.admonition-title { color: #fd7e14; }}.admonition[data-admonition-type="caution"] .admonition-title::before { mask-image: url("/icons/caution.svg"); -webkit-mask-image: url("/icons/caution.svg");}
/* Warning */.admonition[data-admonition-type="warning"] { border-color: #dc3545; background-color: #fff2f2;
.admonition-title { color: #dc3545; }}.admonition[data-admonition-type="warning"] .admonition-title::before { mask-image: url("/icons/warning.svg"); -webkit-mask-image: url("/icons/warning.svg");}
まとめ
今回の記事では、Astro プロジェクト内で remark プラグインを利用して Markdown のアドモニションを実装する方法を紹介しました。