Help us understand the problem. What is going on with this article?

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

この記事は Angular Advent Calendar 2019 の 1 日目の記事です。
今年も 1 日目に飛び込みました @kasaharu です。
今年はなんと Angular #2 Advent Calendar 2019 もあるみたいですよ。

Angular アプリケーションのための GitHub template を作る

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

:white_check_mark: 完成品はこちら

kasaharu/ng-basis

使い方

テンプレートからリポジトリを作成する に書かれている手順で GitHub template からリポジトリを作成します。
リポジトリが作成されたら以下の手順で clone から deploy までを実施します。

$ 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 の作り方です。
kasaharu/ng-basis を template として GitHub リポジトリを作れば以下の作業が必要なくなります!

準備

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

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

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

$ cd ng-basis
$ yarn start

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

:one: Formatter & Linter の導入

なぜ?

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

手順

Formatter にはすっかりデファクトになった Prettier を Linter には TSLintstylelint を採用しました。

Prettier

prettier.png

$ yarn add prettier --dev --exact

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

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

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

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

TSLint

tslint.png

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

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

stylelint

stylelint.png

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

$ 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 は ここ で見ることができます。

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 は ここ で見ることができます。
今回取り上げる 2 つは v9 でデフォルト true に設定されるオプションになります。

fullTemplateTypeCheck

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

:three: テストの設定

unit test 時のオプション

デフォルトでは npm scripts で testng test を実行するようになっているが、ここでは ng test の 3 つのオプションを紹介します。

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

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

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

コードカバレッジ

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

     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 にも記載されています。

unit test

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

@@ -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 に追加します。

     "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 を用意します。

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 は必要なので設定します。

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 にあるように下記のような設定をすることが可能です。

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

:six: Reset CSS

Reset CSS として HTML5 Doctor Reset CSS を使用します。
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

renovate.png

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

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

※ GitHub template をもとに作ったリポジトリに適用するには ここ から設定をする必要があります。

:nine: CircleCI

format, lint, build, test のチェックが自動でできるように CI を導入します。
今回は CircleCI を選択しています。

.circleci/config.yml
version: 2.1

executors:
  ci-executor:
    docker:
      - image: circleci/node:12.13.1-stretch-browsers

references:
  npm_cache_key: &npm_cache_key dependency-npm-{{ checksum "yarn.lock" }}
  save_node_modules: &save_node_modules
    save_cache:
      key: *npm_cache_key
      paths:
        - node_modules
  restore_node_modules: &restore_node_modules
    restore_cache:
      keys:
        - *npm_cache_key

jobs:
  install-packages:
    executor: ci-executor
    steps:
      - checkout
      - run: echo "Install the packages"
      - *restore_node_modules
      - run: yarn install
      - *save_node_modules

  lint:
    executor: ci-executor
    steps:
      - checkout
      - run: echo "Start lint"
      - *restore_node_modules
      - run: yarn lint

  stylelint:
    executor: ci-executor
    steps:
      - checkout
      - run: echo "Start stylelint"
      - *restore_node_modules
      - run: yarn stylelint

  format-check:
    executor: ci-executor
    steps:
      - checkout
      - run: echo "Start check format"
      - *restore_node_modules
      - run: yarn prettier:check

  build:
    executor: ci-executor
    steps:
      - checkout
      - run: echo "Start build"
      - *restore_node_modules
      - run: yarn build

  test:
    executor: ci-executor
    steps:
      - checkout
      - run: echo "Start unit test"
      - *restore_node_modules
      - run: yarn test:ci
      - store_artifacts:
          path: coverage

  e2e:
    executor: ci-executor
    steps:
      - checkout
      - run: echo "Start e2e test"
      - *restore_node_modules
      - run: yarn e2e:ci

workflows:
  version: 2
  build_and_test:
    jobs:
      - install-packages
      - lint:
          requires:
            - install-packages
      - stylelint:
          requires:
            - install-packages
      - format-check:
          requires:
            - install-packages
      - build:
          requires:
            - lint
            - stylelint
            - format-check
      - test:
          requires:
            - lint
            - stylelint
            - format-check
      - e2e:
          requires:
            - lint
            - stylelint
            - format-check

※ GitHub template をもとに作ったリポジトリに適用するには CircleCI - dashboard から Add Projects をする必要があります。

:ten: Deploy

アプリケーションは最終的にデプロイが必要になります。
今回は GitHub Pages にデプロイをすることにします。
GitHub Pages にデプロイするために angular-cli-ghpages というパッケージを使用します。
このパッケージは下記のように $ ng add でインストールが可能です。

$ yarn ng add angular-cli-ghpages

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

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

まとめ

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

明日は @ver1000000 さんです。

参考リンク

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away