React-Markdownをカスタマイズする話

reactでのmarkdownを表示するライブラリ react-markdown のカスタマイズ方法をメモしておきます

今回のカスタマイズするポイントは以下です

  • リンクを新規ウィンドウ(タブ)で開く
  • コードブロックのシンタックスハイライト

カスタマイズの書き方

ReactMarkdownにて各要素のタグに関する処理はcomponentsに記載すればOKです

今回はリンク(a)とコードブロック(code)のカスタマイズを行います

    <ReactMarkdown
        components={{
            a: AnchorTag,
            code: CodeBlock,
        }}
    >

コンポーネントのカスタマイズ

リンクを新規ウィンドウ(タブ)で開く

リンク(a)にカスタム定義 AnchorTagを指定します


            a: AnchorTag,

の部分です

const AnchorTag = ({ node, children, ...props }: any) => {
    try {
        new URL(props.href ?? "");
        props.target = "_blank";
        props.rel = "noopener noreferrer";
    } catch (e) { }
    return <a {...props}>{children}</a>;
}

ソースの解説は不要かな。
aのtargetとrelを指定しました

コードブロックのシンタックスハイライト

コードブロック(code)にカスタム定義 CodeBlockを指定します


            code: CodeBlock,

CodeBlockでは、言語指定した場合にSyntaxHighlighterを使用します
今回はデザイン atomDarkを使っています


import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { atomDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';

const CodeBlock = ({ inline, className, children, }: any) => {
    if (inline) {
        return <code className={className}>{children}</code>;
    }

    const match = /language-(\w+)/.exec(className || '');
    if (!match) {
        return <code className={className}>{children}</code>;
    }

    const lang = match && match[1] ? match[1] : '';

    return (
        <SyntaxHighlighter
            style={atomDark}
            language={lang}
        >
            {String(children).replace(/\n$/, '')}
        </SyntaxHighlighter>
    );
}

終いに

 今回はreact-markdownのカスタマイズに関して書きました
一度定義したら変更することがないので忘れがちなので記事にしました
特にアンカー(a)は基本仕様が_blankだったかなと勘違いしてました

参考:ソース


import ReactMarkdown from 'react-markdown';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { atomDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';

type Props = {
    children?: string | null | undefined;
};

const CustomReactMarkdown: React.FC<Props> = (props: Props) => {
    return <ReactMarkdown
        components={{
            a: AnchorTag,
            code: CodeBlock,
        }}
    >
        {props.children}
    </ReactMarkdown>
}

export default CustomReactMarkdown


const CodeBlock = ({ inline, className, children, }: any) => {
    if (inline) {
        return <code className={className}>{children}</code>;
    }

    const match = /language-(\w+)/.exec(className || '');
    if (!match) {
        return <code className={className}>{children}</code>;
    }

    const lang = match && match[1] ? match[1] : '';

    return (
        <SyntaxHighlighter
            style={atomDark}
            language={lang}
        >
            {String(children).replace(/\n$/, '')}
        </SyntaxHighlighter>
    );
}

const AnchorTag = ({ node, children, ...props }: any) => {
    try {
        new URL(props.href ?? "");
        props.target = "_blank";
        props.rel = "noopener noreferrer";
    } catch (e) { }
    return <a {...props}>{children}</a>;
}