Edited at
Nuxt.jsDay 2

CircleCIを使ってNuxt.jsをGitHub Pagesへデプロイする


やったこと

職場でCircleCIを導入しているプロダクトが増えていることもあり、勉強がてらCircleCIを使ってNuxt.jsをGitHub Pagesにデプロイして公開してみました。

本記事ではその手順について記載しています。



デプロイのプロセスとしてはこのような流れで行うようにしています。

❶. Nuxt.jsのソースをmasterブランチにpushする

❷. CircleCIでlint, testを実施する

❸. CircleCIで公開ファイルを生成して、それをgh-pagesブランチへpushしてサイトを公開

開発環境は以下で行いました。

os: mac

npm: 6.4.1
node: v8.11.1

実際のソースはこちらで公開しています。

GitHub: https://github.com/yagisuke/nuxt-circle-ci

公開したサイト: https://yagisuke.github.io/nuxt-circle-ci


手順

開発手順です。これに沿って記載していきます。

なお、lintやtestの設定はしていますが、今回のミッションは「CircleCIを使ってNuxt.jsをGitHub Pagesにデプロイすること」なので、とりあえず設定したという程度で記載しています。


  1. CircleCIに登録する

  2. Nuxt.jsのプロジェクトを用意する

  3. lintを書いて設定する

  4. testを書いて設定する

  5. CircleCIの実行ファイルを用意する

  6. GitHub Pages用の公開ファイルを生成する

  7. GitHub Pagesへのデプロイ処理を追加する

  8. CircleCIのプロジェクトに追加する

  9. CircleCIに環境変数を設定する

  10. 秘密鍵と公開鍵を登録する

  11. CircleCIのステータスバッヂをつける


CircleCIに登録する

さっそくですが、CircleCIに登録していきます。

それではCicleCIの登録画面を開きます。



GitHubアカウントと連携してアカウントを作成。



CircleCIへの登録が完了しました。


Nuxt.jsのプロジェクトを用意する

続いてGitHubにリポジトリを作り、そこにNuxt.jsのプロジェクトをpushします。

Nuxt.jsは以下のようにVue CLIで準備しました。

$ npm install -g @vue/cli @vue/cli-init

$ vue init nuxt-community/starter-template nuxt-circle-ci
$ cd nuxt-circle-ci
$ npm install
$ npm run dev

起動しておなじみの画面が表示されたら、用意したGitHubのリポジトリにpushしときます。



commit log: https://github.com/yagisuke/nuxt-circle-ci/commit/b23eed087fcf10dd1af752e386bc24bcbcdcec97


lintを書いて設定する

今回はNuxt.jsのドキュメント と同じく、ESLintPrettierを設定しました。

設定内容もNuxt.jsのドキュメントと同じようにしています。ただし、JSのフォーマットは、ダブルクオートよりシングルクオートの方が好みなので、ドキュメントのサンプルに加えて .eslintrc.js"singleQuote": true のオプションを追加しています。詳しくは、下記commit logの.eslintrc.jsを参照ください。

また、package.json のscriptsに、lintコマンドを忘れずに登録しておいてください。

CircleCIのタスクで後ほど使用します。

"scripts": {

...略
"lint": "eslint --ext .js,.vue --ignore-path .gitignore ."
...略
}

commit log: https://github.com/yagisuke/nuxt-circle-ci/commit/d31a11e15e1fd8ba345fd6a898a96a598d72c83d


testを書いて設定する

lintの設定と同様に、testについてもNuxt.jsのドキュメントに揃えてAVAで設定しました。

ドキュメントのサンプルでは、 page/index.vuetest/index.test.js でテストをしていますが、トップページを変更したくなかったので、 page/ava.vuetest/ava.test.js でテストするようにしています。詳しくはcommit logを参照ください。

また package.json のscriptsに、testコマンドを忘れずに登録しておいてください。

CircleCIのタスクで後ほど使用します。

"scripts": {

...略
"test": "ava"
...略
}

commit log: https://github.com/yagisuke/nuxt-circle-ci/commit/23fc386761af0ff15e312ed785d53910e4ee1f8e


CircleCIの実行ファイルを用意する

CircleCIの実行ファイル .circleci/config.yml を用意します。

