microCMSのリッチエディタをマークダウンとして管理できるライブラリを作成しました
こんにちはhiro08です。microCMSでは主にプロダクト開発をしています。
今年のマージしたPRを眺めると、結構色々開発したなーという感覚なのですが、その中でもリッチエディタの開発は大きく記憶に残っています。機会があればぜひ触ってみてください。
ヘッドレスCMSにおけるWYSIWYGエディタの開発は通常とは違い、少し難しい点があります。それは、管理画面上の見た目とAPIのレスポンスが異なることです。保存しているデータは共通であることに対して、管理画面の見た目と、APIのレスポンスに整合性を持たせて開発する必要がありました。
また、これもヘッドレスCMSならではの機能なのですが、WRITE API (POSTやPATCHでHTML文字列で入稿できる) に対応する要件もありました。この機能を作成した際に、データベースに保存する前に、HTMLを解析してDOMのNodeに変換 -> 特定のデータ形式に変換する処理がありました。その業務経験を生かして、HTMLからマークダウンに変換するパーサを開発したので紹介します。
rich-editor-markdown-parserの紹介
microCMSでマークダウンを扱う際は、テキストエリアに直接マークダウンを書く方法と、拡張フィールドを利用して外部にマークダウンエディタを作成する方法の2つあります。ですが、どちらもエディタを通じてmicroCMSへ画像をアップロードができない点や、管理するのが難しい点がありました。
そこで、リッチエディタでマークダウンを管理できれば便利なのでは?と思い、rich-editor-to-markdown-parser
というライブラリを作成しました。これはHTMLをマークダウンに変換するparserライブラリです。
こんな感じのmicroCMSからのHTML文字列をマークダウンに変換してくれます。これによって、利用側ではHTMLではなく、マークダウンとしてパースすることができます。
// microCMSのレスポンス
<h1>Hello World!</h1><p>This <strong>html</strong> string is <s>convert</s>into <a href="https://exampe.com">markdown.</a></p>
// HTML文字列を変換
# Hello World!\n\nThis **html** string is ~~convert~~ into [markdown.](https://exampe.com)
内部ではHTMLをDONのNodeに変換して、その情報を元にマークダウンに変換しています。例えば、<h1>Hello World!</h1>
をDOMのNodeに変換するとElement
の情報が得られます。HTMLのタグがネストすると、childrenに子要素としてElement
の情報が追加されます。この情報を元に、マークダウンに変換しています。
詳しくはMDNのドキュメントも参考になると思います。
<ref *1> Element {
parent: null,
prev: null,
next: Element {
parent: null,
prev: [Circular *1],
next: null,
startIndex: null,
endIndex: null,
children: [ [Text] ],
name: 'p',
attribs: {},
type: 'tag'
},
startIndex: null,
endIndex: null,
children: [
Text {
parent: [Circular *1],
prev: null,
next: null,
startIndex: null,
endIndex: null,
data: 'Hello World!',
type: 'text'
}
],
name: 'h1',
attribs: {},
type: 'tag'
}
対応しているHTML
基本的に埋め込み以外のリッチエディタ側で対応しているHTMLは対応しています。対応しているHTMLのリストはREADMEを参照してください。また、リッチエディタの操作方法でHTMLの詳細を確認することができます。
オプション
画像とマークダウンのスタイル周りのオプションを用意しました。
Option | Description | Defualt |
---|---|---|
image.size | 画像にwidthとheigthを含むオプションです ex) ?w=1200&h=630 | true |
image.query | 画像のクエリを付けるオプションです ex) ?format=webp | '' |
markStyle.strong | ** or __ | ** |
markStyle.em | * or _ | * |
markStyle.li | * or - or + | * |
markStyle.hr | --- or *** or ___ | --- |
markStyle.pre | ``` or ~~~ | ``` |
ツールバーの制御
microCMSのリッチエディタでは、ツールバーを制御することができます。もし、装飾を適用したくない場合はこちらも活用できます。
react-markdownを使ってパースする
Next.js (App Router) の上にreact-markdownを使ってパースする例を紹介します。必要なライブラリをインストールしてください。remark-gfm
を使えばテーブルなどに対応することができます。
npm i rich-editor-to-markdown-parser microcms-js-sdk react-markdown remark-gfm
こちらコード例の全体像です。CSSなどは別途当てる必要があります。Zennのエディタと同じCSSを当てたい場合は、zenn-editorを参考にしてください。
import { createClient } from "microcms-js-sdk";
import ReactMarkdown from "react-markdown";
import parser from "rich-editor-to-markdown-parser";
export const client = createClient({
serviceDomain: "service_domain",
apiKey: "api_key",
});
const getContent = async () => {
const res = await client.get({
endpoint: "endpoint",
contentId: "content_id",
});
return res.content;
};
export default async function Home() {
const content = await getContent();
// HTMLをマークダウンに変換
const markdown = parser(content);
return (
<div>
<ReactMarkdown remarkPlugins={[remarkGfm]>{markdown}</ReactMarkdown>
</div>
);
}
するとこんな感じで各マークダウンの値を変換してくれます。装飾や画像だけでなく、入れ子のリストやテーブルもうまく表示してくれます。
コードハイライトを実装する
最後にコードハイライトを実装する例を挙げます。react-syntax-highlighter
をインストールしてください。
npm i react-syntax-highlighter
こちらがコード例です。ReactMarkdownのcomponentにSyntaxHighlighterのコンポーネントを指定することでコードハイライトすることができます。
import { createClient } from "microcms-js-sdk";
import ReactMarkdown from "react-markdown";
import parser from "rich-editor-to-markdown-parser";
import remarkGfm from "remark-gfm";
import SyntaxHighlighter from "react-syntax-highlighter";
import { atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs";
import type { HTMLAttributes } from "react";
import type { ExtraProps } from "react-markdown";
export const client = createClient({
serviceDomain: "service_domain",
apiKey: "api_key",
});
const getContent = async () => {
const res = await client.get({
endpoint: "endpoint",
contentId: "content_id",
});
return res.content;
};
export default async function Home() {
const content = await getContent();
const markdown = parser(content);
return (
<div>
<ReactMarkdown
children={markdown}
remarkPlugins={[remarkGfm]}
components={{
code(props: HTMLAttributes<HTMLElement> & ExtraProps) {
const { children, className, node, ...rest } = props;
const match = /language-(\w+)/.exec(className || "");
return match ? (
<SyntaxHighlighter
PreTag="code"
children={String(children).replace(/\n$/, "")}
language={match[1]}
style={atomOneDark}
/>
) : (
<code {...rest} className={className}>
{children}
</code>
);
}
}}
/>
</div>
);
}
Next.jsのサーバーを立ち上げるとコードハイライトができていることが確認できます。
おわりに
今回はrich-editor-markdown-parserの紹介でした。ニッチなライブラリではありますが、利用する機会がありましたらぜひ使ってください。今後もユースケースや良さそうなparserライブラリを作成したら、積極的に公開していきたいです。
ぜひPRやスターを送っていただけたら嬉しいです。
Discussion