remark のプラグインを作ってMarkdownの文法を勝手に拡張しよう
投稿日: 2024/06/25
これは友達の話ですが、react-markdownでGithubコメントのような改行を実現しようとしていました。
以下のようなMarkdownがあったときに
input.md
a a a
通常の仕様では
output
a a a
のように表示されます。
しかし、Githubのissueコメントなどでは
output
a a a
のように表示されます。
このような機能を作ろうと思ったときにremark-breaksを使うと簡単に作れます。
しかし、僕 友達はそんなこと知らず、remark-gfmだけを調査してそういうpackageは無いと思い込んでしまいました。
そして、remark のプラグインを自作してしまったのでした。
最終的にこのページで紹介するソースコードは使用しませんでしたが、remark のプラグインを作る知識自体は今後も役に立つと思うので簡単にまとめておきます。
react-markdownでGithubコメントのような改行を実現したいだけなら、この記事よりも以下の方を参考にしてください。
remark のプラグインを作ってMarkdownの文法を勝手に拡張しましょう。
今回は改行しかいじりませんが、かなり自由にMarkdownの文法を改造できます。
参考にしたissueのコメント。
コード全体
import { FC } from "react" import ReactMarkdown from "react-markdown" import { visit } from "unist-util-visit" type Props = { body: string } const remarkBlankLine = () => { const transformer = (tree: any) => { visit(tree, "text", (node) => { if (node.value === "------ br ------") { node.data = node.data || {} node.data.hName = "customBr" } }) } return transformer } export const Markdown: FC<Props> = (props) => { const parsedBody = props.body .replace(/\n{2,}/g, "\n------ br ------\n") .replace(/\n/g, "\n\n") return ( <ReactMarkdown remarkPlugins={[remarkBlankLine]} components={{ customBr() { return <br /> }, }} > {parsedBody} </ReactMarkdown> ) }
解説
const parsedBody = props.body .replace(/\n{2,}/g, "\n------ br ------\n") .replace(/\n/g, "\n\n")
渡されたマークダウン全体を対象に置換します。
複数連続で改行している箇所は後で<br />
タグを出力するので"\n------ br ------\n"
に一時的に置換しています。ちょっとイケてないですね。正直、面倒になってこういう実装にしました。
ちなみにここで<br />
に置換してもXSS対策でエンコードされるのでHTMLタグになりません。
そのあと、単一の改行コードを2つに置換してます。
読み込まれたMarkdownをパーツごとに処理するためにremarkPluginsを自作します。
const remarkBlankLine = () => { const transformer = (tree: any) => { visit(tree, "text", (node) => { if (node.value === "------ br ------") { node.data = node.data || {} node.data.hName = "customBr" } }) } return transformer }
text
を指定しているのでMarkdownを分割して対応する単位でnode
に渡してくれます。言葉で説明するより自分でconsole.log(node)
したらわかりやすいと思います。
渡されたnode
に入っている値が"------ br ------"
の時だけcustomBr
で処理をするように指定します。
<ReactMarkdown remarkPlugins={[remarkBlankLine]} components={{ customBr() { return <br /> }, }} >
先ほどのcustomBr
の時に出力するHTMLを指定します。<br />
です。
完全に余談ですが以下のように代入すると
node.data.hProperties = { test: 'hoge' }
以下のように値を渡せます。
<ReactMarkdown remarkPlugins={[remarkBlankLine]} components={{ customBr({test}) { // hoge console.log(test) return <br /> },