react-markdown v6の変更点
目次
react-markdownのメジャーアップデートv6がきており、v5からいくつか仕様が変更されていた。既存コードを書き直す必要があったので、その際の変更点を残しておく。環境は次のとおりです。
- react-markdown(6.0.0)
- react-styntax-highlight(15.4.3)
- Next.js(10.1.3)
- theme-ui(0.7.0)
主要な変更点
source
が廃止され、children
propsになった。
<Markdown>{`source`}</Markdown>
renderers
がcomponents
propsに変更された。これに伴いコンバートさせるコンポーネント名もいくつか変更された。例えばimage
とimageReference
は両者ともimg
になった。
<Markdown
components={{
code: CodeBlock,
img: BlockImage,
}}>
{...}
</Markdown>
allowDangerousHtml
が削除された。同様のことを行うにはrephy-rawを使う必要があるらしい。
コードブロック
react-syntax-highlighterを併用すると、v5のままでは動作しなくなった。
ちなみにv5ではコードブロックは以下のようにしていた。
import { PrismAsyncLight as SyntaxHighlighter } from "react-syntax-highlighter";
import nord from "react-syntax-highlighter/dist/esm/styles/prism/nord";
const CodeBlock = ({ language, value }) => {
const styles = { margin: "16px 0" };
return (
<SyntaxHighlighter language={language} style={nord} customStyle={styles}>
{value}
</SyntaxHighlighter>
);
};
v5ではCodeBlock関数で受け取るpropsは次のようなものだった。コードブロック(複数行)のみ捜査され、インラインコードは対象外。
{
children: "",
language: "language-bash",
node: object,
value: "ls -la",
}
一方、v6ではコードブロックは次のようなpropsを受け取る。
{
children: ["..."],
className: "language-bash",
node: object,
}
そしてv6はインラインコードも捜査され、以下のようなpropsを受け取る。
{
children: ["..."],
inline: true,
node: object,
}
まとめると
- v5の
className
がv6のlanguage
に対応 - v5の
value
がv6のchildren[0]
に対応 className
はコードブロックのみに存在するinline
はインラインコードにのみ存在する
公式のサンプル
公式にコードハイライトの例が以下のように記載されている。前述のようにコードブロックではclassName
propsが存在するため、これを使い出力を分岐させている。
// 抜粋
const components = {
code({node, className, ...props}) {
const match = /language-(\w+)/.exec(className || '')
return match
? <SyntaxHighlighter language={match[1]} PreTag="div" style={dark} {...props} />
: <code className={className} {...props} />
}
}
render(<ReactMarkdown components={components} children={markdown} />, document.body)
自分のプロジェクトに組み込んでみると、動作はするが2点問題が起こった。
- コンソールにインラインエラーが出る
- コードブロック下部に空白ができる
コンソールのインラインエラー
開発ツールで以下のようなエラーが表示される。
Warning: Received true for a non-boolean attribute inline. If you want to write it to the DOM, pass a string instead:
inline="true"
orinline={value.toString()}
.
これはinline
および{...props}
が原因。手っ取り早く<code className={className} />
にするか、次のようにinlineを切り分けるといい。
const components = {
code({node, className, inline, ...props}) {
const match = /language-(\w+)/.exec(className || '')
return match
? <SyntaxHighlighter language={match[1]} PreTag="div" style={dark} {...props} />
: <code className={className} {inline.toString()} {...props} />
}
}
コードブロック下の空白
スタイルをリセットしても生じるため、ややハマった問題。
これはコードブロックのchildren
propsの末尾に"\n"
が追加されているのが原因。つまり余分な1行が生成されている。解決にはこの改行コードを削除する。
<SyntaxHighlighter language={match[1]} style={dark} PreTag="div">
{children[0].replace(/\n$/i, "")}
</SyntaxHighlighter>
preの増殖
v5までは生成されるコードがpre > code
だったが、v6ではpre > pre > code
になっている。1番上のpreはreact-markdownが生成しているもので、その下のpreはsyntex-hilighterが生成している。react-syntax-highlighter側のpreTag
でタグを変更しないと、二重preが出来上がってしまう。
<SyntaxHighlighter PreTag="div" ... />
また、react-syntax-highlighterにはcustomstyle
という最上位のpreのスタイルを上書きするpropsがある。v5まではこれ用いてコードブロック全体をラップするスタイルを設定できていたが、v6では前述のようにreact-markdownが生成したpreが最上位にあるため、親preにスタイルを適用させる方法がなくなっている。
このようにv6は、使い勝手が幾分悪くなっている印象を受ける。