🍺

react-markdownのsyntax highlightの設定がv6から変わっていた件

2021/06/19に公開

現在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

v5はrenderers

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と定義されています。

https://qiita.com/NeGI1009/items/d38e517f1647ff2000c7

v6はcomponents

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