Vue.jsのサイトジェネレータGridsomeが最高かもしれない

f:id:Keisuke69:20200609112825p:plain

はじめに

前回、Docker + Remote Containersを最高だと紹介したわけですが、今回はGridsomeを紹介します。そもそも前回のブログはこれを試すために環境を整備しなおそうとしたのがきっかけでした。なお、前回のブログはこちら

www.keisuke69.net

どこらへんが最高かっていうと全てのデータソースに対してGraphQLでアクセスできるようになるところ。これって最高じゃないですかね。ま、最終的には静的サイトとしてGenerateしてしまうわけですが。

ちなみに、なぜ、今回Gridsomeを試そうと思ったかというと、最近聞いたとあるPodcastで紹介されてたからです。それだけ。

Gridsome

いわゆるJamstackなフレームワーク。Reactで言うところのGatsbyのVue.js版と言ってもいいかと。

gridsome.org

サイトにかかれていた特徴をそのままあげます。

  • 簡単ローカル開発
  • デフォルトで早い
  • 端からPWA対応
  • Jamstack
  • シンプルかつ安全なデプロイ
  • SEOフレンドリー

要は簡単便利に扱える静的サイトジェネレータ。あとは各種データソースからのデータをGraphQL使ってアクセスできるようになるっていう徹底したフロントエンド志向。例えばWordpressなんかのデータもGraphQLで扱えるようになっちゃうのって結構いい。それ以外にもあらゆるHeadless CMSAPIMarkdownで書かれたファイルなんかもデータソースとして扱えるとのこと。このあたりはプラグインとしていっぱい用意されてます。

どんな感じで動くのか

静的サイトジェネレータなのでhtmlとjsonがページごとに生成される。なお、開発中はローカルサーバを起動してホットリローディング可能な環境で開発できます。この開発モードのときはデータに関しては起動時にデータソースに取りに行ってメモリ上に乗せておくみたいです。起動するとGraphQLのプレイグラウンドも利用可能で、それに対してリアルタイムにクエリ投げて試すことが可能です。こんな感じ。

f:id:Keisuke69:20200608161242p:plain

なお、導入にはNode.jsが必要です。

やってみる

僕の場合はDocker + Remote Containersの環境でやってます。公式ではYarnが推奨されてていますが、今回はひとまずnpmでやってしまいました。なぜならRemote Containersで開発するようのテンプレートプロジェクトで使ってるコンテナにはまだYarnをインストールしていないから。というわけでまずCLIをインストールする。僕の場合は開発用に使ってるコンテナの中にインストール。

$npm init
$npm install --global @gridsome/cli

次にプロジェクトの作成。僕の場合はテンプレートから作ったプロジェクトフォルダ直下に作成したかったのだけどパス指定とかはできないっぽいので一度作成したのち丸ごと移動させた。プロジェクト名は適当に。

$ gridsome create cooool-site 
$ cp -pr ./cooool-site/* ./

なお、Remote ContainersやDockerコンテナなどの環境ではポートの転送設定が必要。Remote Containersの場合はdevcontainer.jsonに以下を追記する。

// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [8080]

そして起動。実行するとホットリローディングできるローカルサーバとGraphQLのデータレイヤが起動されます。

$ gridsome develop

このコマンドを実行すると初期化して、ソースを読み込んで、GraphQLスキーマ作成して、ランタイム用のコードを生成して起動されます。 この状態でブラウザからhttp://127.0.0.1:8080/にアクセスできます。あとhttp://127.0.0.1:8080/___exploreにアクセスすると先ほど述べたGraphQLのプレイグラウンドにアクセスできていろいろ遊べます。スキーマとかは右側のタブ?を開くと確認できるので扱いやすいかと。

データソースとかもろもろ設定してみる

さて、これだけだと単に起動しただけなので少しだけいじってみます。プロジェクトの設定はgridsome.config.jsです。 まずはローカルのマークダウンファイルをデータソースにして試してみようってことでこんな感じにしてみました。iconてのはfaviconとかの画像で、iOSのタッチアイコンの設定もできます。

module.exports = {
  siteName: "Cooooool Site!!!!",
  plugins: [
    {
      use: '@gridsome/source-filesystem',
      options: {
        typeName: 'Post',
        path: './blog/**/*.md',
      }
    }
  ],
  templates: {
    Post: '/blog/:id'
  },
  icon: {
    favicon: "./src/red-skull.png",
    touchicon: {
      src: "./src/red-skull.png",
      sizes: [76, 152, 120, 167],
      precomposed: true,
    },
  },
};

