リポジトリを作ってから一行もコードを書かずに GitHub Pages にデプロイまでできる GitHub template を作った話

この記事は [Angular Advent Calendar 2019](https://qiita.com/advent-calendar/2019/angular) の 1 日目の記事です。
今年も 1 日目に飛び込みました @kasaharu です。
今年はなんと [Angular #2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/angular-2) もあるみたいですよ。

# Angular アプリケーションのための GitHub template を作る
Angular アプリケーションを new したあとにやったほうがいいことを詰め込んで GitHub Pages にデプロイまでできる GitHub template を作ったお話です。
Angular アプリケーション以外でも役に立つことがある(?)かもしれません:grinning:

# :white_check_mark: 完成品はこちら
[kasaharu/ng-basis](https://github.com/kasaharu/ng-basis)

## 使い方
[テンプレートからリポジトリを作成する](https://help.github.com/ja/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template) に書かれている手順で GitHub template からリポジトリを作成します。
リポジトリが作成されたら以下の手順で clone から deploy までを実施します。

```shell
$ git clone [the-repositoryname]
$ cd [the-repositoryname]
$ yarn
$ yarn ng deploy --base-href=/[the-repositoryname]/
```

デプロイ完了後 `https://[your-username].github.io/[the-repositoryname]` にアクセスするとページが表示されます:rocket: 

## :pencil: 何が盛り込まれているか？
ここから先は [kasaharu/ng-basis](https://github.com/kasaharu/ng-basis) の作り方です。
[kasaharu/ng-basis](https://github.com/kasaharu/ng-basis) を template として GitHub リポジトリを作れば以下の作業が必要なくなります！

# 準備
まずは Angular アプリケーションを準備。今回は Angular CLI v9.0.0-rc.3 を使用しており Routing はありで stylesheet format は SCSS を選択して開始しています。

```shell
$ npx @angular/cli@next new ng-basis
// ※ 作成したタイミングの @next が v9.0.0-rc.3 だっただけで @next をつけると v9.0.0-rc.3 になるわけではないです
```

生成されたら以下のコマンドを実行。

```shell
$ cd ng-basis
$ yarn start
```

たったこれだけで Angular アプリケーションが起動します:a: 
準備ができたのでここから ng-basis を使うと設定済みの :ten: を紹介します。

# :one: Formatter & Linter の導入
## なぜ？
チームで開発をすると考え方や経験の差で書き方がバラけて全体の可読性が下がるという問題が出てきがちです。
スペースや改行位置が統一されていない、という粒度のものから循環的複雑度が上がりメンテナンスが困難という問題まで起こる可能性があります。
小さな書き方の差分が議論のもとになってしまうことがあるのでそれらを Formatter & Linter に任せておけば悩みが少なくなると思います。

## 手順
Formatter にはすっかりデファクトになった [Prettier](https://prettier.io/) を Linter には [TSLint](https://palantir.github.io/tslint/) と [stylelint](https://stylelint.io/) を採用しました。

### Prettier
<img width="688" alt="prettier.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28573/dfb1ffed-9c71-1153-0512-1742adac1f77.png">

```shell
$ yarn add prettier --dev --exact
```

フォーマットルールを書くための設定ファイル `.prettierrc.json` とフォーマットしたくないファイルの一覧を書く `.prettierignore` を用意します。
それぞれ下記のような感じで書いています。

- [.prettierrc.json](https://gist.github.com/kasaharu/c4f37609fd2fbded3cfe1170058b0aae)
- [.prettierignore](https://gist.github.com/kasaharu/3472dc64f3a9c6c514e69f8e334a7327)

npm scripts でも実行できるように package.json に下記を追加します。

```json:package.json
  "scripts": {
    ...,
    "prettier": "prettier --write './**/*.{ts,html,scss}'",
    "prettier:check": "prettier --check './**/*.{ts,html,scss}'"
  },
```

デフォルトのコードに Formatter する余地があるので `$ yarn prettier` を実行しておきます。

## TSLint
<img width="601" alt="tslint.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28573/d6e429a8-4f34-8f4c-eacc-3b38d9ea1324.png">

TypeScript でも Linter は ESLint に移行していく流れですが Angular CLI が TSLint を使っているのでまだ Angular way に乗っておきます。
Angular CLI でアプリケーションを作成した場合 TSLint はインストール済みなのでルールの更新をします。
更新後は [tslint.json](https://gist.github.com/kasaharu/476660a92fa9558b9760ec52265d974c) のようにしました。
チームごとにこだわりポイントは異なると思うので改善の余地はあります。
ルールを変更すると既存のコードが lint error になるので `$ yarn lint --fix` で修正しておきます。

:warning: `src/test.ts` は import 順に意味があるので `/* tslint:disable:ordered-imports */` をして TSLint のルールを無効化しておきます:warning: 

## stylelint
<img width="178" alt="stylelint.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28573/3e833cfa-4c87-d0e3-0e03-6f53cc0dd056.png">

SCSS のための Linter には stylelint を導入します。
すぐに使えるようにおすすめの standard ルールもあるのでそれも一緒にインストールします。

```shell
$ yarn add stylelint stylelint-config-standard -D
```

`.stylelintrc` を用意して下記の設定をします。
Angular CLI を使って component を作成するとデフォルトで空のスタイルファイルが生成されるので空のスタイルファイルがあっても許容するように今回は `no-empty-source` のルールを無効化しました。

```:.stylelintrc
{
  "extends": "stylelint-config-standard",
  "rules": {
    "no-empty-source": null
  }
}
```

# :two: CompilerOptions の見直し:hammer_pick:
Angular アプリケーションには 2 つの CompilerOptions があり、一つは TypeScript CompilerOptions でもう一つは Angular CompilerOptions です。
どちらも `tsconfig.json` に書くので必要なルールを追記します。

## TypeScript CompilerOptions
TypeScript の CompilerOptions は [ここ](https://www.typescriptlang.org/docs/handbook/compiler-options.html) で見ることができます。

### strict
TypeScript のルールを一気に厳格にするルールです。以下のルールが有効になります。

ルール名 | ルール
:----- | :-----
noImplicitAny | 暗黙の any を許可しない
noImplicitThis | 暗黙の any を返す this 式を許可しない
alwaysStrict | use strict をつける
strictBindCallApply | 関数の bind, call, apply メソッドのチェックが厳格になる
strictNullChecks | null チェックが厳格になる
strictFunctionTypes | 関数型のチェックが厳格になる
strictPropertyInitialization | class プロパティの初期値のチェックが厳格になる

Angular CLI の v9 からはアプリケーションを `ng new` する際に `--strict` オプションが指定できるようになるので strict な設定が求められているのがわかります。
ref. https://github.com/angular/angular-cli/pull/14905#issuecomment-531896302

### noUnusedLocals / noUnusedParameters
未使用のローカル変数やパラメータがある場合にエラーになるルールが有効になる。

### noImplicitReturns
関数内のすべての条件が返り値を返さないとエラーになるルールが有効になる。


## Angular CompilerOptions
Angular の CompilerOptions は [ここ](https://angular.jp/guide/angular-compiler-options) で見ることができます。
今回取り上げる 2 つは v9 でデフォルト true に設定されるオプションになります。

### fullTemplateTypeCheck
テンプレートファイル(*.html)のバインディング式の検証が有効になるルールです。

# :three: テストの設定
## unit test 時のオプション
デフォルトでは npm scripts で `test` は `ng test` を実行するようになっているが、ここでは `ng test` の 3 つのオプションを紹介します。

| オプション名 | 説明 | 設定値 |
|:-----|:-----|:-----|
| code-coverage | コードカバレッジのレポート | true |
| watch | 監視モード | false |
| progress | ビルド中のログ出力 | false |

angular.json で test architect のオプションを変更することで設定可能だが、今回は npm scripts でオプションを指定しています。
package.json を以下のように変更します。

```diff
-    "test": "ng test",
+    "test": "ng test --code-coverage --watch false --progress false",
```

## コードカバレッジ
上の設定で test 実行後にコードカバレッジが表示されるようになるので、続けて karma.conf.js を下記のように変更してコードカバレッジの閾値を設定します。
test 実行後にコードカバレッジが閾値を下回るとエラー(または警告)を出すようになります。

```diff
     coverageIstanbulReporter: 
       dir: require('path').join(__dirname, './coverage/ng-basis'),
       reports: ['html', 'lcovonly', 'text-summary'],
       fixWebpackSourcePaths: true,
+      thresholds: {
+        emitWarning: false, // true にすると閾値に届いていなくてもエラーにならない
+        global: {
+          statements: 100,
+          lines: 100,
+          branches: 100,
+          functions: 100,
+        },
+      },
     },
```

## CI 用の設定
CI 用に HeadlessChrome で動作するように設定を追加します。
設定方法は [Configure CLI for CI testing in Chrome](https://angular.io/guide/testing#configure-cli-for-ci-testing-in-chrome) にも記載されています。

### unit test
unit test は Karma で実行されるので karma.config.js に設定を追加します。

```diff
@@ -35,6 +35,12 @@ module.exports = function(config) {
     logLevel: config.LOG_INFO,
     autoWatch: true,
     browsers: ['Chrome'],
+    customLaunchers: {
+      ChromeHeadlessCI: {
+        base: 'ChromeHeadless',
+        flags: ['--no-sandbox'],
+      },
+    },
     singleRun: false,
     restartOnFileChange: true,
   });
```

また専用の実行コマンドを npm scripts に追加します。

```diff
     "start": "ng serve",
     "build": "ng build",
     "test": "ng test --code-coverage --watch false --progress false",
+    "test:ci": "yarn test --browsers=ChromeHeadlessCI",
     "lint": "ng lint",
     "e2e": "ng e2e",
     "stylelint": "stylelint './**/*.scss'",
```

### E2E test
E2E test は Protractor で実行されるので専用の protractor-ci.conf.js を用意します。

```js:protractor-ci.conf.js
const config = require('./protractor.conf').config;

config.capabilities = {
  browserName: 'chrome',
  chromeOptions: {
    args: ['--headless', '--no-sandbox']
  }
};

exports.config = config;
```

# :four: generate command のオプション設定
Angular CLI を使っていると component や service の作成に CLI の generate command を使うのが一般的になります。そのため generate 後に行いたい lint fix などがある程度自動でできると DX の向上が見込まれます。

### @schematics/angular:component
| プロパティ | 説明 | 設定可能な値 | デフォルト値 |
|:-----|:-----|:-----|:-----|
| style | CSS プリプロセッサの指定 | css, scss, sass, less, styl | css |
| changeDetection | change detection strategy の指定 | Default, OnPush | Default |
| lintFix | lint --fix の指定 | true / false | false |

component 以外にも class, directive, module, pipe, service にも `lintFix` は必要なので設定します。

```diff:angular.json
@@ -2,6 +2,28 @@
   "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
   "version": 1, 
   "newProjectRoot": "projects",
+  "schematics": {
+    "@schematics/angular:class": {
+      "lintFix": true
+    },
+    "@schematics/angular:component": {
+      "style": "scss",
+      "changeDetection": "OnPush",
+      "lintFix": true
+    },
+    "@schematics/angular:directive": {
+      "lintFix": true
+    },
+    "@schematics/angular:module": {
+      "lintFix": true
+    },
+    "@schematics/angular:pipe": {
+      "lintFix": true
+    },
+    "@schematics/angular:service": {
+      "lintFix": true
+    }
+  },
   "projects": {
     "ng-basis": {
       "projectType": "application",
```

デフォルトでは `projects` -> `ng-basis`(プロジェクト名) 配下に設定されているが Multiple projects などで複数のプロジェクト管理している場合にすべてのプロジェクトに同じ schematics のルールを適用するには今回のようにトップレベルで指定するのが楽です。

# :five: Production build の source map configuration
デフォルトでは source map が出力しない設定になっているが、バンドルはしたくないが出力だけしたいケースがあります。
[Optimization and source map configuration](https://angular.jp/guide/workspace-config#optimization-and-source-map-configuration) にあるように下記のような設定をすることが可能です。

```json:angular.json
"build": {
  ...
  "configurations": {
    "production": {
      ...
      "sourceMap": { "scripts": true, "styles": false, "hidden": true, "vendor": true },
    }
  }
}
```



# :six: Reset CSS
Reset CSS として [HTML5 Doctor Reset CSS](http://html5doctor.com/html-5-reset-stylesheet/) を使用します。
Reset CSS を `src/assets/styles/reset.css` として配置し angular.json の build と test architect の styles で読み込みます。


# :seven: Node.js のバージョン管理
フロントエンド開発をやっていると開発環境で使用する Node.js のバージョン管理も重要になります。
asdf や nodenv を使っていると `.node-version` があると自動的に Node.js のバージョンが切り替わり便利なので `.node-version` を配置します。

```:.node-version
12.13.0
```

# :eight: Renovate
<img width="660" alt="renovate.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28573/f8343734-0417-ff23-d03c-b7f72049c256.png">

フロントエンド開発はパッケージアップデートととの戦いにもなります。
継続的なパッケージアップデートのためにも [Renovate](https://renovate.whitesourcesoftware.com/) を導入します。
Renovate の使い方は https://qiita.com/kasaharu/items/1af74b49e98658cf9a8e にも書いています！

Angular CLI で最初にインストールされているパッケージは Agnular のバージョンアップするさいに `ng update` で上げるほうが Angular way から外れないのでここでは自分で追加でインストールしたもののみを enable にします。
- 例) [renovate.json](https://gist.github.com/kasaharu/eb09b2d0b2bfce3331cb252b0ce98524)

※ GitHub template をもとに作ったリポジトリに適用するには [ここ](https://github.com/marketplace/renovate) から設定をする必要があります。

# :nine: CI
format, lint, build, test のチェックが自動でできるように CI を導入します。
~~今回は CircleCI を選択しています。~~
※ GitHub template をもとに作ったリポジトリに適用するには [CircleCI - dashboard](https://circleci.com/dashboard) から Add Projects をする必要があったので、設定無しで使用できる GitHub Actions に変更しました。
CircleCI を使う場合は [.circleci/config.yml](https://github.com/kasaharu/ng-basis/blob/6dcc5e82b556a2af7a16ad53dc4098f706c96a55/.circleci/config.yml) が参考になると思います。

GitHub Actions の設定は以下

```yml:.github/workflows/ci.yml
name: Node CI

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [12.x]

    steps:
      - uses: actions/checkout@v1

      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}

      - name: Get yarn cache
        uses: actions/cache@v1
        with:
          path: node_modules
          key: ${{ runner.OS }}-yarn-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.OS }}-yarn-

      - name: yarn install
        run: |
          yarn
        env:
          CI: true

      - name: TSLint
        run: |
          yarn lint
        env:
          CI: true

      - name: stylelint
        run: |
          yarn stylelint
        env:
          CI: true

      - name: format
        run: |
          yarn prettier:check
        env:
          CI: true

      - name: build
        run: |
          yarn build --prod
        env:
          CI: true

      - name: unit test
        run: |
          yarn test:ci
        env:
          CI: true

      - name: Archive code coverage results
        uses: actions/upload-artifact@v1
        with:
          name: code-coverage-report
          path: coverage

      - name: e2e test
        run: |
          yarn e2e:ci
        env:
          CI: true
```

# :ten: Deploy
アプリケーションは最終的にデプロイが必要になります。
今回は GitHub Pages にデプロイをすることにします。
GitHub Pages にデプロイするために [angular-cli-ghpages](https://github.com/angular-schule/angular-cli-ghpages) というパッケージを使用します。
このパッケージは下記のように `$ ng add` でインストールが可能です。

```shell
$ yarn ng add angular-cli-ghpages
```

さらにデプロイも簡単で下記のコマンドを実行すると https://kasaharu.github.io/ng-basis にデプロイされます。

```shell
$ yarn ng deploy --base-href=/ng-basis/
```

# まとめ
今回はコードを書かずにデプロイまでできる Angular アプリケーション用の GitHub template を作った話をしました。
その過程でフロントエンド開発をする上で最初にやっておきたいこと、Angular アプリケーションを作っていく上で設定してあると楽になることを盛り込みました。
Angular や各種パッケージは継続的にアップデートしていく予定です。
おわり

明日は @ver1000000 さんです。

# 参考リンク
- [テンプレートからリポジトリを作成する](https://help.github.com/ja/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template)
- [TypeScript Compiler Options](https://www.typescriptlang.org/docs/handbook/compiler-options.html)
- [Angular コンパイラオプション](https://angular.jp/guide/angular-compiler-options)
- [add strict compiler option to workspace tsconfig](https://github.com/angular/angular-cli/pull/14905)
- [Configure CLI for CI testing in Chrome](https://angular.io/guide/testing#configure-cli-for-ci-testing-in-chrome)
- [GitHub Page を使う](https://help.github.com/ja/github/working-with-github-pages)
- [HTML5 Doctor Reset CSS](http://html5doctor.com/html-5-reset-stylesheet/)
- [diescake/igata](https://github.com/diescake/igata)