株式会社microCMS
💡

microCMSのリッチエディタをマークダウンとして管理できるライブラリを作成しました

2023/12/19に公開

こんにちはhiro08です。microCMSでは主にプロダクト開発をしています。

今年のマージしたPRを眺めると、結構色々開発したなーという感覚なのですが、その中でもリッチエディタの開発は大きく記憶に残っています。機会があればぜひ触ってみてください。

https://blog.microcms.io/release-richeditor-public-version/

ヘッドレス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ライブラリです。

https://github.com/hiro08gh/rich-editor-to-markdown-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のリッチエディタでは、ツールバーを制御することができます。もし、装飾を適用したくない場合はこちらも活用できます。

APIスキーマのツールバーの画像

react-markdownを使ってパースする

https://github.com/remarkjs/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>
  );
}

するとこんな感じで各マークダウンの値を変換してくれます。装飾や画像だけでなく、入れ子のリストやテーブルもうまく表示してくれます。

rich-editor-to-markdown-parserのパース後の画像

コードハイライトを実装する

最後にコードハイライトを実装する例を挙げます。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やスターを送っていただけたら嬉しいです。

https://github.com/hiro08gh/rich-editor-to-markdown-parser

株式会社microCMS
株式会社microCMS

Discussion