なにこれ

Gatsby公式サイトのプラグインの章のまとめ。

プラグイン概要

Gatsbyプラグインは、Gatsbyの全処理を拡張および修正可能です。 例えば下記のようなことができます。

  • 外部コンテンツ(CMS、ファイル、REST APIなど)を追加してGraphQLで扱えるようにする
  • ファイル(Markdown、YAML、CSVなど)データをJSON形式にフォーマットする
  • サードパーティーの機能(Google Analytics, Instagram)をGatsbyで作ったWebサイトに追加する

またnpmパッケージでモジュール化されているため、巨大で複雑なWebサイトでも簡潔に機能追加・管理が可能です。

公開プラグインを検索する

プラグイン一覧と個別仕様は GatsbyのPluginsで確認できます。 公式プラグインとコミュニティ提供のものをあわせると、なんと502個もあるようです。(2018/10/20現在)

プラグインの使い方

プラグインはnpmパッケージで公開されています。 まずはnpmインストールしましょう。

下記はgatsby-transformer-jsonの例

npm install --save gatsby-transformer-json

次にgatsby-config.jsplugins配列に追加してください。

gatsby-config.jsの一部
module.exports = {
  plugins: [`gatsby-transformer-json`],
}

オプションも指定できます。 下記のようにgatsby-config.jsを書き換えましょう。

gatsby-config.jsの一部
module.exports = {
  plugins: [
    // オプションなしで指定する場合は下記のようにプラグイン名を文字列で指定しましょう。
    "gatsby-plugin-react-helmet",
    // オプションありの場合は、オブジェクトで指定します。
    {
      resolve: `gatsby-source-filesystem`,
      // オプションはさらにオブジェクトで指定します。
      options: {
        path: `${__dirname}/src/data/`,
        name: "data",
      },
    },
    {
      resolve: `gatsby-transformer-remark`,
      options: {
        // プラグイン自体を拡張するプラグインを指定できます。
        // その場合はオプションのpluginsにプラグインを定義しましょう。
        plugins: [`gatsby-remark-smartypants`],
      },
    },
    {
      resolve: "gatsby-plugin-offline",
      // 下記のように空オプションの場合は、プラグイン名を文字列で指定した場合と同じです。
      options: {
        plugins: [],
      },
    },
  ],
}

ローカルのプラグインを使う場合

自作プラグインなどは、npm公開せずともpluginsフォルダに配置すると使えます。

gatsby-config.jsの一部
module.exports = {
  plugins: [`gatsby-local-plugin`],
}

他フォルダに起きたいならフォルダパスを指定しましょう。

gatsby-config.jsの一部
module.exports = {
  plugins: [
    {
      resolve: require.resolve(`/path/to/gatsby-local-plugin`),
    },
  ],
}

プラグインの作り方

作成するファイル

※[必須]と記載がないものについては任意です。

  • package.json --- [必須]ローカルプラグインの場合は空オブジェクトでも可。

    • name GraphQLのデータ構造におけるプラグインの識別子です。未指定の時はプラグインのフォルダ名になります。
    • version キャッシュ管理用です。これが変わるとキャッシュがクリアされます。未指定の時は、gatsby- *ファイルの内容のMD5ハッシュになります。ローカルプラグインを開発中の場合は、不用意なキャッシュを避けるため指定しないほうが良いでしょう。
    • keywords 検索用に指定してください。ここでgatsbygatsby-pluginの2つを指定してnpm公開すると、Plugins | Gatsbyのリストに追加できます。
  • 以下3ファイルはプラグインが拡張・修正したい機能に応じて作成してください。

命名規約

タイプごとに推奨する命名規約があります。

ローカルプラグイン

plugins配下にプラグイン名のフォルダを作ります。

plugins
└── my-own-plugin
    └── package.json

この状態ではプラグイン用のgatsby-config.jsを定義していないので、プロジェクトはプラグインを自動認識できません。

プロジェクトのgatsby-config.jsにプラグイン名(フォルダ名)を指定しましょう。

gatsby-config.jsの一部
module.exports = {
  plugins: [
    'my-own-plugin',
  ]
}

なおプラグインのソースコードはBabelでトランスコンパイルされないので注意してください。 新しいJavaScript文法を使いたい場合はプラグインフォルダ配下にsrcフォルダを作って配下にコード格納し、Babelでビルドした資産をプラグインフォルダ配下に出力するなどの工夫が必要です。

どういう場合にプラグインを作るか

なんでもかんでもプラグイン化するわけではありません。
JavaScriptやReact.jsの機能(ライブラリ)を追加する場合などはプラグインを作らなくてよいのです。以下に例を示します。

  • 一般的な機能を提供するJavaScriptパッケージ(lodashaxiosなど)を使う場合
  • React.jsのUIライブラリ(Ant DesignMaterial UIなど)を使う場合
  • 統合可視化ライブラリ(HighchartsD3.jsなど)を使う場合

