オレオレ記法で書いたMarkdownを任意のReactコンポーネントに変換する。
JAMStackに挑戦する中で、Reactをベースとしたフレームワーク「Next.js」を用いてMarkdownでブログ記事を書くようになりました。しかしYouTubeやTwitterの埋め込みを行おうにも、Markdownの標準的な記法には含まれていないので何とかする必要がありました。
この記事ではオレオレ記法で書かれた拡張MarkdownをReactで利用するために私が用いた方法を書きまとめます。
この記事について
Markdownから読み込んだ文字列をReact上で使用するための方法を書きます。
⇨ Markdown to ReactElement
Markdownでブログテンプレートを作ろうと思っている人などの参考になれば幸いです。
Markdownを変換する方法としてはUnifiedエコシステムを用いるやり方が王道ですが、自分で拡張パーサーを作成するのはちょっと難易度が高かったので、よりお手軽な方法に頼っています。拡張パーサーの作成に挑戦する場合は下記の記事などが参考になるはずです。
オレオレMarkdownの変換
オレオレ記法の例
今回は簡単に以下のようなYouTubeとTwitterの埋め込みをオレオレ記法で表現します。
(Qiita上でMarkdownの中にMarkdownを記載する方法が分からなかったので1,2,3は行番号と思って読み飛ばしてください)
コードを表現するMarkdown記法にtwitte
, youtube
というオレオレな言語名を指定しています。
1 ```twitter:a:b
2 1234567890
3 ```
1 ```youtube
2 1234567890
3 ```
続いて実装です。
node_moduleの調達
基本的なReactの動作環境とMarkdownの読み込みまでは整っている前提です。
yarn add react-markdown remark-gfm
-
react-markdown
: 7.1.0 -
remark-gfm
: 3.0.x
react-markdown
はMarkdownをReactElementに変換するパッケージです。基本はGitHub準拠のMarkdown記法を採用したいので、remark-gfm
をパーサーとして使用します。(内部的にUnifiedエコシステムを使用しています)
Markdownを変換
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import OriginalComponent from "xxxxx";
type Props = {
content: string;
};
const ArticleBody = ({ content }: Props) => {
const convertResult = (
<ReactMarkdown remarkPlugins={[remarkGfm]} components={{code: CodeBlock}}>
{content}
</ReactMarkdown>
);
return <div>{convertResult}</div>;
};
export default ArticleBody;
基本的にreact-markdown
のマニュアルに記載しているとおりです。Markdownで記載した文字列をcontent
として渡し、remarkGfm
を使ってHTMLに変換。code
タグとして解釈されたタグがあればCodeBlock
として追加の変換処理を加えています。
CodeBlock
というのが今回作るオレオレMarkdownの読み替え部分です。
import { CodeProps } from "react-markdown/lib/ast-to-react";
import TweetEmbed from "xxxxx";
import YouTubeEmbed from "xxxxx";
const CodeBlock = ({ node, className, children, ...props }: CodeProps) => {
// カスタム値の判定材料を抽出
const prefix = "language-";
const classes = className
?.split(" ")
.find((c) => c.startsWith(prefix))
?.replace(prefix, "");
const params = classes ? classes.split(":") : [];
if (params.length > 0 && params[0] === "twitter") {
// Twitter埋め込み
const id = children.toString().replace(/\r?\n/g, "");
return <TweetEmbed id={id} />;
}
if (params.length > 0 && params[0] === "youtube") {
// YouTube埋め込み
const id = children.toString().replace(/\r?\n/g, "");
return <YouTubeEmbed videoId={id} />;
}
// 通常のコンポーネントを返却
return <code className={className}>{children}</code>;
};
export default CodeBlock;
react-markdown
ではcode
タグとして解釈した場合、className
にはprefixとしてlanguage-
が付与されます。今回の例だとlanguage-twitter:a:b
やlanguage-youtube
となります。なのでparams
にてclassName
を分解してオレオレ引数として変換しています。
params
⇨twitter -> [twitter, a, b]
⇨youtube -> [youtube]
children
⇨1234567890
あとはparamsを参照してオレオレ記法として変換するのか、通常のコードブロックとして何もしないのかを振り分けます。
終わり
実際に実装してみたのが下記になります。
今度は自力でUnifiedエコシステムのプラグインを作ってオレオレ記法に対応してみたいです。