react-markdownのsyntax highlightの設定がv6から変わっていた件
現在Next.jsで自作ブログを作成中なのですが、マークダウンファイルをhtmlに変換してくれるnpmパッケージとしてreact-markdownを使っています。
react-markdownのreadme.mdにはコードブロックのシンタックスハイライトのコード例も記載されています。
最初はweb上の記事の実装例を参考にコードブロックのシンタックスハイライトの設定をしていたのですが、TypeScriptを使用していると型が合わず警告が出る…という状態になり、ここではじめてv5とv6で書き方が大きく変わっていることに気づきました。
本記事では、react-markdown
のv6・TypeScript利用時のシンタックスハイライトの定義方法をご紹介します。
準備
create-react-app
でReactアプリの雛形を作成し、以下のコマンドで必要なnpmパッケージをインストールします。
$ yarn add react-markdown react-syntax-highlighter
$ yarn add -D @types/react-syntax-highlighter
renderers
v5はv5ではrenderers
というpropsでシンタックスハイライトの設定をしていました。renderers.code
の型はReact.ElementType
と定義されているため、CodeBlock
というような関数コンポーネントを指定することができました。
参考: v5.0.3のREAME.md
import React from "react";
import ReactMarkdown from "react-markdown";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { dark } from "react-syntax-highlighter/dist/esm/styles/prism";
const App: React.VFC = () => {
const renderers = {
code: CodeBlock,
};
// Did you know you can use tildes instead of backticks for code in markdown? ✨
const markdown = `Here is some JavaScript code:
~~~js
console.log('It works!')
~~~
`;
return <ReactMarkdown renderers={renderers}>{markdown}</ReactMarkdown>;
};
interface CodeBlockProps {
language: string | null;
value: string;
}
const CodeBlock: React.VFC<CodeBlockProps> = (props) => {
return (
<SyntaxHighlighter style={dark} language={props.language ?? undefined}>
{props.value}
</SyntaxHighlighter>
);
};
export default App;
※ React.ElementType
の型定義については以下の記事を参照ください。
React.ElementTypeは'span'や'div'などHTMLタグの文字列 | React.ComponentClass | React.FCと定義されています。
components
v6はv6ではcomponents
というpropsに変わり、renderers
はなくなりました。
components.code
はv5のようにReact.ElementType
ではなく、code: CodeComponent | ReactMarkdownNames
という独自の型となっています。そのため、この型をreact-markdown/src/ast-to-react
からimportしてCodeBlock
を定義しました。この型をインポートしなくても良い書き方があれば良いのですが思い付かず。。
import React from 'react';
import ReactMarkdown from 'react-markdown';
import {
CodeComponent,
ReactMarkdownNames,
} from 'react-markdown/src/ast-to-react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { dark } from 'react-syntax-highlighter/dist/esm/styles/prism';
const App: React.VFC = () => {
const components = {
code: CodeBlock,
};
// Did you know you can use tildes instead of backticks for code in markdown? ✨
const markdown = `Here is some JavaScript code:
~~~js
console.log('It works!')
~~~
`;
return <ReactMarkdown components={components}>{markdown}</ReactMarkdown>;
};
const CodeBlock: CodeComponent | ReactMarkdownNames = ({
inline,
className,
children,
...props
}) => {
const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
<SyntaxHighlighter style={dark} language={match[1]} PreTag="div" {...props}>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
);
};
export default App;
ちなみに、v6のREADME.mdにあるシンタックスハイライトのコード例は以下となります。
ReactのESLintの設定をしているとreact/no-children-props
で怒られます。
import React from 'react'
import ReactMarkdown from 'react-markdown'
import {Prism as SyntaxHighlighter} from 'react-syntax-highlighter'
/* Use `…/dist/cjs/…` if you’re not in ESM! */
import {dark} from 'react-syntax-highlighter/dist/esm/styles/prism'
import {render} from 'react-dom'
const components = {
code({node, inline, className, children, ...props}) {
const match = /language-(\w+)/.exec(className || '')
return !inline && match ? (
<SyntaxHighlighter style={dark} language={match[1]} PreTag="div" children={String(children).replace(/\n$/, '')} {...props} />
) : (
<code className={className} {...props}>
{children}
</code>
)
}
}
// Did you know you can use tildes instead of backticks for code in markdown? ✨
const markdown = `Here is some JavaScript code:
~~~js
console.log('It works!')
~~~
`
render(<ReactMarkdown components={components} children={markdown} />, document.body)
おわりに
react-markdown
のこの変更を通して「React.ElementType
とは?React.ReactNode
とは?」といったReactの型定義について調べ直すきっかけになりましたが、
個人的にはv5の方がわかりやすい・書きやすいように感じます。。
Discussion