プラグインはGatsby APIをパッケージ化して最低限の設定で済むようにするのが目的です。 例えばStyled Componentsを使う場合、自分でGatsby APIを使ってサーバーサイドレンダリング対応を組み込めますが、 この対応はプラグイン化すべきです。実際これはGatsby-plugin-styled-componentsというプラグインがあります。

Source PluginとTransformer Plugin

主要なプラグインのタイプの2つにSource PluginとTransformer Pluginがあり両者は連携して機能します。 例えばマークダウンファイルの場合、 Source Pluginのgatsby-source-filesystemがファイルからFileノードを供給し、Transformer Pluginのgatsby-transformer-remarkがFileノードをMarkdownRemarkノードに変換します。

なおFileノードはファイルの生の内容とメディアタイプを含みます。 メディアタイプは必須項目ではありませんが、Source Pluginで生成したノードが生データ(Transformer Plugin未処理のデータ)であることを示す手段です。 メディアタイプでSource PluginとTransformer Pluginの橋渡しを行い、データ読み込みと加工を分離することで、各プラグインを小さく保つことができるのです。

Source Pluginの作り方

npmパッケージとして作成します。package.jsongatsby-node.jsを作りましょう。 gatsby-node.jsは下記のような感じです。

gatsby-config.jsの一部
exports.sourceNodes = async ({ actions }) => {
  const { createNode } = actions
  // データを生成(ここではデータを外部から取得する例を示す)
  const data = await fetch(REMOTE_API)

  // データからノードを生成
  data.forEach(datum => createNode(processDatum(datum)))

  // 生成したノードをリターン
  return
}

NOTE: Gatby APIの実装詳細は、sourceNodescreateNodeを参照してください。 ここでは例示していませんが、gatsby-node-helpersを使うとAPIの実装が簡単になるので記述量が大きくなるようなら、使ってみることをお勧めします。

ノード間参照を定義する

プラグインが出力するノートにおいて、ノード間の参照を定義することでより複雑な構造を定義します。 これらの実現方法は2つあります。

(1) Transformation relationships
gatsby-transformer-remarkでは、Fileノードが親、MarkdownRemarkノードが子という関係を定義し、 親ノードのマークダウン文字列、子ノードでHTMLに変換しています。 これらの関係はcreateParentChildLinkを使って定義します。具体的な実装方法についてはgatsby-transformer-remarkを参照してください。
なお子ノードは親ノードからの派生なので、親ノードが削除時は全ての子ノードが削除されます。

(2) Foreign-key relationships
別々のオブジェクト(型定義も全く別々)を紐付ける方法です。 この場合Transformation relationshipsと異なり、片方のオブジェクトが削除されても関係するオブジェクトは削除されません。

Transformer Plugin

通常のNPMパッケージとして作成します。package.jsongatsby-node.jsを作りましょう。

変換後ノードの型定義

gatsby-node.jsで変換後ノードの型定義をsetFieldsOnGraphQLNodeTypeに指定します。

gatsby-config.jsの一部
exports.setFieldsOnGraphQLNodeType = require(`./extend-node-type`)

NOTE: setFieldsOnGraphQLNodeTypeの詳細はAPIリファレンス参照

キャッシュの取り扱い

変換処理はコストがかかるため、ビルドするたびに作り直さずにすむようにGatsbyのグローバルキャッシュ機能を使います。 キャッシュキーには少なくとも関連リソースのcontentDigestが必要です。 たとえば、gatsby-transformer-remarkは、HTMLノードに下記のようにキャッシュキーを指定しています。

extend-node-type.js
const htmlCacheKey = node =>
  `transformer-remark-markdown-html-${
    node.internal.contentDigest
  }-${pluginsCacheStr}-${pathPrefixCacheStr}`

キャッシュへのアクセスは下記のようにします。

extend-node-type.js
const cachedHTML = await cache.get(htmlCacheKey(markdownNode))

cache.set(htmlCacheKey(markdownNode), html)

プラグインの公開方法

プラグインのpackage.jsonのkeywordにgatsbygatsby-pluginをつけてnpm公開すれば、 最大12時間後にライブラリ一覧のインデックスにいったん追加されます。 その後公式サイトのデイリービルドが走ると、ようやくプラグインが一覧に追加され、 GatsbyのPluginsで見れるようになります。

NOTE: せっかく公開するなら検索しやすいようにgatsbygatsby-plugin以外にもキーワードを指定しましょう。例えばMarkdown MathJax transformerは下記のように指定しています。

"keywords": [
  "gatsby",
  "gatsby-plugin",
  "gatsby-transformer-plugin",
  "mathjax",
  "markdown",
]

まとめ

Gatsbyはプラグイン機構が非常に考えぬかれたスマートな形をしていますね。
機能追加時もnpmインストールしてgatsby-config.jsに設定を追加するだけなのでコードがとてもスッキリするなぁという印象でした。
プラグイン公開時も、npmパッケージでkeywordにgatsbygatsby-pluginをつけるだけというシンプルさも見逃せません。
なお今回Source Plugin Tutorialのページは見ていませんが、 自作プラグインを公開する時はこのページを参考にしたいと思います。