Nuxt.jsにTypeScriptを導入してCircleCIでGitHub Pagesへデプロイ

初めて「Nuxt.jsにTypeScriptを導入してCircleCIでGitHub Pagesへデプロイ」してみたところ、欲張りすぎたため、いろんなところにハマった記録です。

ちなみに、わたしのスキル感はこんな感じ。初心者が頑張ってみた感じです。

- Nuxt.js: Vue.jsは使っているが、Nuxt.jsは初めて使ってみる
- TypeScript: 難しい型は分からない。。勉強し始めたばかり。
- CircleCI（CI/CD）: 業務ではjenkinsを使用してビルド、デプロイしている。
- GitHub Pages: 存在は知っているが、使ったことはない。

## 概要
1. Nuxt.jsのプロジェクトを作成
    - TypeScriptも勉強中のため、ついでに組み込んでみる
2. CircleCIを使ってビルド、GitHub Pagesへデプロイ

### 開発環境
```
node v10.13.0
yarn v1.21.1
```

## 教科書
基本的には、こちらの記事の手順に従ってやってみました。
CircleCIを使ってNuxt.jsをGitHub Pagesへデプロイする
https://qiita.com/yagi_suke/items/03a2411fdc3137ce657a

Nuxt.jsへのTypeScriptの導入は、こちらの記事に沿って進めました。
Nuxt.js PWA(Progressive Web Apps) のベースアプリをTypeScript対応する
https://riotz.works/articles/lulzneko/2019/05/10/typescripting-base-app-of-nuxtjs-pwa/

### 公式
CircleCI
https://circleci.com/docs/ja/2.0/getting-started/
バージョン 2.0

Nuxt.js
https://ja.nuxtjs.org/guide/
バージョン 2.11.x

Nuxt TypeScript
https://typescript.nuxtjs.org/guide/

## 完成したもの
Github
https://github.com/harada-k/nuxt-typescript
デプロイしたページ
https://harada-k.github.io/nuxt-typescript/

## 実装
### STEP1：Nuxt.jsのプロジェクトの用意
まずは、Nuxtのインストール。
https://ja.nuxtjs.org/guide/installation
`create-nuxt-app` を利用してインストールしました（yarnを使用しました）。

```
$ yarn create nuxt-app <project-name>
```

#### TypeScriptの導入でハマったポイント
次に、TypeScriptを使ってみたいので、こちらの記事を参考にTypeScriptを導入します。
https://riotz.works/articles/lulzneko/2019/05/10/typescripting-base-app-of-nuxtjs-pwa/
Nuxt.jsもTypeScriptも初心者ですが、手順に従ってやってみます。


ところが、うまくいかない・・・（早すぎる躓き）。
Nuxt.jsのバージョンが、記事はv2.6.3、自分はv2.11.0だったのが原因でした。
Nuxt.js、バージョン2.9からTypeScriptのサポートが大幅に変更になったんですね・・・。
https://typescript.nuxtjs.org/guide/

公式の手順に従って進め、なんとか導入完了。
公式のドキュメント、大事！！

vueファイルをTypeScriptに書き換える際は、スクリプトベース（Vue.extend）で実装。

#### eslintの導入でハマったポイント
`tsconfig.json`、`eslint`の導入、設定などは、以下の記事を参考に実装しました。
https://qiita.com/tomopict/items/ced60c44203852faca0a

当初はnodeのバージョンを`11.10.0`にしていたのですが、、、

```
yarn add -D @typescript-eslint/eslint-plugin
```

上記のこちらのコマンドでエラー発生。。

```
error @typescript-eslint/eslint-plugin@2.22.0: The engine "node" is incompatible with this module. Expected version "^8.10.0 || ^10.13.0 || >=11.10.1". Got "11.10.0"
error Found incompatible module.
info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.
```

nodeバージョンは、`"^8.10.0 || ^10.13.0 || >=11.10.1"`でやっておくれ、ということのようです。
`10.13.0`に変更したところ、無事にインストールできました。

ESLint公式にも書いてありますね。。
https://eslint.org/docs/user-guide/getting-started

##### 余談
`yarn install` に `--ignore-engines` オプションを付けると、エラーを無視できるようになるそうです。

https://blog.yukung.org/difference-between-the-behavior-of-yarn-and-npm-for-engine/

もしくは、configにセット。

```
yarn config set ignore-engines true
```

ただし、エラーを無視するだけなので、packageが指定するnodeバージョンで実行したほうが良いと思います。

ちなみに、`package.json`に`engines`を指定すると、nodeなどのバージョンを固定できます。

```json:package.json
{
  "engines": {
    "node": ">=10.14",
    "npm": ">=6",
    "yarn": ">=1"
  }
}
```
逆に言うと、`typescript-eslint`の`packege.json`に、`engines`の指定が入っていた、ということになりますね。
https://github.com/typescript-eslint/typescript-eslint/blob/master/package.json#L51


### STEP2：CircleCIの設定
CircleCIへの登録は、Githubアカウントとの連携のみなので、問題なくクリア。

続いて、CircleCIの実行ファイル`.circleci/config.yml`を作成してみます。

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

#### Docker Images
jenkinsでのビルド、デプロイは日常的に行なっているので、大体のイメージは掴めたのですが、Docker Imageは初めての遭遇。

Dockerとは？から学ぼうと思ったのですが、ぼんやりしたイメージしか掴めませんでした。。
https://www.ogis-ri.co.jp/otc/hiroba/technical/docker/part1.html
https://qiita.com/kotaro-dr/items/b1024c7d200a75b992fc

