Gridsomeで複数ディレクトリから記事一覧を表示する


※この記事は私のブログからの転載ですので、こちらも良ければご覧ください〜

はじめに

Gridsomeは様々なデータを静的サイトとして表示することができます。今回はサークルのホームページを作成する時にハマった部分の整理も兼ねて、複数のディレクトリ配下にあるマークダウンファイルを一覧表示する方法を書いていきます。

※この記事に載せているのは私なりの解釈なので、なにかご指摘等ありましたらコメント欄か私のGitHubまで連絡いただけると幸いです。

Gridsome概要

Gridsomeについて超ざっくりと概要を紹介しておきましょう。

GridsomeはVueで作られた静的サイトジェネレータです。似たような技術でReactで作られたGatsbyがあります。静的サイトジェネレータは文字通り静的サイト(HTMLなど)の生成に特化したツールで、比較的データのやり取りが頻繁には発生しないWebサイト(ポートフォリオやブログなど)で使われることが多いです。

GridsomeはGatsbyにインスパイアされて開発されている技術で、その仕組みもGatsbyに似たものになっています。CMSやマークダウンファイルなどのデータがGraphQLデータレイヤーと呼ばれるツールに一時的に保存され、このデータレイヤーから最終的に静的サイトが生成されます。Gridsomeの公式サイトにも、データレイヤーに関して以下のような説明がなされています。

The GraphQL data layer is a tool available in development mode. This is where all of the data imported into a Gridsome project is temporarily stored. Think of it as a local database that helps you work faster and better with your data.

出典:The GraphQL data layer

マークダウンデータをインポートする

2020/10/03現在の開発環境は以下の通りです。

Tool Details
Ubuntu 20.04.1 LTS x86_64
Node.js v10.19.0
Yarn 1.22.5
Gridsome v0.7.0

この作業ではNewsProductsについての記事を別々のディレクトリで管理しながら、一括でマークダウンデータをGraphQLデータレイヤーにインポートし、トップページに表示するといった作業を行います。

今回はマークダウンファイルをデータに持っていきたいので、マークダウンファイルをGraphQLデータレイヤーにインポートできるGridsomeプラグインを導入します。@gridsome/vue-remarkというプラグインを、以下のコマンドでぶち込みます。今回はYarnを使用しています。

$ yarn add @gridsome/vue-remark
    yarn add v1.22.5
    [1/4] Resolving packages...
    [2/4] Fetching packages...
    .
    .
    .
    Done in 10.34s.

上記のように@gridsome/vue-remarkの導入が終わったら、gridsome.config.jsに以下のような書き込みを行います。

//gridsome.config.js
module.exports = {
  siteName: 'Gridsome',
  plugins: [
    {
      use: '@gridsome/vue-remark',
      options: {
        typeName: 'NewsPost', // 必須。GraphQL上で扱う型定義
        baseDir: './posts/news', // 記事となるmarkdownファイルを置くディレクトリ
        pathPrefix: '/news', // URLになるパス。必須ではない。
        template: './src/templates/NewsPost.vue' // 記事ページのVueコンポーネントファイルの指定
      }
    },
    {
      use: '@gridsome/vue-remark',
      options: {
        typeName: 'ProductPost',
        baseDir: './posts/products',
        pathPrefix: '/products',
        template: './src/templates/ProductPost.vue'
      }
    }  
  ]
}

Gridsomeでイチからブログを作る - Markdownファイルで記事を作るよりコードを参照

これらの書き方は@gridsome/vue-remarkにしっかりと載っています。この部分は以下のように説明することができます。

  • マークダウンファイルをデータとして利用するには、マークダウンファイルを入れておくディレクトリがそれぞれのGraphQL上の型1つにつき1つ必要。
  • NewsProductのそれぞれに対して、GraphQL上の型・格納するディレクトリ・パス・テンプレートファイルの指定を行っている。
  • 先程導入したプラグインを、NewsProductのそれぞれに対して適用させている。

このgridsome.config.jsが、Gridsomeのプロジェクトのデータ設定になるので、ここは非常に重要となります。この設定一つでデータを読み込めなかったりするので、しっかり記述しましょう。

データの定義が完了したので、次はデータとなるマークダウンファイルを格納するディレクトリを作成します。

mkdir posts/news
mkdir posts/products

2つのnewsディレクトリとproductsディレクトリの中には、以下のような形式でマークダウンファイルを作成します。両方のディレクトリにこの形式のマークダウンファイルがないとエラーを吐きますので、2つともファイルを作成しておきましょう。

---
title: "成功していればここにタイトルが表示されます"
---

## これは最初の投稿です

マークダウンで記事一覧を表示することかできたぞ!やったぜ!  
テンションあがるなァ^〜

こうしてマークダウンファイルを作成しました。---で囲まれた部分は「フロントマター」と呼ばれ、各マークダウンファイルのメタデータの定義・設定を記述できます。フロントマターを記述すると、先程のプラグインによってメタデータがGraphQLデータレイヤーにインポートされます。具体的には

  • title: マークダウンファイルでtitleを記述した部分
  • content: 記事本文

の2つに加え、

  • id: 各マークダウンファイルに紐付けられている一意の文字列

の3つの情報がインポートされています。以降は、これらデータレイヤー上のデータを必要に応じてQuery(日本語で「照会・問い合わせ」)していきます。

投稿一覧ページを作る

次に各ページにGraphQLを記述して、データレイヤー上にインポートされたデータから必要なデータを照会していきます。Index.vueに記述されているデフォルトのコードにGraphQLのデータを照会する部分の項目を追記していきます。なお、<script><style>は本題から外れますし、デフォルト値のままなので割愛しています。

