mermaid記法の図を描画する(続)
2024年10月12日

mermaid記法の図を描画する(続)

3,133文字(読了まで約8分)
headerimage

前回の実装では、ブログ記事を記述する mdx ファイル内に、mermaid クラスを持つ<div>要素を記述することで、mermaid 記法の記述・描画を可能としました。

ただ個人的にはあんまり mdx 内にタグを書きたくはありません。 できれば markdown の書き味のまま、mermaid 図を入れたいと感じるため、今回は markdown のコードブロックで図を書けるようにします。

mdx を html に変換するときに、独自のカスタムコンポーネントとマッピングさせることができるのでこれを使います。 mdx 内のコードブロックで mermaid が指定された場合に、上述のような<div>要素に置き換えられれば OK です。

実際に mdx で記述したコードブロック部分は html としては次のようになっています。

コードブロック部分をdevtool表示

<figure> タグから始まり、その直下の<pre>タグで、data-language属性を持っています。 ここでは属性値がmdxとなっていますが、mermaidが入ってきた場合に変換すればよさそうです。

また、たくさんの<span>タグが連なっており、表示している文字列に装飾がされています。 mermaid指定のコードブロックとしては、すべての innerText を集めて、変換後の<div>要素内に入れてやればよさそうです。

mdx のカスタムコンポーネントを用意します

/app/components/mdx/CustomCodeBlock.tsx
import { Child, FC, JSXNode } from "hono/jsx";
 
// 再帰的にchildrenを処理してテキストを抽出する関数
const extractInnerText = async (children: Child): Promise<string> => {
  if (!children) {
    return "";
  }
  if (typeof children === "string") {
    return children; // テキストノードの場合、そのまま返す
  }
 
  if (children instanceof Promise) {
    return await children; // テキストノードの場合、そのまま返す
  }
 
  if (Array.isArray(children)) {
    // 子要素が配列の場合、それぞれを再帰的に処理
    let text = "";
    for (const child of children) {
      text += await extractInnerText(child);
    }
    return text;
  }
 
  if (
    typeof children === "object" &&
    children.props &&
    children.props.children
  ) {
    // 子要素を持つJSX要素の場合、その子要素を再帰的に処理
    return extractInnerText(children.props.children);
  }
 
  return ""; // その他の要素は無視
};
 
type Props = {
  children: Child;
};
export const CustomCodeBlock: FC<Props> = async ({ children }) => {
  // 直下のpreタグが、data-languageとしてmermaid指定されていれば要素置き換え
  if (
    children instanceof Object &&
    "tag" in children &&
    children.tag === "pre" &&
    (children as JSXNode).props["data-language"] === "mermaid"
  ) {
    // mermaidの要素として変換
    const text = await extractInnerText(children);
    return <div class="mermaid">{text}</div>;
  } else {
    // 通常のコードブロックの処理
    return <figure data-rehype-pretty-code-figure>{children}</figure>;
  }
};
 
export default CustomCodeBlock;

カスタムコンポーネントとして登録

/app/components/mdx/index.ts
import CustomCodeBlock from "./CustomCodeBlock";
 
export function useMDXComponents(): MDXComponents {
  return {
    figure: CustomCodeBlock,
  };
}

上記 index.ts は vite の設定として参照します。

vite.config.ts
・・・略・・・
      plugins: [
        ・・・略・・・
        mdx({
          jsxImportSource: "hono/jsx",
          remarkPlugins: [remarkFrontmatter, remarkMdxFrontmatter],
          /**
           * カスタムmdxコンポーネントのインポート指定
           * ※絶対パスとして指定が必要
           **/
          providerImportSource: path.resolve(__dirname, "./app/components/mdx"),
          ・・・略・・・
        })
      ]
・・・略・・・

graph TD; A-->B; A-->C; B-->D; C-->D;

本ブログ記事中で markdown の中で mermaid 記法の図を表示できるようにしました。

  • カスタムコンポーネントとして、mdx のコードブロックで mermaid クラスが指定された場合に、<div class="mermaid">・・・</div>要素に置き換えるようにしました。
  • mdx から変換後の html の構成を解析し、関係ないコードブロックを変換しないようカスタムコンポーネントを実装しました。

以上です。

- コメント -

    このサイトではcookieを利用して、サイト訪問者の識別に利用します。 cookieの利用に同意いただくことで、サイト訪問者は記事のいいね機能等をご利用いただけます。 なお、サイト運営者はアクセス統計としてcookieの情報を利用する場合があります。