設定としてはここで指定したパス、つまりblog/**/以下のmdファイルを全部データソースとして読み込みこんでPostというスキーマでクエリ可能にして、TemplatesとしてはPost.vueを使ってアクセス時のパスは/blog/:idだよってのを指定してます。

ちなみにドキュメントはまだまだ。例えばローカルファイルのインポート周りの設定について書かれているところがコントリビューション求む的な感じになってたりという状態です。

今回はひとまずローカルファイルをデータソースとして扱うためのプラグインである@gridsome/source-filesystemを設定したけど、プラグインについてはここにいっぱいあるので適宜自分が利用したいデータソースにあわせて選択するのがいいと思います。あとでWordPressも試してみる。

gridsome.org

さて、GridsomeにはPagesTemplateという概念があります。簡単に言うとPagesは言葉通り単一ページのことで、Templateはコレクションを表現する際のテンプレートとなってます。

コレクションというのはいわゆる各種プログラミングで出てくるコレクションみたいなもので、つまり個々のデータの塊ですね。ブログなんかだと投稿のデータが集まったものってイメージです。

触って見た感じでは単一の固定ページはもちろん、投稿記事一覧なんかはPagesとして作るのがいいんだと思う。で、個々の記事はTemplatesを用いて表示させるって感じかな。その他ComponentsとかLayoutって概念もあるけどそれは割愛。ComponentsはVueの単一ファイルコンポーネント(Single File Conponents)ですね。

というわけで記事一覧のページをPagesとして、個々のページをTemplatesとしてそれぞれ用意します。

Pagesはsrc/pages/blog/Index.vueとして用意しました。ちなみにファイル名がURLになります。つまり、/pages/Sample.vueを作ると/sampleでアクセス可能になります。今回はindexだけど。

<template>
  <div>
    <div v-for="edge in $page.posts.edges" :key="edge.node.id">
      <router-link :to="{ path: edge.node.path}" append><h2>{{ edge.node.title }}</h2></router-link>
    </div>
  </div>
</template>

<page-query>
query {
  posts: allBlogPost {
    edges {
      node {
        id
        title
        path
        slug
      }
    }
  }
}
</page-query>

見てのとおりすごくシンプルなものを用意しました。基本的にGridsomeではGraphQLのクエリをページかテンプレートだと<page-query>コンポーネントだと<static-query>で指定します。クエリ結果のデータには$pageでアクセスします。クエリ自体は普通のGraphQLクエリですね。フィールドはとりあえず適当に(といっても用意するファイルに存在するもの)。

Templatessrc/templates/Post.vueとしてこんな感じで。

<template>
  <Layout>
    <div class="article">
      <h1 class="article-title">title: {{ $page.post.title }}</h1>
      <article v-html="$page.post.content" />
      <p>{{ $page.post.path }}</p>
    </div>
  </Layout>
</template>

<page-query>
query Post($id: ID!){
   post(id:$id) {
    id
    title
    content
    path
  }
}
</page-query>

こっちはidを指定しているのが大きな違い。単一データをクエリするときは引数としてidpathを渡す必要があります。

さて、最後に実際のデータとなるファイルをマークダウンでいくつか用意します。こんな感じのものをコピペで複数用意するといいと思います。もちろん1つだけでもいいけど。

