🍴

microCMSのリッチエディタで取得できる値を、いい感じに処理するライブラリの紹介

2021/07/09に公開

はじめに

microCMS を使っている方はご存知の通り、
API スキーマ設定の際に用いることのできる入力フォームとして、「リッチエディタ」があります。

こちらのように様々なオプションのあるエディタになっており、
データとしては HTML が返ってきて、これひとつでもかなりのユースケースをカバーできます。

ただ、僕自身、取得した HTML データをそのまま使うことはほとんどなく
サーバーで処理をした上で、クライアントビルドに含めることが多いです。

そんなふうに、毎回同じような処理を書いている気がしたので、
その処理をある程度汎用化して、ライブラリとして作成し、
公開をしましたので、ぜひお使いください。

https://www.npmjs.com/package/microcms-richedit-processer

特徴

このライブラリの特徴を紹介します。

HTML データの加工機能について

  • img タグ
    • 遅延読み込み(現在、遅延読み込みライブラリのlazysizesのクラス名をサポート)
    • レスポンシブ画像のサポート(srcSet, sizes 属性によってウインドウ幅に合わせて最適な画像を配信する技術)
    • placeholder 画像の設定
    • imgix パラメータの追加
    • width, height 属性の自動設定
  • iframe タグ
    • 遅延読み込み(現在、遅延読み込みライブラリのlazysizesのクラス名をサポート)
    • レスポンシブ対応
  • pre タグ内の code タグ
    • シンタックスハイライトのためにクラス名の追加(現在、highlight.jsをサポート)
  • 共通
    • クラス名の追加
    • 任意の属性値の追加

HTML データから別データの自動作成機能について

  • 目次リストの作成

使い方

まずはインストールします。

npm i microcms-richedit-processer
# yarn add microcms-richedit-processer

ここでは Next.js を使って解説します。
他フレームワークを使っている場合は適宜読み替えてください。

ビルド時に処理したいので、getStaticProps を使います。

import { GetStaticProps, NextPage } from "next";

import { createTableOfContents, processer } from "microcms-richedit-processer";

type Props = {
  body: string;
  toc: {
    id: string;
    text: string;
    name: string;
  }[];
};

export const getStaticProps: GetStaticProps<Props> = async () => {
  const { contents } = await fetch(
    "https://{サービスID}.microcms.io/api/v1/{エンドポイント}",
    {
      headers: {
        "X-API-KEY": "{APIキー}",
      },
    }
  ).then((res) => res.json());

  // contents.bodyにHTMLデータが取得できる想定です。
  return {
    props: {
      body: await processer(contents.body),
      // オプションを渡す場合
      // body: processer(contents.body, {}),
      toc: createTableOfContents(contents.body),
      // オプションを渡す場合
      // toc: createTableOfContents(contents.body, {}),
    },
  };
};

2つの関数をインポートしています。

processer は HTML データの加工を担当します。
createTableOfContents は目次リストの作成を担当します。

このように処理した文字列データをクライアント側でレンダリングに用いれば OK です!

動作の詳細

以下でそれぞれの動作を解説します。

共通のオプション

それぞれの要素に指定できる共通のオプションを
img タグを例に紹介します。

クラス名の追加

processer(content, { img: { addClassName: ["class01", "class02"] } });
<img
-  src="https://sample.com/image.png"
   alt
+  width="画像の横幅が自動で入ります"
+  height="画像の縦幅が自動で入ります"
+  class="class01 class02 lazyload"
+  data-src="https://sample.com/image.png?auto=format"
+  data-srcset="https://sample.com/image.png?auto=format&w=640 640w, https://sample.com/image.png?auto=format&w=750 750w, https://sample.com/image.png?auto=format&w=828 828w, https://sample.com/image.png?auto=format&w=1080 1080w, https://sample.com/image.png?auto=format&w=1200 1200w, https://sample.com/image.png?auto=format&w=1920 1920w, https://sample.com/image.png?auto=format&w=2048 2048w, https://sample.com/image.png?auto=format&w=3840 3840w"
+  data-sizes="100vw"
/>

任意の属性値の追加

processer(content, {
  img: { addAttributes: { "aria-label": "sampleLabel", "data-id": "dataid" } },
});
<img
-  src="https://sample.com/image.png"
   alt
+  aria-label="sampleLabel"
+  data-id="dataid"
+  width="画像の横幅が自動で入ります"
+  height="画像の縦幅が自動で入ります"
+  class="lazyload"
+  data-src="https://sample.com/image.png?auto=format"
+  data-srcset="https://sample.com/image.png?auto=format&w=640 640w, https://sample.com/image.png?auto=format&w=750 750w, https://sample.com/image.png?auto=format&w=828 828w, https://sample.com/image.png?auto=format&w=1080 1080w, https://sample.com/image.png?auto=format&w=1200 1200w, https://sample.com/image.png?auto=format&w=1920 1920w, https://sample.com/image.png?auto=format&w=2048 2048w, https://sample.com/image.png?auto=format&w=3840 3840w"
+  data-sizes="100vw"
/>

