Tips & Tricks

Claude CodeでMarkdown処理・変換を実装する方法

Claude Codeを使ってMarkdownの解析・変換・拡張を効率的に実装。remarkプラグイン、カスタムコンポーネント、目次生成の実践コード付き。

Markdown処理をClaude Codeで効率化する

ブログやドキュメントサイトでは、Markdownの解析・変換・拡張が必要になります。Claude Codeを使えば、remarkやrehypeのプラグイン開発からカスタムコンポーネントの実装まで短時間で構築できます。

unified/remarkによるMarkdown解析

> remarkでMarkdownを解析してHTML変換するパイプラインを作って。
> コードハイライト、GFM対応、目次生成も含めて。
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkGfm from "remark-gfm";
import remarkRehype from "remark-rehype";
import rehypeHighlight from "rehype-highlight";
import rehypeStringify from "rehype-stringify";
import rehypeSlug from "rehype-slug";

export async function markdownToHtml(markdown: string): Promise<string> {
  const result = await unified()
    .use(remarkParse)
    .use(remarkGfm) // テーブル、チェックリスト、取り消し線
    .use(remarkRehype, { allowDangerousHtml: true })
    .use(rehypeSlug) // 見出しにIDを付与
    .use(rehypeHighlight) // コードハイライト
    .use(rehypeStringify, { allowDangerousHtml: true })
    .process(markdown);

  return String(result);
}

// 使用例
const html = await markdownToHtml(`
# はじめに

これは**テスト**です。

## コード例

\`\`\`typescript
const greeting = "Hello, World!";
console.log(greeting);
\`\`\`

| 項目 | 値 |
|------|-----|
| A    | 100 |
| B    | 200 |
`);

目次の自動生成

Markdownの見出しから目次(TOC)を自動生成する機能です。

interface TocItem {
  id: string;
  text: string;
  level: number;
  children: TocItem[];
}

export function extractToc(markdown: string): TocItem[] {
  const headingRegex = /^(#{1,6})\s+(.+)$/gm;
  const items: TocItem[] = [];
  let match: RegExpExecArray | null;

  while ((match = headingRegex.exec(markdown)) !== null) {
    const level = match[1].length;
    const text = match[2].trim();
    const id = text
      .toLowerCase()
      .replace(/[^\w\s\u3000-\u9fff]/g, "")
      .replace(/\s+/g, "-");

    items.push({ id, text, level, children: [] });
  }

  // ネスト構造に変換
  const toc: TocItem[] = [];
  const stack: TocItem[] = [];

  items.forEach((item) => {
    while (stack.length > 0 && stack[stack.length - 1].level >= item.level) {
      stack.pop();
    }

    if (stack.length === 0) {
      toc.push(item);
    } else {
      stack[stack.length - 1].children.push(item);
    }

    stack.push(item);
  });

  return toc;
}

カスタムremarkプラグイン

Markdownに独自の構文を追加するプラグインを作成します。

import { visit } from "unist-util-visit";
import type { Plugin } from "unified";
import type { Root, Paragraph, Text } from "mdast";

// :::note や :::warning で囲むとアラートブロックに変換するプラグイン
export const remarkAlert: Plugin<[], Root> = () => {
  return (tree) => {
    visit(tree, "paragraph", (node: Paragraph, index, parent) => {
      const firstChild = node.children[0];
      if (firstChild?.type !== "text") return;

      const text = (firstChild as Text).value;
      const match = text.match(/^:::(note|warning|tip|danger)\s*\n/);

      if (!match) return;

      const type = match[1];
      const content = text.slice(match[0].length).replace(/\n:::$/, "");

      // カスタムHTMLに変換
      (node as any).type = "html";
      (node as any).value = `<div class="alert alert-${type}"><p>${content}</p></div>`;
      (node as any).children = undefined;
    });
  };
};

MarkdownからReactコンポーネントへの変換

MDXを使わずにMarkdownをReactコンポーネントに変換する方法です。

import { useMemo } from "react";
import { markdownToHtml } from "./markdown";
import DOMPurify from "isomorphic-dompurify";

interface MarkdownRendererProps {
  content: string;
  className?: string;
}

export function MarkdownRenderer({ content, className }: MarkdownRendererProps) {
  const [html, setHtml] = useState("");

  useEffect(() => {
    markdownToHtml(content).then((result) => {
      setHtml(DOMPurify.sanitize(result));
    });
  }, [content]);

  return (
    <div
      className={`prose prose-lg max-w-none ${className || ""}`}
      dangerouslySetInnerHTML={{ __html: html }}
    />
  );
}

Markdownファイルのフロントマター解析

ブログ記事などのフロントマターを含むMarkdownを解析します。

import matter from "gray-matter";

interface BlogPost {
  title: string;
  description: string;
  pubDate: string;
  category: string;
  tags: string[];
  content: string;
}

export function parseBlogPost(fileContent: string): BlogPost {
  const { data, content } = matter(fileContent);

  return {
    title: data.title || "",
    description: data.description || "",
    pubDate: data.pubDate || "",
    category: data.category || "",
    tags: data.tags || [],
    content,
  };
}

// 複数ファイルの一括読み込み
import fs from "fs/promises";
import path from "path";

export async function getAllPosts(dir: string): Promise<BlogPost[]> {
  const files = await fs.readdir(dir);
  const mdFiles = files.filter((f) => f.endsWith(".md") || f.endsWith(".mdx"));

  const posts = await Promise.all(
    mdFiles.map(async (file) => {
      const content = await fs.readFile(path.join(dir, file), "utf-8");
      return { ...parseBlogPost(content), slug: file.replace(/\.mdx?$/, "") };
    })
  );

  return posts.sort(
    (a, b) => new Date(b.pubDate).getTime() - new Date(a.pubDate).getTime()
  );
}

CLAUDE.mdファイルの書き方についてはCLAUDE.md設定のベストプラクティスを、Webスクレイピングで取得したデータの加工はWebスクレイピングをご覧ください。

まとめ

Claude Codeを使えば、Markdownの解析パイプライン構築、カスタムプラグイン開発、フロントマター解析、目次生成まで、Markdown処理に関する実装を短時間で完了できます。「こんなMarkdown拡張を作りたい」と伝えるだけで適切な実装が得られます。

詳しくはClaude Code公式ドキュメントを参照してください。

#Claude Code #Markdown #remark #rehype #コンテンツ管理