実行ファイルの中身は以下のように記載し、プロジェクトのrootディレクトリに配置します。

version: 2

jobs:
build:
docker:
- image: circleci/node:8
steps:
- checkout
- restore_cache:
keys:
- npm-{{ checksum "package.json" }}
- run: npm install
- save_cache:
paths:
- node_modules
key: npm-{{ checksum "package.json" }}
- run: npm run lint
- run: npm run test
deploy:
docker:
- image: circleci/node:8
steps:
- checkout
- restore_cache:
keys:
- npm-{{ checksum "package.json" }}
- run: npm install
- run: git config user.name $GITHUB_NAME
- run: git config user.email $GITHUB_EMAIL
- run: npm run generate:gh-pages
- run: npm run deploy:gh-pages
workflows:
version: 2
build-deploy:
jobs:
- build
- deploy:
requires:
- build
filters:
branches:
only: master

commit log: https://github.com/yagisuke/nuxt-circle-ci/commit/0afeb3c163e2ed6f106e09cd4d4a21262367937b

実行ファイルを以下の順にもう少し見ていきます。

1. config.ymlの全体像

2. buildジョブについて

3. deployジョブについて

4. workflowsについて


config.ymlの全体像

ざっくり説明すると、ブランチにソースをpushしたら、lintとtestを実施して、

gh-pagesブランチに公開ファイルをpushするようにしています。



冒頭でも記載しましたが、今回のGitHub Pagesへのデプロイはこのような流れとなっています。

❶. Nuxt.jsのソースをmasterブランチにpushする

❷. CircleCIでlint, testを実施する

❸. CircleCIで公開ファイルを生成して、それをgh-pagesブランチへpushしてサイトを公開

この❷、❸がCircleCIのジョブとなり、このように記載しています。

version: 2

jobs:
# ジョブを登録
build: # ❷のジョブ
# lintを実施する
# testを実施する
deploy: # ❸のジョブ
# 公開ファイルを生成する
# 公開ファイルをgh-pagesブランチへpushする
workflows:
# ❷のジョブと❸のジョブを呼び出す


buildジョブについて

buildジョブでは、package.jsonに定義したlintとtestを実施します。

(ジョブの名前はtestとかにしておけば良かったかも)

build:

docker:
- image: circleci/node:8
steps:
- checkout
- restore_cache:
keys:
- npm-{{ checksum "package.json" }}
- run: npm install
- save_cache:
paths:
- node_modules
key: npm-{{ checksum "package.json" }}
- run: npm run lint
- run: npm run test

まず、lintとtestを実行するためには、nodeが必要なので、docker上にnode環境を作り、そこにソースをチェックアウトして、npm installで必要なパッケージをインストールします。インストールが終わったら、lintとtestを実施してbuildジョブは終了します。

(npmとかyarnはdockerイメージに用意してくれている)

restore_cachesave_cacheについては、npm installでできたnode_modulessave_cacheでキャッシュして、package.jsonに変更がなければ、restore_cacheでキャッシュを再利用しています。


deployジョブについて

deployジョブでは、公開ファイルを生成して、それをgh-pagesブランチへpushしています。

deploy:

docker:
- image: circleci/node:8
steps:
- checkout
- restore_cache:
keys:
- npm-{{ checksum "package.json" }}
- run: npm install
- run: git config user.name $GITHUB_NAME # GitHubのアカウント情報を設定
- run: git config user.email $GITHUB_EMAIL
- run: npm run generate:gh-pages # 公開ファイルを生成
- run: npm run deploy:gh-pages # gh-pagesブランチへpush

「GitHubのアカウント情報を設定」のタスクで指定している$GITHUB_NAME$GITHUB_EMAILは、CircleCIのサイト上で変数を登録することができ、それを使用しています。

また、「公開ファイルを生成」と「gh-pagesブランチへpush」するタスクは、package.jsonに後ほど登録します。これらについては後ほど説明します。


workflowsについて

config.ymlの一番下にあるworkflowsのブロックです。

workflowsは、jobsに指定したジョブを実行をすることができ、またその実行を制御することができます。


ジョブを実行させる

ジョブを実行させるにはこのように書きます。

