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

※この記事は[私のブログ](https://o-xian.netlify.app/posts/3.html)からの転載ですので、こちらも良ければご覧ください〜

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

※この記事に載せているのは私なりの解釈なので、なにかご指摘等ありましたらコメント欄か[私のGitHub](https://github.com/o-xian999/oaxis)まで連絡いただけると幸いです。
  
## 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.  

![](https://gridsome.org/assets/static/import-data.eb9be63.3e0083b3c8c40a300ab593b006f88025.png)
> 出典：[The GraphQL data layer](https://gridsome.org/docs/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
  
この作業では`News`と`Products`についての記事を別々のディレクトリで管理しながら、一括でマークダウンデータをGraphQLデータレイヤーにインポートし、トップページに表示するといった作業を行います。 
  
今回はマークダウンファイルをデータに持っていきたいので、マークダウンファイルをGraphQLデータレイヤーにインポートできるGridsomeプラグインを導入します。`@gridsome/vue-remark`というプラグインを、以下のコマンドでぶち込みます。今回は`Yarn`を使用しています。

```sh
$ 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`に以下のような書き込みを行います。

```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ファイルで記事を作る](https://blog.solunita.net/posts/develop-blog-by-gridsome-from-scrach-by-markdown/)よりコードを参照  
  
これらの書き方は[@gridsome/vue-remark](https://gridsome.org/plugins/@gridsome/vue-remark)にしっかりと載っています。この部分は以下のように説明することができます。

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

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

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

```
mkdir posts/news
mkdir posts/products
```

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

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

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

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

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

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

の2つに加え、

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

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

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

```Vue
<!-- 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`型のデータを全て参照する`allNewsPost`と`allProductPost`を用いています。このページでは、これらのデータを参照して利用していきます。
- `g-link`内では以下の操作を行っています
  - `$page`でこのページを範囲として一括で参照しておいて、`news in $page.allNewsPost.edges`で`NewsPost`型データの`edge`以下にあるデータ群に`news`と名付け、`v-for`で繰り返し表示しています。
  - `:key`では`v-for`をかける際の個々のマークダウンファイルの識別を、`:path`ではブラウザ表示した際の個々のマークダウンファイルに対応したパスの受け渡しを行っています。
  - `news.node.title`で、マークダウンファイルのフロントマターのtitle情報をデータレイヤー上から持ってきて表示しています。この`news`とは、先程`g-link`タグ内で`news`と名前をつけたデータ群です。

これらは`News`を対象としていますが、全く同様の操作を`Product`でも行えば大丈夫です。この操作をすることで、トップページに記事のタイトルが一覧として表示されます。
    
最後に、マークダウンファイルを表示する時に利用するVueファイルを作成していきます。`./src/templates`配下に`NewsPost.vue`という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ファイルで記事を作る](https://blog.solunita.net/posts/develop-blog-by-gridsome-from-scrach-by-markdown/)よりコードを参照

ここでは以下の操作を行っています。
- 先程同様、データレイヤー上のデータから`<page-query>`内でデータを参照します。今回では`NewsPost`型のマークダウンファイルデータのうち`newsPost`と名前をつけて`id: $id`で`id`を手がかりにすると指定し、各`id`に対応したマークダウンファイルの`title`(記事のタイトル：フロントマター内の記述)と`content`(記事本文：フロントマターより下にある記述)を照会しています。
- `$page.newsPost.title`で同様に`title`データを、`<VueRemarkContent/>`で`content`データを表示します(`<VueRemarkContent/>`はプラグインで定められた記述です)。
  
ここでちょっとGraphQLの動作について掘り下げてみましょう。上記の`NewsPost.vue`の`page-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](https://graphql.org/learn/queries/#variables)  
  
この`$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の挙動を理解した状態でリベンジできるんじゃねとちょっと思いますた。

## 参考
- [Gridsome公式サイト](https://gridsome.org/)
- [@gridsome/vue-remark](https://gridsome.org/plugins/@gridsome/vue-remark)
- [Queries and Mutations | GraphQL](https://graphql.org/learn/queries/)
- [Gridsomeでイチからブログを作る - Markdownファイルで記事を作る | TRIAL AND SPIRAL](https://blog.solunita.net/posts/develop-blog-by-gridsome-from-scrach-by-markdown/)
- [Gridsomeでブログを作る | Qiita](https://qiita.com/ririli/items/ebd57c3c993e0b8403bd)
- [GraphQL入門 - 使いたくなるGraphQL | Qiita](https://qiita.com/bananaumai/items/3eb77a67102f53e8a1ad)
- [ブログはWordPressは辞めて、全て静的サイトジェネレーターに移行したい](https://qiita.com/shinya_sato_qiita/items/03259dc82b7ded62810b)
- [Feature #23 | GitHub](https://github.com/o-xian999/oaxis/pull/23)