pre
とcode
タグを置き換えるためのコンポーネントを作成し、react-markdown
のcomponents
プロパティに渡します。
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
のような階層になってしまいます。
タグ階層を綺麗にするためにpre
とcode
を別々で処理するようにしました。