<!-- Index.vue -->
<template>
<Layout>
    <h1>Hello, world!</h1>
    .
    .
    .
    <!-- 以下を記述-->
    <div>
      <h1>News</h1>
      <g-link 
        v-for="news in $page.allNewsPost.edges"
        :key="news.node.id" 
        :to="news.node.path">
          <h2>{{ news.node.title }}</h2>
      </g-link>
    </div>

    <div>
      <h1>Products</h1>
      <g-link 
        v-for="product in $page.allProductPost.edges"
        :key="product.node.id" 
        :to="product.node.path">
          <h2>{{ product.node.title }}</h2>
      </g-link>
    </div>
</Layout>
</template>

<!-- 以下を記述 -->
<page-query>
  query{
    allNewsPost{
      edges{
        node{
          id //各マークダウンファイルに対応する一意の文字列
          title //フロントマター上で記述したメタデータ
          path //プラグインによって設定されたパス
        }
      }
    },
    allProductPost{
      edges{
        node{
          id
          title
          path
        }
      }
    }
  }
</page-query>

ここでは以下のような挙動を意図しています。

  • <page-query>内では、gridsome.config.jsで定義したGraphQLデータ型を参考に、all[型名]という名前で指定した型の持つすべてのデータを指定します。今回ではNewsPost型とProductPost型のデータを全て参照するallNewsPostallProductPostを用いています。このページでは、これらのデータを参照して利用していきます。
  • g-link内では以下の操作を行っています
    • $pageでこのページを範囲として一括で参照しておいて、news in $page.allNewsPost.edgesNewsPost型データのedge以下にあるデータ群にnewsと名付け、v-forで繰り返し表示しています。
    • :keyではv-forをかける際の個々のマークダウンファイルの識別を、:pathではブラウザ表示した際の個々のマークダウンファイルに対応したパスの受け渡しを行っています。
    • news.node.titleで、マークダウンファイルのフロントマターのtitle情報をデータレイヤー上から持ってきて表示しています。このnewsとは、先程g-linkタグ内でnewsと名前をつけたデータ群です。

これらはNewsを対象としていますが、全く同様の操作をProductでも行えば大丈夫です。この操作をすることで、トップページに記事のタイトルが一覧として表示されます。

最後に、マークダウンファイルを表示する時に利用するVueファイルを作成していきます。./src/templates配下にNewsPost.vueというVueファイルを作成します。

<!-- NewsPost.vue -->
<template>
  <article>
    <h1>{{ $page.newsPost.title }}</h1>
    <VueRemarkContent/>
  </article>
</template>

<page-query>
query NewsPost ($id: ID!) {
  newsPost(id: $id) {
    title
    content
  }
}
</page-query>

Gridsomeでイチからブログを作る - Markdownファイルで記事を作るよりコードを参照

ここでは以下の操作を行っています。
- 先程同様、データレイヤー上のデータから<page-query>内でデータを参照します。今回ではNewsPost型のマークダウンファイルデータのうちnewsPostと名前をつけてid: $ididを手がかりにすると指定し、各idに対応したマークダウンファイルのtitle(記事のタイトル:フロントマター内の記述)とcontent(記事本文:フロントマターより下にある記述)を照会しています。
- $page.newsPost.titleで同様にtitleデータを、<VueRemarkContent/>contentデータを表示します(<VueRemarkContent/>はプラグインで定められた記述です)。

ここでちょっとGraphQLの動作について掘り下げてみましょう。上記のNewsPost.vuepage-queryで、新たに$id: ID!なる記述が出てきたかと思います。

GraphQLの公式ドキュメントには、このような記述がなされています。

Variables

So far, we have been writing all of our arguments inside the query string. But in most applications, the arguments to fields will be dynamic: For example, there might be a dropdown that lets you select which Star Wars episode you are interested in, or a search field, or a set of filters.

It wouldn't be a good idea to pass these dynamic arguments directly in the query string, because then our client-side code would need to dynamically manipulate the query string at runtime, and serialize it into a GraphQL-specific format. Instead, GraphQL has a first-class way to factor dynamic values out of the query, and pass them as a separate dictionary. These values are called variables.

出典:Queries and Mutations

この$id: ID!では、ID!型の変数$idを定義しています(!はNull許容)。NewsPost.vueはファイル自体は1つのVueコンポーネントですが、実際に表示するマークダウンファイルは複数個あるうちの1つとなります。そしてこのコンポーネントが指定するGraphQL上のデータも、複数あるマークダウンのうちの1つのみとなります。つまり、コンポーネントに渡されるデータIDが動的なものなります。このように対象となるデータが逐一変化する時のGraphQL上のデータは、単語の頭に$をつけて「変数」として宣言しなければなりません。これに注意して、NewsPost.vueのGraphQLを記述する必要があるんですね〜。

先のIndex.vueが表示の対象としていたマークダウンファイルは、ディレクトリ配下に存在する「全ての」マークダウンファイルでしたので、それらが提供するデータをallを付けることで一括して指定していましたね。それの1個ずつバージョンと考えています。

Productでも全く同様のことを記述していけばよいです。その際は、照会するデータ型がNewsPost型とProductPost型で違うので、別途ProductPost.vueのようなVueテンプレートを作成していくと良いでしょう。

所感

GraphQLが具体的にどんなことをやっているのかを理解できたのはデカいと思います。というか、内容自体がGridsomeというよりGraphQLについての記事になってしまった気がします。GraphQlを除けば、Gridsomeは実質VueみたいなものでしたねKing of 安直。いつの日か断念していたGatsby開発も、GraphQLの挙動を理解した状態でリベンジできるんじゃねとちょっと思いますた。

参考