はじめに
前回、Docker + Remote Containersを最高だと紹介したわけですが、今回はGridsomeを紹介します。そもそも前回のブログはこれを試すために環境を整備しなおそうとしたのがきっかけでした。なお、前回のブログはこちら。
どこらへんが最高かっていうと全てのデータソースに対してGraphQLでアクセスできるようになるところ。これって最高じゃないですかね。ま、最終的には静的サイトとしてGenerateしてしまうわけですが。
ちなみに、なぜ、今回Gridsomeを試そうと思ったかというと、最近聞いたとあるPodcastで紹介されてたからです。それだけ。
Gridsome
いわゆるJamstackなフレームワーク。Reactで言うところのGatsbyのVue.js版と言ってもいいかと。
サイトにかかれていた特徴をそのままあげます。
- 簡単ローカル開発
- デフォルトで早い
- 端からPWA対応
- Jamstack
- シンプルかつ安全なデプロイ
- SEOフレンドリー
要は簡単便利に扱える静的サイトジェネレータ。あとは各種データソースからのデータをGraphQL使ってアクセスできるようになるっていう徹底したフロントエンド志向。例えばWordpressなんかのデータもGraphQLで扱えるようになっちゃうのって結構いい。それ以外にもあらゆるHeadless CMSやAPI、Markdownで書かれたファイルなんかもデータソースとして扱えるとのこと。このあたりはプラグインとしていっぱい用意されてます。
どんな感じで動くのか
静的サイトジェネレータなのでhtmlとjsonがページごとに生成される。なお、開発中はローカルサーバを起動してホットリローディング可能な環境で開発できます。この開発モードのときはデータに関しては起動時にデータソースに取りに行ってメモリ上に乗せておくみたいです。起動するとGraphQLのプレイグラウンドも利用可能で、それに対してリアルタイムにクエリ投げて試すことが可能です。こんな感じ。
なお、導入には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にはPages
とTemplate
という概念があります。簡単に言うと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クエリですね。フィールドはとりあえず適当に(といっても用意するファイルに存在するもの)。
Templates
はsrc/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
を指定しているのが大きな違い。単一データをクエリするときは引数としてid
かpath
を渡す必要があります。
さて、最後に実際のデータとなるファイルをマークダウンでいくつか用意します。こんな感じのものをコピペで複数用意するといいと思います。もちろん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ドルとお財布にも優しい。
今回はお試しなのでこの3.5ドルのモデル(1vCPU, 512MB, 20GB SSD)で用意。
詳細は割愛しますが、こんな感じでWordPressを選んで
プランを選択するだけです。
これだけでインターネット公開のための細かい設定とかなくWordPressを実行するために必要な環境が揃ったサーバを用意できます。もちろん細かい設定をしていくことも可能です。あとブラウザからSSHでつなぐことも可能。というわけで知っててよかったAWS。
用意されたパブリックIPにブラウザからアクセスするとこんな感じで動いてます。管理画面は普通のWordPress同様/admin
ですね
アクセスにはこのインスンタンスにログインして/home/bitnami/bitnami_credentials
を確認するとわかります。
お次ははてなブログ側からデータをエクスポートします。
これは管理画面から「設定
」>「詳細設定
」とすすんで「エクスポート
」>「記事のバックアップと製本サービス
」でダウンロードします。
WordPress側は管理画面の「ツール
」>「インポート
」>「Movable Type と TypePad
」で「今すぐインストール
」をクリックした後「インポーターの実行
」で先程ダウンロードしたはてなブログ側のデータを指定してインポートします。なお、そのままインポートするとslug
が日付になってしまうので注意。
そしてGridsomeのWordPressプラグインからアクセス可能にするためにはWordPress側でREST APIを有効化する必要があります。これはプラグインを入れます。 ← 最近のWordPressでは最初から有効になってるとのことです。僕の環境ではうまく動かなかったので入れたのですが、通常は不要と思われます。
そして最後にGridsome側に@gridsome/source-wordpress
をnpmもしくはyarnでインストールします。ここではnpmで。
$ npm install @gridsome/source-wordpress --save
ここまでやったらあとはgridsome.config.js
とPages
、Templates
を編集します。
まずは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
を使ってます。
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
して確認します。
まとめ
Vue.js版のGatsbyとも言えるGridsomeは何が一番いいかって各種データソースに対してGraphQLでアクセス可能になることにつきると思います。あとはVue.jsで使える静的サイトジェネレータってところですか。
Vue.jsに馴染みが深くてNuxt.jsほどリッチなものは不要ってときには選択肢の1つにはなるんじゃないでしょうか。