img タグの加工

img タグから紹介します。
まずはデフォルトの動作です。

<img
-  src="https://sample.com/image.png"
   alt
+  data-src="https://sample.com/image.png?auto=format"
+  data-srcset="https://sample.com/image.png?auto=format&w=640 640w, https://sample.com/image.png?auto=format&w=750 750w, https://sample.com/image.png?auto=format&w=828 828w, https://sample.com/image.png?auto=format&w=1080 1080w, https://sample.com/image.png?auto=format&w=1200 1200w, https://sample.com/image.png?auto=format&w=1920 1920w, https://sample.com/image.png?auto=format&w=2048 2048w, https://sample.com/image.png?auto=format&w=3840 3840w"
+  data-sizes="100vw"
+  width="画像の横幅が自動で入ります"
+  height="画像の縦幅が自動で入ります"
+  class="lazyload"
/>

上記のように src 属性で指定した値は data-src 属性に変換され、auto パラメータの format が設定されます。
auto パラメータの詳細
そして、width と height を src 属性で指定した画像 URL から取得して、割り当てます。
また、リッチエディタ上で画像サイズなどを指定した場合、width, height 属性にはその値が設定されますのでご安心ください。

レスポンシブ画像の設定には
[640, 750, 828, 1080, 1200, 1920, 2048, 3840]というデフォルトの配列の値を使用します。
こちらを元に、それぞれの画像サイズの URL が作成され、意識することなくデバイスサイズに応じた画像が配信できます。

レスポンシブ画像に関するオプション

srcset を生成する際に参照するデバイスサイズを変更できます。
また、sizes 属性を変更することもできます。

processer(content, {
  img: { deviceSizes: [640, 1280], sizes: "(min-width: 640px) 1000px, 100vw" },
});
<img
-  src="https://sample.com/image.png"
   alt
+  data-src="https://sample.com/image.png?auto=format"
+  data-srcset="https://sample.com/image.png?auto=format&w=640 640w, https://sample.com/image.png?auto=format&w=1280 1280w"
+  data-sizes="(min-width: 640px) 1000px, 100vw"
+  width="画像の横幅が自動で入ります"
+  height="画像の縦幅が自動で入ります"
+  class="lazyload"
/>

imgix パラメータ

microCMS では画像を取得する際に imgixAPI を使用できます。
その際に指定するパラメータを設定するオプションです。

内部的にts-imgixを使っていて、エディタによる保管が効くので、
typo の危険がかなり薄い状態で指定できます。

processer(content, { img: { parameters: { q: 50, w: 800, h: 600 } } });
<img
-  src="https://sample.com/image.png"
  alt
+  width="800"
+  height="600"
+  data-src="https://sample.com/image.png?auto=format&w=800&h=600&q=50"
+  data-srcset="https://sample.com/image.png?auto=format&w=640&h=480&q=50 640w, https://sample.com/image.png?auto=format&w=750&h=563&q=50 750w, https://sample.com/image.png?auto=format&w=828&h=621&q=50 828w, https://sample.com/image.png?auto=format&w=1080&h=810&q=50 1080w, https://sample.com/image.png?auto=format&w=1200&h=900&q=50 1200w, https://sample.com/image.png?auto=format&w=1920&h=1440&q=50 1920w, https://sample.com/image.png?auto=format&w=2048&h=1536&q=50 2048w, https://sample.com/image.png?auto=format&w=3840&h=2880&q=50 3840w"
+  data-sizes="100vw"
+  class="lazyload"
/>

プレースホルダー画像の設定

画像が読み込まれるまでの間、表示される代替画像を設定するかどうかを選択できます。
こちらの機能を lazysizes プラグインの blur-up と組み合わせることで
インターネット接続が遅いユーザーに対しても、ストレスを軽減させます。

https://github.com/aFarkas/lazysizes/tree/master/plugins/blur-up

processer(content, { img: { placeholder: true } });
<img
-  src="https://sample.com/image.png"
   alt
+  width="画像の横幅が自動で入ります"
+  height="画像の縦幅が自動で入ります"
+  data-srcset="https://sample.com/image.png?auto=format&w=640 640w, https://sample.com/image.png?auto=format&w=750 750w, https://sample.com/image.png?auto=format&w=828 828w, https://sample.com/image.png?auto=format&w=1080 1080w, https://sample.com/image.png?auto=format&w=1200 1200w, https://sample.com/image.png?auto=format&w=1920 1920w, https://sample.com/image.png?auto=format&w=2048 2048w, https://sample.com/image.png?auto=format&w=3840 3840w"
+  data-lowsrc="https://sample.com/image.png?w=50&q=30&blur=10"
+  data-sizes="100vw"
+  class="lazyload"
/>'