保存先はblogというフォルダを作ってそこに放り込みました。src配下ではなくプロジェクトのディレクトリ直下に作ってます。

---
title: "First Post"
description: "The first post written in Markdown"
date: 2019-04-20
id: 1
blog_id: 100
---

# First Post
ここにはFirst Postのコンテンツが入ります。

- リスト1
- リスト2
- リスト3

ローカルファイルをデータソースにするときの肝はtitleとかのメタデータの部分ですね。ここを参照してGraphQLのスキーマが作られます。本文はcontentというフィールドに格納されます。

このあたりでローカルサーバを起動しなおして、http://127.0.0.1:8080/blog/にアクセスすると作成したページが見れるはずです。起動しなおすのはGraphQLの型のデータは起動時に作成されるからですね。ここではconfigいじってるので再起動しないと反映されません。

PagesとかTemplatesのファイルだけであればホットリロードされます。アクセスして表示される一覧には個別ページへのリンクを生成するようにしてるのでそっちも見れると思います。

ほら簡単。

こんなに簡単だとHeadless CMSとの連携とかがみなぎりますね。

ビルドする

実際に公開するには作ったものをベースに静的ファイルを生成します。

gridsome buildでdistの下にHTMLファイルなどが生成されます。

この時点でGraphQLでアクセスしていたデータソースのデータも静的なデータとして吐き出されます。なので生成したらまるっとこれをどこかでホスティングすればOK。

Wordpressをデータソースに設定してみる

とりあえず試すだけならここまででもいいと思うけど、せっかくなのでファイルをソースにするのではなく、WordPressをデータソースにしてみようと思います。

中身のデータがあまりないやつだとクエリするときにつまらないのでちゃんと中身のデータがあるやつがいいなということでWordPressを用意してこのブログのデータをインポートして使おうと思います。

ちなみにWordPressを用意するのには自前でサーバ借りるなどして構築、ロリポップとかShifter的なサービスといった手段があると思いますが、今回はAWSのLightsailってサービスを使って用意しました。

このサービスはAWSといってもVPS的なサービスで、さくっと簡単に事前構成済みのサーバを構築できるというものです。しかもサーバ1台あたり最低スペックだと月3.5ドルとお財布にも優しい。

aws.amazon.com

今回はお試しなのでこの3.5ドルのモデル(1vCPU, 512MB, 20GB SSD)で用意。

詳細は割愛しますが、こんな感じでWordPressを選んで f:id:Keisuke69:20200609101140p:plain

プランを選択するだけです。 f:id:Keisuke69:20200609101156p:plain

これだけでインターネット公開のための細かい設定とかなくWordPressを実行するために必要な環境が揃ったサーバを用意できます。もちろん細かい設定をしていくことも可能です。あとブラウザからSSHでつなぐことも可能。というわけで知っててよかったAWS

用意されたパブリックIPにブラウザからアクセスするとこんな感じで動いてます。管理画面は普通のWordPress同様/adminですね f:id:Keisuke69:20200609101213p:plain

アクセスにはこのインスンタンスにログインして/home/bitnami/bitnami_credentialsを確認するとわかります。

お次ははてなブログ側からデータをエクスポートします。

これは管理画面から「設定」>「詳細設定」とすすんで「エクスポート」>「記事のバックアップと製本サービス」でダウンロードします。

WordPress側は管理画面の「ツール」>「インポート」>「Movable Type と TypePad」で「今すぐインストール」をクリックした後「インポーターの実行」で先程ダウンロードしたはてなブログ側のデータを指定してインポートします。なお、そのままインポートするとslugが日付になってしまうので注意。

そしてGridsomeのWordPressプラグインからアクセス可能にするためにはWordPress側でREST APIを有効化する必要があります。これはプラグインを入れます。 ← 最近のWordPressでは最初から有効になってるとのことです。僕の環境ではうまく動かなかったので入れたのですが、通常は不要と思われます。

