React でリアルタイム Markdown エディターを作る【シンタックスハイライト対応】
フロントエンドエンジニアの茶木です。Markdown 便利ですよね。
先日、React 上で Markdown をHTMLとして表示するケースがありました。
react-markdown というライブラリを使って簡単に実装ができました。簡単すぎてブログの記事とするには少し物足りないので、シンタックスハイライトの追加といったアドバンスも含めて解説していきます。
基本
react-markdown の基本は非常に簡単です。children
にパースしたい Markdown の文字列を読み込ませればそれだけで変換されます。
import ReactMarkdown from "react-markdown";
const text = "テキスト **太字** *斜体* (Markdown)[https://github.com/remarkjs/react-markdown]";
const MarkdownStatic = () => {
return (
<ReactMarkdown>{text}</ReactMarkdown>
);
};
たとえば、このコードなら、テキスト 太字 斜体 Markdown のようにパースされます。
リアルタイムエディタ
踏まえて、左のテキストエリアに、Markdown 形式で入力すると、リアルタイムに右のプレビュー画面に HTMLにパースされて出力されるようなものを作っていきます。

import { useState } from "react";
import ReactMarkdown from "react-markdown";
import styles from "./Markdown.module.css";
const MarkdownEditor = () => {
const [text, setText] = useState("");
return (
<div className={styles.container}>
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
className={styles.editor}
/>
<div className={styles.preview}>
<ReactMarkdown>{text}</ReactMarkdown>
</div>
</div>
);
};
useState
で管理して textarea
の更新をトリガーにパースし直されるようにしました。これだけで入力に即応してプレビューが更新されるエディタは完成です。
GFM 拡張
GFM (GitHub Flavored Markdown)
というMarkdown の拡張仕様に対応します。
これは、GitHub で使用される拡張仕様で、打ち消し線、チェックボックス、テーブル、自動リンクなどがあります。

import remarkGfm from "remark-gfm";
<ReactMarkdown remarkPlugins={[remarkGfm]}>{text}</ReactMarkdown>
remarkGfm
は、Remark
という Markdown パーサーのプラグインです。これを remarkPlugins
に渡すだけです。
SyntaxHighlighter
次のように構文のハイライトも対応します。対応している言語はいくつもあり、```typescript
といった形でコードブロックに言語を指定できます。対応する言語があればパースして着色してくれます。

import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
<div className={styles.preview}>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
code(props) {
const { children, className } = props;
const match = /language-(\w+)/.exec(className || "");
return match ? (
<SyntaxHighlighter language={match[1]}>
{children}
</SyntaxHighlighter>
) : (
<code className={className}>{children}</code>
);
},
}}
>
{text}
</ReactMarkdown>
</div>
コードの解説です。react-syntax-highlighter
をインポートしておきます。
ReactMarkdown の components
内で code()
とすることで、パース後に <code></code>
となるものをフックします。<code></code>
となるのは、前述の ```
で始まるコードブロックと `
で始まるインラインのコードです。
className
には、コードブロックに指定した言語名が、language-xxx
の形で格納されるので、言語名を取得します。取得できれば、SyntaxHighlighter
の language
として children
といっしょに渡せばハイライトしてくれます。言語名が取得できないときは、言語指定なしのコードブロックもしくは、インラインのコードとなり、通常と同様の処理を行っています。
さらなる拡張と改良
SyntaxHighlighter で components
よりフックを行っています。たとえば a
タグをフックして、Amazon や Youtube のリンクと判断すれば埋め込みを行うといったことも拡張が可能です。
他にも使い勝手に関する改良として、プログラミングコードを扱う場合ではタブキーによるインデントの操作ができると便利ですが、現状ではタブキーはフォーカスの移動となり行えません。もうひとつは、テキストエリアでスクロールを行ったとき、プレビューのスクロール位置も連動すると便利そうです。
これらは次回以降のテーマとしておきます。
Gaji-Labo は支援しているスタートアップに対して、ただの作業者ではなく頼れるパートナーとして支援できる仕事のやり方を大切にしています。便利なライブラリを使う場合であっても、改良点やケアすべき点は多いものです。プラスワンの価値を考えていけるようなエンジニアでありたいと思います。