現在携わっているプロジェクトでmarkdown-itを利用しているのですが、実現したい表現のために新たな機能拡張が必要になりました。カスタムプラグインの作成方法を調べてみることにしました。
課題感
標準のMarkdown記法だけでは、例えば特定のキーワード(例: IMPORTANT:
)を含むテキストを自動で特定のCSSクラス付きのタグで囲んだり、外部リンクに一律でtarget="_blank"
属性を付与したりといった、独自のレンダリングルールを適用することが難しいという課題がありました。
解決策:プラグイン機構の活用
この課題に対し、markdown-it
が提供するプラグイン機構を利用することで、独自のルールを簡単に追加できることが分かりました。具体的には、プラグインとして機能するJavaScript(TypeScript)の関数を作成し、それをmarkdownIt.use()
メソッドでインスタンスに登録することで、レンダリング処理を拡張し、前述の課題を解決できました。
実装手順
実際にプラグインを導入した際の手順は、大きく分けて「プラグインの作成」と「インスタンスへの登録」の2ステップでした。以下にその詳細を記述します。
1. プラグインの作成
まず、プラグインとして機能する関数を作成します。この関数は第一引数にmarkdown-it
のインスタンスを、第二引数にオプションを受け取ることができます。
今回は例として、特定のテキストを強調表示する簡単なプラグインを作成してみました。
// customPlugins/myCustomPlugin.ts
import type MarkdownIt from "markdown-it";
import type Token from "markdown-it/lib/token";
export function myCustomPlugin(md: MarkdownIt, options?: any) {
// オプションのデフォルト値
const defaultOptions = {
className: "my-custom-class",
};
const effectiveOptions = { ...defaultOptions, ...options };
function rule(state: any) {
// ここにプラグインのロジックを記述
// 例: 特定のパターンに一致するテキストを強調表示する
for (let i = 0; i < state.tokens.length; i++) {
const token = state.tokens[i];
if (token.type === "inline" && token.content.includes("IMPORTANT:")) {
const newToken = new state.Token("strong_open", "strong", 1);
newToken.attrSet("class", effectiveOptions.className);
// 元のトークンを分割して、強調表示する部分を囲む
// (この部分は簡略化しており、実際の処理はより複雑になります)
// ...
}
}
}
// 'core' ルールとしてプラグインを登録
md.core.ruler.push("my_custom_rule", rule);
}
このコードでは、md.core.ruler
に自作のルールを追加することで、トークン全体に処理を加えています。
2. インスタンスへの登録
次に、作成したプラグインをmarkdown-it
のインスタンスに登録します。登録にはuse()
メソッドを使用しました。
import MarkdownIt from "markdown-it";
import { myCustomPlugin } from "./customPlugins/myCustomPlugin"; // 作成したプラグインをインポート
// MarkdownItインスタンスの作成
const markdownIt = new MarkdownIt({
html: true,
breaks: true,
linkify: true,
typographer: true,
});
// カスタムプラグインの登録
markdownIt
.use(myCustomPlugin, { className: "highlight" }); // オプションを指定して登録
export default markdownIt;
import
でプラグイン関数を読み込み、markdownIt.use()
の第一引数にその関数を渡すだけで登録が完了します。複数のプラグインを使用したい場合は、use()
をチェーンして記述することも可能です。
3. オプションの指定
プラグインがオプションを受け付ける作りにした場合、use()
メソッドの第二引数にオブジェクトとして渡すことができます。
// 例: 複数のオプションを渡す場合
markdownIt.use(myCustomPlugin, { className: "important-text", anotherOption: "value" });
これにより、プラグインの挙動を登録時に変更でき、再利用性が高まると感じました。
実装のポイント
実装してみてポイントだと感じたのは、プラグインがmarkdown-it
のどの処理段階に介入するかを意識することでした。
サンプルコードのようにmd.core.ruler
にルールを追加する方法は、Markdownテキスト全体をスキャンして処理を行う場合に適しています。一方で、特定の要素(例えばリンクや画像など)のレンダリング方法だけを変更したい場合は、md.renderer.rules
の既存のルールを上書きする方法が有効です。
どちらの方法を選択するかは、実現したい機能によって変わってくるため、markdown-it
の処理パイプラインを理解しておくことが重要だと分かりました。
まとめ
今回、markdown-it
のカスタムプラグインを導入する基本的な手順を確認し、Markdownのレンダリング処理を自由に拡張できることが分かりました。これにより、プロジェクト固有の細かい要件にも柔軟に対応できる見通しが立ちました。
今後は、より複雑な構文を解釈・レンダリングするプラグインの開発や、既存の便利なプラグインとの連携なども試していきたいと考えています。