ja.wordpress.org

そして最後にGridsome側に@gridsome/source-wordpressをnpmもしくはyarnでインストールします。ここではnpmで。

$ npm install @gridsome/source-wordpress --save

ここまでやったらあとはgridsome.config.jsPagesTemplatesを編集します。

まずはgridsome.config.jsです。pluginsの中身を変更しています。先ほどインストールしたGridsomeのプラグインを指定して、WordPressのURLを指定しておきます。なお、ここではGraphQLの型名をなんとなくblogに変更してます。あとはtemplatesとして新たにBlogPost.vueを用意してそれを使うようにしています。それ以外はひとまずこのままでOKですが、perPageとかconcurrentの値を増やすと環境によってはgridsome developとかする際にメモリ不足で落ちます。

module.exports = {
  siteName: "Cooooool Site!!!!",
  plugins: [
    {
      use: "@gridsome/source-wordpress",
      options: {
        baseUrl: "http://xxx.xxx.xxx.xxx/", // required
        typeName: "blog",
        apiBase: 'wp-json',
        perPage: 10,
        concurrent: 1,
      },
    },
  ],
  templates: {
    BlogPost: "/:year/:month/:day/:slug",
  },
  icon: {
    favicon: "./src/red-skull.png",
    touchicon: {
      src: "./src/red-skull.png",
      sizes: [76, 152, 120, 167],
      precomposed: true,
    },
  },
};

そしてこちらがsrc/pages/blog/Index.vueです。型名が変わったのでそこだけ変更してます。

<template>
  <div>
    <div v-for="edge in $page.posts.edges" :key="edge.node.id">
      <router-link :to="{ path: edge.node.path}" append><h2>{{ edge.node.title }}</h2></router-link>
    </div>
  </div>
</template>

<page-query>
query {
  posts: allBlogPost {
    edges {
      node {
        id
        title
        path
        slug
      }
    }
  }
}
</page-query>

最後にsrc/templates/BlogPost.vueを以下のような感じで作成。これはさっきのPost.vueとほぼ同じで型名だけが違います。

<template>
  <Layout>
    <div class="article">
      <h1 class="article-title">{{ $page.blogPost.title }}</h1>
      <article v-html="$page.blogPost.content" />
      <p>{{ $page.blogPost.path }}</p>
    </div>
  </Layout>
</template>

<page-query>
query BlogPost($path: String){
   blogPost(path:$path) {
     id
     title
     content
  }
}
</page-query>

そして、今回は少しだけ見た目も整えたいと思います。

使ったのは最近話題となりつつあるクラスレスなCSSフレームワークの中からWarter.cssを使ってます。

github.com

Gridsomeでのサイト全体にまたがるCSSの設定はsrc/main.jsに記述することで行なえます。HTMLのheadタグへの追加はhead.link.pushで行います。CSS以外ももちろん追加できます。

import DefaultLayout from '~/layouts/Default.vue'

export default function (Vue, { router, head, isClient }) {
  head.link.push({
    rel: 'stylesheet',
    href: 'https://cdn.jsdelivr.net/gh/kognise/water.css@latest/dist/dark.min.css'
  })
  // Set default layout as a global component
  Vue.component('Layout', DefaultLayout)
}

とこんな感じでできあがったのでgridsome developして確認します。

f:id:Keisuke69:20200609103610p:plain

まとめ

Vue.js版のGatsbyとも言えるGridsomeは何が一番いいかって各種データソースに対してGraphQLでアクセス可能になることにつきると思います。あとはVue.jsで使える静的サイトジェネレータってところですか。

Vue.jsに馴染みが深くてNuxt.jsほどリッチなものは不要ってときには選択肢の1つにはなるんじゃないでしょうか。

©Keisuke Nishitani, 2020   プライバシーポリシー