チラ裏的な技術メモ

Gatsby.jsにMarkdownとAsciidocを共存させる方法

[Gatsby.js][asciidoc]

このブログを作った目的の一つにAsciidocでの技術記事環境の構築がありましたが、気軽なメモ書きではMarkdownも追加使いたいところ。気軽に共存させようとしたら思っていたよりも面倒だったので実現にいたる実装をメモします。

まずGatsbyの仕組みとしてMarkdownとAsciidocを変換するためにそれぞれのtransformerプラグインを利用します。

$ npm install --save gatsby-transformer-remark gatsby-transformer-asciidoc

でインストールしてから、他のプラグインと同様に gatsby-config.js に記述します。

gatsby-config.js
module.exports = {
  // ...
  plugins: [
    `gatsby-transformer-remark`,
    `gatsby-transformer-asciidoc`,
    // ...
  ],
  // ...
}

それぞれのプラグインによってGraphQL Schemaに allMarkdownRemarkallAsciidoc が追加されます。また allFile の中にも childMarkdownRemarkchildAsciidoc が含まれるので現状はこちらを利用しています。

src/pages/index.js
export const pageQuery = graphql`
  query {
    allFile(filter: { sourceInstanceName: { eq: "blog" }, extension: { regex: "/(md|adoc)/" } }) {
      edges {
        node {
          childMarkdownRemark {
            // ...
          }
          childAsciidoc {
            // ...
          }
        }
      }
    }
    // ...
  }
`;
Note
ここでは allFile を利用しましたが、GraphSQLのSchemaに allMarkdownRemarkallAsciidoc をマージした項目を追加するほうが良いかもしれません。 Gatsby - Schema customization を参考に項目をマージするようなresolve関数を定義して項目名を統一した allPost のような項目を追加したほうが扱いやすいかもしれません。

投稿一覧はざっくりと

  1. allFileからedge(node)を取得

  2. nodeをAsciidocかMarkdownか判別して振り分け

  3. AsciidocとMarkdownの構造から、共通の構造になるよう詰め直し

  4. 投稿日付でソート

といった流れで処理しています。TypeScriptなら共通の構造になる型を定義するといい感じ。

コードはこんな感じです。

src/pages/index.js
export default ({ data }) => {
  const pages = data.allFile.edges
    .filter((edge) => {
      // childAsciidocかchildMarkdownRemarkが非undefinedのものに絞り込み(なくても多分大丈夫)
      return edge.node.childAsciidoc || edge.node.childMarkdownRemark;
    })
    .map((edge) => {
      if (edge.node.childAsciidoc) {
        // Asciidoc の場合の処理
        const child = edge.node.childAsciidoc;

        // 同じ構造のオブジェクトに詰め直す
        const item = {
          slug: child.fields.slug,
          title: child.document.title,
          description: child.pageAttributes.description,
          date: child.revision.date,
        };
        return item;
      } else {
        // Markdown の場合の処理
        const child = edge.node.childMarkdownRemark;

        // 同じ構造のオブジェクトに詰め直す
        const item = {
          slug: child.fields.slug,
          title: child.frontmatter.title,
          description: child.excerpt,
          date: child.frontmatter.date,
        };
        return item;
      }
    })
    .sort((a, b) => moment(b.date).diff(moment(a.date)));
    // ...

これでAsciidocとMarkdownで記述した記事ノードが共通のデータ構造に格納された状態になるので、あとは普通に表示していくだけです。

ここまで一覧ページ想定で記述してきましたが、個別ページも同じように共通のデータ構造に詰め直して表示するだけです。

で、AsciidocでもMarkdownでも記述できる状態に整えてあるスターターキットがこちらになります。

よかったら使ってください。もう少し細かい機能を実装したら公式サイトに登録依頼投げたいと思っています。


Written by moomooya