ヘッドレス CMS の Contentful のマークダウンテキストに unist を使ってショートコード的な機能を実装してみる

2023.01.20

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

どうも、ベルリンオフィスの小西です。

ヘッドレス CMS の Contentful ではリッチテキスト形式、マークダウン形式など様々なエディターが用意されていますが、特定のテキスト入力をフロント側でカスタマイズしたいケースがあると思います。

イメージとしては WordPress のショートコード的な機能が近いのですが、今回それを Contentful + Gatsby で実装する方法を紹介します。

今回やりたいこと

例えば Contentuful 側のマークダウンエディターで入力された下記のテキストがあるとします。

念願だった[color="#e24040"]マッターホルン[/color]を歩いてきました。

そのまま Contentful 上のプレビューだと↓のような表示になります(当たり前ですが)。

それをフロント側で↓の表示ができるようにします。

前提

今回の機能は gatsby-transformer-remark の子プラグインとして実装しますので、下記のパッケージが必要になります。Gatsby は 4系でも問題ないかと思います。

  • "gatsby": "^5.3.3",
  • "gatsby-source-contentful": "^8.3.1",
  • "gatsby-transformer-remark": "^6.3.2",
  • "react": "^18.2.0",

使用する関連プラグインの理解

作業に着手する前に、今回使用する関連の Gatsby プラグインについて軽く触れておきたいと思います。さっさと実装したいという方は次の「自前プラグインの実装」まで飛んでいただいて大丈夫です。

gatsby-transformer-remarkremark を利用するための Gatsby オフィシャルのパッケージです。

remark は JavaScript でマークダウンテキストを扱うパッケージですが、特徴としてはマークダウンをいったん AST (Abstract Syntax Trees)と呼ばれるツリー状のオブジェクトに変換して、さまざまな処理(例えばマークダウンを HTML に、など)を行いやすくしてくれます。

AST例:

"htmlAst": {
  "type": "root",
  "children": [
    {
      "type": "element",
      "tagName": "p",
      "properties": {},
      "children": [
        {
          "type": "text",
          "value": "念願だった[color=\"#e24040\"]マッターホルン[/color]を歩いてきました。"
        }
      ]
    },
...

gatsby-transformer-remarkgatsby-source-contentful を併用することで、ソースデータとして Contentful から素のマークダウン形式で渡ってくるデータを AST に変換して、色々いじりやすくしてくれてるわけです。

そもそも Gatsby のパッケージには大きく分けて2つあり、ソースの取り込みとデータ変換を目的とするものがあります。

gatsby-source-」から始まるソースプラグイン

ある特定のデータ群を Gatsby のノードに変換して、ページ生成のソースに使えるようにするためのものです。API を通じた外部ソースやローカルの内部ファイルからデータを収集できます。

例えば Shopify からデータを取り込む gatsby-source-shopify , ローカルのファイル群をソース化する gatsby-source-filesystem などがあります。

gatsby-transformer-」から始まる変換プラグイン

ソースプラグインから提供されたデータを、新しいノードやノードフィールドに「変換」して使いやすくします。

例えば gatsby-transformer-json では生の JSON ファイルから JavaScript オブジェクトに変換したり、 gatsby-transformer-sharp では ImageSharp というノードに変換することで GraphQL クエリを通じて画像変換をかけられるようになります。

今回制作するプラグインもテキストの「変換」処理を行うためのものなので、「gatsby-transformer-」の命名に沿って実装したいと思います。

自前プラグインの実装

では、テキストのカスタマイズを実現するプラグインを作っていきます。

1. 自前プラグインの初期化とインストール

Gatsby アプリケーションのルートディレクトリ( src と同じ階層)に plugins というディレクトリを作成し、さらにその中に今回のパッケージ gatsby-remark-textcolor を作成します。

$ cd ~/Gatsbyアプリのルートディレクトリ
$ mkdir -p plugins/gatsby-remark-textcolor
$ cd plugins/gatsby-remark-textcolor

パッケージを初期化して package.json を生成します。色々聞かれますが、そのままでOKです。

$ npm init

package name: (gatsby-remark-textcolor) 
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 

Is this OK? (yes)

Contentful から渡ってくるデータソースのノードに対して変更を加えるため、パッケージをインストールします。 (注: unist-util-visit の新しいメジャーバージョンは ESM であり、Gatsby でまだ完全にサポートされていないため、v2 をインストールする必要があります)

$ npm install unist-util-visit@^2

unist (Universal Syntax Tree)はシンタックスツリーの仕様で、複雑なコードを書かずに AST を扱えるようになります。 unist-util-* という接頭辞のついたオープンソースのパッケージが多数公開されています。

今回使用する unist-util-visit は、変換するノードを特定する関数、AST を変換する関数などを提供してくれます。

その後、プラグイン起動に必要なファイルのみ残しておきます。

$ rm -r node_modules package-lock.json

2. 実装処理

plugins/gatsby-remark-textcolor/index.js

const visit = require("unist-util-visit");

module.exports = ({ markdownAST }, pluginOptions) => {

  const regexMatch = /\[color="(#(?:[0-9a-fA-F]{3}){1,2})"\](.+?)\[\/color\]/g;

  visit(markdownAST, "text", (node) => {

    node.type = "html"
    node.value = node.value.replace(regexMatch, '<span style="color: $1">$2</span>');

  })

  return markdownAST
}

visit では AST を探索(各ノードにそれぞれアクセス)し、記述された処理で変換した値を返します。

visit(tree[, test], visitor[, reverse])

今回のコードでは、markdownAST 内の text ノードをフィルターし、正規表現にマッチする文字列を置換しています。

また HTML タグを認識させるため、node.typetext から html に置き換えています。

なお、pluginOptions は後述の gatsby-config.js で指定するオプション値を受け取れます。

3. アプリケーションへ自前プラグインのインストール

アプリケーションのルートディレクトリに戻り、 gatsby-config.js を開きます。

gatsby-transformer-remark 内のプラグインとしてインストールします。

{
      resolve: `gatsby-transformer-remark`,
      options: {
        plugins: [
          `gatsby-remark-textcolor`,
        ],
      },
    },
...

オプションを指定する場合は下記のような記述になります。先のpluginOptions で受け取れます。

{
      resolve: `gatsby-transformer-remark`,
      options: {
        plugins: [
          {
            resolve: `gatsby-remark-textcolor`,
            options: {
              backgroundColor: true, //あくまで例です。
            },
          },
...

4. 出来栄え

以上で準備が完了したのでアプリを立ち上げます。

$ gatsby develop

できました!

最後に

以上、仕組みをわかっていれば比較的簡単にショートコードを実装することができました。

例えば Contetnful 文中の画像を 自動でデバイス最適化してくれる gatsby-remark-images-contentful も同じ仕組みで、パッケージの中身を見てみると、Contentful の画像パスを含むノードを、最適化された画像 HTML タグに書き換えてくれていたりします。

クラスメソッドでは Contentful, Jamstack のご相談、運用支援を承っています。

ご興味のある方はぜひお問い合わせください。

参考資料