実際にDockerをインストールして動かしてみると良いとは思いますが、今回の主軸はそこではないので、一旦スキップ。。

開発環境（node.js v10.13.0）と同じimageを使えば良いのではないか？と理解し、CircleCIが提供しているビルド済みイメージから選びました。
https://circleci.com/docs/ja/2.0/circleci-images/#nodejs

#### Nuxt.jsのrouterの設定
```yaml:.circleci/config.yml
run: yarn run generate:gh-pages
```
Nuxt.jsのrouterの設定は、公式の「GitHub Pagesへデプロイするには？」に詳しく書かれています。
https://ja.nuxtjs.org/faq/github-pages/

`package.json`のscriptsに、Github Pages向けにrouter設定してビルドを行うコマンドを追加します。

```json:package.json
"generate:gh-pages": "DEPLOY_ENV=GH_PAGES nuxt-ts generate",
```

このコマンドを、CircleCIのYAMLファイル内で呼び出して実行します。

```yaml:.circleci/config.yml
- run: yarn run generate:gh-pages
```

#### gh-pagesパッケージの導入
`gh-pages`ブランチは、デプロイするものだけ（`generate:gh-pages`コマンドで作成した、distフォルダの中身のみ）が入った状態にしたいので、`gh-pages`パッケージを導入します。
https://www.npmjs.com/package/gh-pages

`package.json`のscriptsに、デプロイジョブを追加します。

```json:package.json
"deploy:gh-pages": "gh-pages -d dist -m 'chore: update [skip ci]' -t",
```

このコマンドを、CircleCIのYAMLファイル内で呼び出して実行します。

```yaml:.circleci/config.yml
- run: yarn run deploy:gh-pages
```

***
config.ymlの記述については、こちらの記事で理解を深めました。
https://qiita.com/gold-kou/items/4c7e62434af455e977c2#configyml
***


#### 秘密鍵と公開鍵を登録する
「CircleCIのプロジェクトに追加する」「CircleCIに環境変数を設定する」は、手順通りでクリア。

次のハマりポイントは、「CircleCIに秘密鍵を登録する」でした。。

```
$ ssh-keygen -t rsa -b 4096 -C 任意の文字列 -f ./id_rsa # パスワードは空にすること
```
Githubに登録する公開鍵はすんなりクリアできたのですが、CircleCIに登録する秘密鍵が、エラーで登録できない！！

原因は、OpenSSH 7.8から、秘密鍵のヘッダの形式が変更になったことにあるようでした。

https://dev.classmethod.jp/server-side/network/openssh78_potentially_incompatible_changes/

> それ以前の秘密鍵は、OpenSSLのPEM形式で作成されていましたが、本バージョンからOpenSSHの形式がデフォルトとなり、秘密鍵のヘッダの形式が大きく変わっています。

PEM形式で作成するには、`-m PEM`オプションをつければOK。

```
ssh-keygen -t rsa -b 4096 -C "任意の文字列" -f ./id_rsa -m PEM
```
これで無事に秘密鍵が登録できました。。

よく見ると、CircleCIのドキュメントにも書いてありました。。。公式ドキュメント大事。。
https://circleci.com/docs/ja/2.0/gh-bb-integration/#github-%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%82%AD%E3%83%BC%E3%81%AE%E4%BD%9C%E6%88%90
> 注意：最近 ssh-keygen は、デフォルトで PEM 形式のキーを生成しないようにアップデートされました。 プライベートキーが -----BEGIN RSA PRIVATE KEY----- で始まらない場合、ssh-keygen -m PEM -t rsa -C "your_email@example.com" でキーを生成すると、強制的に PEM 形式で生成できます。

## まとめ
これで、masterブランチに変更をpushすると、自動でLint、build、deployが行われるようになりました。

Github
https://github.com/harada-k/nuxt-typescript
デプロイしたページ
https://harada-k.github.io/nuxt-typescript/

Lintジョブはmasterに限定していないので、featureブランチを切って修正を行い、リモートにpushした場合も、Lintジョブが走ります。
featureブランチからmasterブランチへPull requestを作成、masterにマージされると、自動的にビルド、Github pagesにデプロイされます。

フロー的にもわかりやすいし、便利です。

ハマりどころが他にもあるとは思いますが、何かのお役に立てたら幸いです。
今回の教訓は、「ハマったら公式！」

## 参考
いまさらだけどCircleCIに入門したので分かりやすくまとめてみた
https://qiita.com/gold-kou/items/4c7e62434af455e977c2

Nuxt v2.9.2でTypeScript, eslint, Prettier環境構築 + VSCodeの設定
https://qiita.com/tomopict/items/ced60c44203852faca0a

デコレータ使わない Vue.js + TypeScript で進んだ「LINEのお年玉」キャンペーン
https://engineering.linecorp.com/ja/blog/vue-js-typescript-otoshidama/

3分でつくる2019年版 Nuxt.js TypeScript 開発環境設定
https://qiita.com/hiropy0123/items/4ee85fdb9b19bc89056f

package.json の engine について npm と yarn の挙動の違い
https://blog.yukung.org/difference-between-the-behavior-of-yarn-and-npm-for-engine/

package.jsonに"engines"を設定すると「このバージョンのNode.jsでしか動かない」を表明できる
https://qiita.com/suin/items/994458418c737cc9c3e8