遅延読み込みの無効化

レスポンシブ画像の設定、width, height 属性の設定を自動で行うので、
遅延読み込みをしない場合でもこのライブラリは有効です。

processer(content, { img: { lazy: false } });

処理後

<img
-  src="https://sample.com/image.png"
+  src="https://sample.com/image.png?auto=format"
+  srcset="https://sample.com/image.png?auto=format&w=640 640w, https://sample.com/image.png?auto=format&w=750 750w, https://sample.com/image.png?auto=format&w=828 828w, https://sample.com/image.png?auto=format&w=1080 1080w, https://sample.com/image.png?auto=format&w=1200 1200w, https://sample.com/image.png?auto=format&w=1920 1920w, https://sample.com/image.png?auto=format&w=2048 2048w, https://sample.com/image.png?auto=format&w=3840 3840w"
+  sizes="100vw"
   alt
+  width="画像の横幅が自動で入ります"
+  height="画像の縦幅が自動で入ります"
/>

iframe タグの加工

デフォルトで iframe の width と height に基づいてアスペクト比を決定して、親要素の幅に合わせてサイズをレスポンシブにします。

+ <div style="position: relative; padding-bottom: calc(480 / 854 * 100%);">
    <iframe
-    class="embedly-embed"
+    class="embedly-embed lazyload"
-    src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FO1bhZgkC4Gw%3Ffeature%3Doembed&display_name=YouTube&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DO1bhZgkC4Gw&image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FO1bhZgkC4Gw%2Fhqdefault.jpg&key=d640a20a3b02484e94b4b0a08440f627&type=text%2Fhtml&schema=youtube"
     width="854"
     height="480"
     scrolling="no"
     title="YouTube embed"
     frameborder="0"
     allow="autoplay; fullscreen"
     allowfullscreen="true"
+    data-src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FO1bhZgkC4Gw%3Ffeature%3Doembed&display_name=YouTube&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DO1bhZgkC4Gw&image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FO1bhZgkC4Gw%2Fhqdefault.jpg&key=d640a20a3b02484e94b4b0a08440f627&type=text%2Fhtml&schema=youtube"
+    style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;"
   ></iframe>
+ </div>

width, height の指定

microCMS 上で設定した iframe の width,height 属性を上書きしたい場合はこちらのオプションを使ってください。
その値を元にレスポンシブします。

processer(content, { iframe: { width: 960, height: 640 } });
+ <div style="position: relative; padding-bottom: calc(640 / 960 * 100%);">
    <iframe
-    class="embedly-embed"
+    class="embedly-embed lazyload"
-    src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FO1bhZgkC4Gw%3Ffeature%3Doembed&display_name=YouTube&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DO1bhZgkC4Gw&image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FO1bhZgkC4Gw%2Fhqdefault.jpg&key=d640a20a3b02484e94b4b0a08440f627&type=text%2Fhtml&schema=youtube"
-    width="854"
+    width="960"
-    height="480"
+    height="640"
     scrolling="no"
     title="YouTube embed"
     frameborder="0"
     allow="autoplay; fullscreen"
     allowfullscreen="true"
+    data-src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FO1bhZgkC4Gw%3Ffeature%3Doembed&display_name=YouTube&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DO1bhZgkC4Gw&image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FO1bhZgkC4Gw%2Fhqdefault.jpg&key=d640a20a3b02484e94b4b0a08440f627&type=text%2Fhtml&schema=youtube"
+    style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;"
   ></iframe>
+ </div>

遅延読み込みの無効化

親要素の幅に合わせて自動でサイズをレスポンシブにするので
遅延読み込みをしない場合でもこのライブラリは有効です。

processer(content, { iframe: { lazy: false } });
+ <div style="position: relative; padding-bottom: calc(480 / 854 * 100%);">
    <iframe
     class="embedly-embed"
     src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FO1bhZgkC4Gw%3Ffeature%3Doembed&display_name=YouTube&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DO1bhZgkC4Gw&image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FO1bhZgkC4Gw%2Fhqdefault.jpg&key=d640a20a3b02484e94b4b0a08440f627&type=text%2Fhtml&schema=youtube"
     width="854"
     height="480"
     scrolling="no"
     title="YouTube embed"
     frameborder="0"
     allow="autoplay; fullscreen"
     allowfullscreen="true"
+    style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;"
   ></iframe>
