react-markdownでcodeタグを
シンタックスハイライトする

react-markdownでcodeタグを シンタックスハイライトする

#React
#TypeScript
#Markdown
2024-07-27

precodeタグを置き換えるためのコンポーネントを作成し、react-markdowncomponentsプロパティに渡します。

Pre.tsx

1import { isValidElement, ReactNode } from "react";
2import { Components } from "react-markdown";
3import { Prism } from "react-syntax-highlighter";
4import style from "react-syntax-highlighter/dist/cjs/styles/prism/vsc-dark-plus";
5
6function getLanguage(className: string) {
7  const match = /language-(\w+)/.exec(className);
8  return match && match[1] ? match[1] : "";
9}
10
11function getChildren(children: ReactNode) {
12  if (isValidElement(children)) {
13    return children;
14  }
15  throw new Error("Invalid children");
16}
17
18export const Pre: Components["pre"] = (props) => {
19  // `pre > code`の構造になっているため、`code`要素を取得する
20  const codeElement = getChildren(props.children);
21  // `code`要素の`className`属性から言語を取得する
22  const lang = getLanguage(codeElement.props.className ?? "");
23
24  return (
25    <Prism style={style} language={lang} showLineNumbers>
26      {String(codeElement.props.children).replace(/\n$/, "")}
27    </Prism>
28  );
29};

Code.tsx

1import { Components } from "react-markdown";
2
3export const Code: Components["code"] = (props) => {
4  return <code className="inline-code">{props.children}</code>;
5};

Markdown.tsx

1// (省略)
2export function Markdown() {
3  // (省略)
4  const content = `
5\`\`\`tsx
6import React from "react";
7
8export function App() {
9  return <div>Hello, World!</div>;
10}
11\`\`\`
12  `;
13
14  return (
15    <ReactMarkdown
16      components={{
17        pre: Pre,
18        code: Code,
19      }}
20    >
21      {content}
22    </ReactMarkdown>
23  );
24}

参考にしたブログなどでは、codeタグを置換することで、インラインと複数行をまとめて処理していました。 しかし、ReactMarkdownが複数行のコードブロックをpreタグでラップしたcodeに変換するため、pre > pre > codeのような階層になってしまいます。

タグ階層を綺麗にするためにprecodeを別々で処理するようにしました。

参考

ハイライトライブラリ

シェア