この場合、「build」と「deploy」ジョブは並列で実行します。

今回は「build -> deploy」の順に実行させたいので、その制御を次で行ないます。

jobs:

build:
...略
deploy:
...略
workflows:
version: 2
build-deploy:
jobs:
- build <-- buildジョブを実行
- deploy <-- deployジョブを実行


実行の順番を制御する

実行の順番を制御するには、requiresを使います。

「build -> deploy」というジョブの順番にしたい場合は下記のように記載します。

ただし、「deploy」ジョブは、masterブランチにpushされた時しか実行して欲しくないので、その制御を次で行います。

jobs:

...略
workflows:
...略
- build
- deploy:
requires: <-- ここ
- build


特定のブランチでしか実行しないようにする

ジョブを実行するかどうかの条件はfiltersで指定し、特定のブランチでしか実行したくないジョブは、branchesに記載します。今回の「deploy」ジョブは、masterブランチでしか実行したくないので、このようになりました。

jobs:

...略
workflows:
...略
- deploy:
...略
filters: <-- ここ
branches:
only: master

workflowsについて詳しく調べたい方は本家の解説や、こちらの記事をオススメします。


GitHub Pages用の公開ファイルを生成する

Nuxt.jsは、アプリケーションをビルドして、ルートごとにHTMLファイルを生成してくれるnuxt generateという便利なコマンドを用意しています。

参照: https://ja.nuxtjs.org/guide/commands/

そのコマンドを実行すれば簡単に公開ファイルを生成できますが、GitHub PagesのURLは、https://<username>.github.io/<repository-name>となるため(カスタムドメインがない場合)、アプリケーションのベースURLに/repository-name/を設定する必要があります。

具体的には以下のようにソースを修正しています。

package.jsonのscripts

"scripts": {

...略
"generate:gh-pages": "DEPLOY_ENV=GH_PAGES nuxt generate"
}

nuxt.config.js

// GitHub Pageへのデプロイ時には、ベースURLを変更している。

var baseRoute = env => (env === 'GH_PAGES' ? '/nuxt-circle-ci/' : '/')

module.exports = {
...
link: [{
rel: 'icon',
type: 'image/x-icon',
href: baseRoute(process.env.DEPLOY_ENV) + 'favicon.ico'
}]
},
router: {
base: baseRoute(process.env.DEPLOY_ENV)
},
...
}

上記のソースは、Nuxt.jsのドキュメントに記載してあるものと一緒ですので、詳しくはそちらをご覧ください。

commit log: https://github.com/yagisuke/nuxt-circle-ci/commit/ca55cd1655e2a08b01c95d18bd0b2cf3c8c05d16


GitHub Pagesへのデプロイ処理を追加する

上記で生成された公開ファイルをgh-pagesブランチへpushする処理を追加していきます。

今回はこちらを参考に、gh-pagesというパッケージを使用してpushするようにしました。

まずは、gh-pagesのインストールを行います。

$ npm install gh-pages --save-dev

次に、package.jsonのscriptsにdeployコマンドを設定します。

"scripts": {

...略
"deploy:gh-pages": "gh-pages -d dist -m 'chore: update [skip ci]' -t"
...略
}

commit log: https://github.com/yagisuke/nuxt-circle-ci/commit/7bd778e1eda378119aa1cca5a9fc39241f7be30a


gh-pagesパッケージのオプションについて

gh-pagesのオプションを見ていきます。


-d dist

pushするディレクトリを指定します。

公開ファイルが配置される dist ディレクトリを指定します。


-m 'chore: update [skip ci]'

コミットメッセージを指定します。

リポジトリにコミットすると、CicleCIのジョブが起動してしまいますが、[skip ci]をコミットメッセージに含めると、起動せずにskipすることができます。


-t

pushするディレクトリ内のファイルを、ドットファイルを含めてpushするように指定しています。

GitHub Pagesは、defaultでjekyllという静的サイトジェネレータを採用しており、そのjekyllが、_(アンダースコア)から始まるディレクトリを読み込まないようにしてしまいます。

参照: https://blog.github.com/2009-12-29-bypassing-jekyll-on-github-pages/