+ </div>

pre > code タグの加工

デフォルトではオフになっているので、オプションでオンにして使用できます。
また、その際はシンタックスハイライトに用いるライブラリを適宜インストールする必要があります。
(現在は highlight.js のみ)

npm i highlight.js
# yarn add highlight.js
processer(content, { code: { enabled: true } });
<pre>
   <code>
-  import { AppProps } from &#x27;next&#x2F;app&#x27;\n\nconst MyApp = ({ Component, pageProps }: AppProps): JSX.Element =&gt; {\n  return &lt;Component {...pageProps} &#x2F;&gt;\n}\n\nexport default MyApp
+  <span class="hljs-keyword">import</span> { AppProps } from <span class="hljs-string">&#x27;next/app&#x27;</span>\n\n<span class="hljs-keyword">const</span> MyApp = ({ Component, pageProps }: AppProps): JSX.<span class="hljs-built_in">Element</span> =&gt; {\n  <span class="hljs-keyword">return</span> &lt;Component {...pageProps} /&gt;\n}\n\n<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> MyApp
   </code>
</pre>

また、ハイライトに使うテーマの css はご自身でバンドルしてください。
ご参考までに一例を記述します。

index.tsx
import "highlight.js/styles/github-dark.css";

import { GetStaticProps, NextPage } from 'next'
import { processer } from "microcms-richedit-processer";

type Props = {
  body: string;
};

export const getStaticProps: GetStaticProps<Props> = async () => {
  const { contents } = await fetch(
    "https://{サービスID}.microcms.io/api/v1/{エンドポイント}",
    {
      headers: {
        "X-API-KEY": "{APIキー}",
      },
    }
  ).then((res) => res.json());

  return {
    props: {
      body: await processer(contents.body, {
        code: { enabled: true }
      }),
    },
  };
};

const IndexPage: NextPage<Props> = ({ body }) => {
  return (
    <>
      <style global jsx>{`
      .content pre > code {
        display: block;
        padding: 1rem;
      }
      `}</style>
      <div className="content" dangerouslySetInnerHTML={{ __html: body }} />
    </>
  )
}

createTableOfContents のデフォルト動作

目次リストを作成する createTableOfContents の動作を紹介します。
作成されるリストは microCMS 公式で紹介されている目次リストと同じ形式となっています。
https://blog.microcms.io/create-table-of-contents

元となる HTML データ

<h1 id="h98a35185af">What is Lorem Ipsum?</h1>
<p>
  Lorem Ipsum is simply dummy text of the printing and typesetting industry.
</p>
<h2 id="hf76e6834d0">Where does it come from?</h2>
<p>Contrary to popular belief, Lorem Ipsum is not simply random text.</p>
<h3 id="h1c03416cd7">Why do we use it?</h3>
<p>
  It is a long established fact that a reader will be distracted by the readable
  content of a page when looking at its layout.
</p>
<h4 id="rdAK6TEAQqx">Where can I get some?</h4>

作成されるリスト

[
  { id: "h98a35185af", text: "What is Lorem Ipsum?", name: "h1" },
  { id: "hf76e6834d0", text: "Where does it come from?", name: "h2" },
  { id: "h1c03416cd7", text: "Why do we use it?", name: "h3" },
];

createTableOfContents のオプションを指定した時の動作

目次に利用する見出しタグの変更

createTableOfContents(content, { tags: "h2, h4" });
[
  { id: "hf76e6834d0", text: "Where does it come from?", name: "h2" },
  { id: "rdAK6TEAQqx", text: "Where can I get some?", name: "h4" },
];

name キーの無効化

createTableOfContents(content, { dataForName: false });
[
  { id: "h98a35185af", text: "What is Lorem Ipsum?" },
  { id: "hf76e6834d0", text: "Where does it come from?" },
  { id: "h1c03416cd7", text: "Why do we use it?" },
];

こちらのオプションは現在はtagNamefalseのみですが、
microCMS のアップデートで見出しタグに何か意味づけができるものが追加されたときは
そちらの指定もできるように機能追加予定です。

おわりに

HTML データの処理は Jamstack なサイトを作る時に地味に面倒な処理だったりします。
その辺りをライブラリ側に隠蔽することで、ソースコードもスッキリするかと思います!

ぜひ使ってみてください!

URLs

https://www.npmjs.com/package/microcms-richedit-processer
https://github.com/dc7290/microcms-richedit-processer#readme

余談 ちょっとした後悔

今回作ったライブラリの名前が

microcms-richedit-processer

なのですが、もしかすると意味的には
processer ではなく processor かもしれないと思ったのです・・・

しかし、気づいた時にはすでに遅く、
まあ、意味は通じるしいいだろうという感じです。

Discussion