nuxt generateで生成した公開ファイルの中には、_nuxtという_(アンダースコア)から始まるディレクトリがあるため、jekyllを使用して公開するとエラーとなります。それを避けるためにも、nuxt generateがdefaultで生成してくれる.nojekyllというファイルをpushして、jekyllを使用しないようにする必要があります。


CircleCIのプロジェクトに追加する

CircleCIにGitHubのリポジトリを紐づけていきます。

まずは、CircleCIの「Add Projects」画面を開き、作業リポジトリの「Set Up Project」ボタンを押下します。

「Set Up Project」画面が開くので、以下のように設定して、「Start building」ボタンを押下します。

Operating System: Linux

Language: Node



すると、CircleCIの実行ファイルのジョブが起動して、エラーになります。

これは、実行ファイルに定義した$GITHUB_NAME$GITHUB_EMAILが未設定なためです。

また、CircleCIからリポジトリにpushするためには、リポジトリのwrite権限が必要になるのでそちらも後ほど設定していきます。


CircleCIに環境変数を設定する

CircleCIの実行ファイル.circle/config.ymlに設定した$GITHUB_NAME$GITHUB_EMAILの変数をCircleCIに登録していきます。

まずは「Jobs」画面を開いて、リポジトリの歯車アイコンを押下します。



プロジェクトの「Settings」画面が開くので、「Environment Variables」メニューを選び、「Add Variable」ボタンを押下して、変数を登録していきます。



こちらは$GITHUB_NAMEの設定です。



こちらは$GITHUB_EMAILの設定です。



ここで設定した変数は、実行ファイルから直接利用することができます。


秘密鍵と公開鍵を登録する

リポジトリに公開ファイルをpushするためには、CircleCIに秘密鍵を登録し、GitHubに公開鍵を登録する必要があるので、それを行なっていきます。


秘密鍵と公開鍵を作成する

以下のコマンドを叩いて、秘密鍵(id_rsa)公開鍵(id_rsa.pub)を作成しました。

$ ssh-keygen -t rsa -b 4096 -C 任意の文字列 -f ./id_rsa # パスワードは空にすること


GitHubに公開鍵を登録する

先ほど作成した公開鍵(id_rsa.pub)をGitHubに登録していきます。

まずはリポジトリの「settings」を開き、「Deploy keys」画面を開きます。

すでにCircleCIにはread権限が付与されていますが、今回はwrite権限が必要なので、

「Add Deploy key」ボタンから新たに追加します。



公開鍵(id_rsa.pub)のファイルの中身を貼り付け、Allow write accessにチェックをつけて追加してください。



登録が完了したら、元からあったread onlyのKeyは不要になるので削除して、次はCircleCIに秘密鍵を設定します。


CircleCIに秘密鍵を登録する

秘密鍵(id_rsa)をCircleCIに登録していきます。

CircleCIのプロジェクトの「Settings」画面を開いて、「SSH Permissions」メニューを選択します。

「Add SSH Key」ボタンを押下して秘密鍵を登録していきます。



以下のように秘密鍵(id_rsa)を入力して、「Add SSH Key」を押して登録します。

また、GitHubで削除したread権限しかないkeyを削除しときます。

CircleCIのプロジェクトの「Settings」画面を開いて、「Checkout keys」メニューを選択します。

そこで登録されている認証keyの×ボタンを押して削除します。



参考: https://mikoto2000.blogspot.com/2018/05/circleci-sphinx-gh-pages.html#gh-pages-ブランチへ-push-するための設定


CircleCIのステータスバッヂをつける

CircleCIのプロジェクトの「Settings」画面を開いて、「Status Badges」メニューを選択します。



以下のようにして、出力されたEmbed CodeをREADME.mdに追加してpushしました。これで、masterブランチのCircleCIのステータスがREADME.mdに表示されるようになります。

Branch: master

API Token: None
Embed Code: Markdown

commit log: https://github.com/yagisuke/nuxt-circle-ci/commit/a7f56b89cfc27bba8ea613383067571a4778cc87


おわり

CircleCIのジョブが無事に終了すれば、GitHub Pagesが公開されます。

長文でまとまりのない文章になってしまいましたが、私個人としては色々はまって良い勉強の機会とすることができました。

お付き合いいただきありがとうございました。