【GitHub Actions】ReactプロジェクトのCI/CDパイプラインを構築してみた


GitHub Actions学習のまとめとして、Create React AppのプロジェクトのCI/CDパイプラインを構築してみました。

使用したnpmパッケージについては以下の記事でまとめています。

プロジェクトの作成

npx create-react-app react-app --use-npmでプロジェクトを作成します。

ワークフローの構成

Git-flowでの運用を想定して、以下のタイミングで実行するワークフローを作成します。

  1. featureからdevelop向けのプルリクを作成したとき
  2. プルリクが承認されてfeatureがdevelopへマージされたとき
  3. developからmaster向けのプルリクを作成したとき
  4. プルリクが承認されてdevelopがmasterへマージされたとき

加えて、リリースされたらSlackに通知するワークフローも作成します。

ワークフローの作成

前項の図でワークフローが4つありましたが、これらを1つのymlファイルに記述します。

ワークフロー①の作成(一部分)

まずは①(featureからdevelop向けのプルリクを作成したとき)のワークフローの内容を記述します。

ci.yml
name: CI
on:
  pull_request:
    branches: [develop] #develop向けのプルリクが作成されたときにジョブを実行

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2 #チェックアウトするアクション
      - name: Use NodeJS
        uses: actions/setup-node@v1 #特定バージョンのnodeを設定するアクション
        with:
          node-version: "12.x"
      - run: npm ci #dependencyのインストール
      - run: npm run format:check #コードフォーマットチェック
      - run: npm test -- --coverage #テスト実行
        env:
          CI: true #現在の環境がCI環境であることを仮定

ここではdependencyのインストール、コードフォーマットチェック、テスト実行を記述しています。
CIにおいては、npm installではなくnpm ciでdependencyのインストールを行います。
npm ciでは、依存関係の更新をせずに整合性チェックと依存パッケージのダウンロードのみを行うため、npm installより高速に動作し、CIで必要なことだけを行うことができます。

CI: trueではテストがCI環境で実行されることを仮定しています。

ワークフロー②の作成

developからworkflowというfeatureブランチをきって、git pushしてdevelop向けのプルリクをつくるとワークフローが実行されます。

ワークフロー②(プルリクが承認されてfeatureがdevelopへマージされたとき)の内容のステップを追記します。

ci.yml
name: CI
on:
  pull_request:
    branches: [develop]
  push:
    branches: [develop] #developにマージされたときに実行

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Use NodeJS
        uses: actions/setup-node@v1
        with:
          node-version: "12.x"
      - run: npm ci
      - run: npm run format:check
      - run: npm test -- --coverage
        env:
          CI: true
      - name: Build Project #ステージング用のビルドを行うステップ
        if: github.event_name == 'push' #pushイベントのときだけ実行
        run: npm run build #ビルド実行
      - run: npm install -g surge #surgeのインストール
      - name: Deploy to Staging #ステージングデプロイを行うステップ
        if: github.event_name == 'push' #pushイベントのときだけ実行
        run: npx surge --project ./build --domain awesome-quiet.surge.sh #デプロイ
        env: #surgeの認証
          SURGE_LOGIN: ${{ secrets.SURGE_LOGIN }} #surge whoamiで取得
          SURGE_TOKEN: ${{ secrets.SURGE_TOKEN }} #surge tokenで取得

developへのマージをトリガにするために、develop向けのpushイベントを追加します。
また、ワークフローは①と②で共有されているので、ビルドやデプロイがpull_requestイベントで実行されないように、if: github.event_name == 'push'という実行条件を追加します。

今回、デプロイにはsurgeというnpmパッケージを使用しています。ログイン名とパスワード(token)を予め作成して、GitHubリポジトリのsecretsに環境変数として登録しておきます。

ワークフローの実行内容を確認すると、developへのプルリク時点ではbuild, deployステップを飛ばしています。

developへマージ(push)するとbuild, deployが実行されています。

dependencyのキャッシュ

ワークフローごとにnpm ci行うのは無駄なので、dependenciesのキャッシュ用のアクションをステップに追加します。

    steps:
      - uses: actions/checkout@v2
      - name: Cache node_modules
        uses: actions/cache@v1
        with:
          path: ~/.npm #キャッシュしたファイルとキーを格納する場所(OSによって異なる)
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} #キャッシュのリストアや保存をするためのキー
          restore-keys: | #キャッシュをリストアするためのキーのリスト
            ${{ runner.os }}-node-

hashFilesを使用することで、depencencyに変更があったときに新しいキャッシュをつくることができます。

最初のワークフローを実行すると、以下のようにキーが生成されます。

Artifact(テストカバレッジ、ビルドファイル)のアップロード

テストとビルドの後にGitHubのActionsタブからArtifactをダウンロードできるようにするため、これらをアップロードするアクションをステップを追加します。

ci.yml
      - run: npm test -- --coverage
        env:
          CI: true
      - name: Upload Test Coverage
        uses: actions/upload-artifact@v1 #artifactをアップロードするアクション
        with:
          name: code-coverage #ダウンロード時の表示名
          path: coverage #アップロードするフォルダのパス
      - name: Build Project
        if: github.event_name == 'push'
        run: npm run build
      - name: Upload Build Coverage
        if: github.event_name == 'push'
        uses: actions/upload-artifact@v1
        with:
          name: build
          path: build

ワークフローを実行すると以下のようにArtifactがアップロードされます。

masterにマージされたときだけ実行するステップ

ワークフロー④(プルリクが承認されてdevelopがmasterへマージされたとき)のときだけ実行されるステップを作成します。

リリースの作成

masterにマージされたときにリリースを作成するステップを追加します。
リリースの作成にはsemantic-releaseを使います。

ci.yml
      - name: Create a Release
        if: github.event_name == 'push' && github.ref == 'refs/heads/master'
        run: npx semanic-release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

また、リリースの画面からbuild.zip, coverage.zipをダウンロードできるようにします。

ci.yml
      - name: ZIP Assets
        if: github.event_name == 'push' && github.ref == 'refs/heads/master'
        run: |
          zip -r build.zip ./build
          zip -r coverage.zip ./coverage

リリースの設定は以下のようになります。

release.config.js
module.exports = {
  branches: "master",
  repositoryUrl: "https://github.com/suzuki0430/react-app",
  plugins: [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    [
      "@semantic-release/github",
      {
        assets: [
          {path: "build.zip", label: "Build"},
          {path: "coverage.zip", label: "Coverage"},
        ],
      },
    ],
  ],
};

codecovへのテストカバレッジのアップロード

codecovはコードのカバレッジを計測し、可視化したりslackに通知したりできる外部サービスです。
envでcodecovで作成したTOKENを指定するだけで、カバレッジレポートをアップロードすることができます。

ci.yml
      - name: Upload Coverage Reports
        if: github.event_name == 'push' && github.ref == 'refs/heads/master'
        run: npx code-coverage
        env:
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

Slack通知用のワークフローの作成

リリースが作成されたときにSlack通知を行うワークフローを作成します。
ci.ymlのCreate releaseステップをトリガにするため、CUSTOM_TOKENを新しくつくります。

リリースの作成(published)をトリガにしたワークフローを以下のように作成します。

release.yml
name: Notify on Release
on:
  release:
    types: [published]

jobs:
  slack-message:
    runs-on: ubuntu-latest
    steps:
      - name: Slack Message
        run: |
          curl -X POST -H 'Content-type: application/json' --data '{"text":"New release ${{ github.event.release.tag_name }} is out, <${{ github.event.release.html_url }}|check it out now.>"}' ${{ secrets.SLACK_WEBHOOK }}

おわりに

GitHub Actionsの使い方が大体わかったので、今度は社内業務で役に立つようなワークフローを作成してみたいと思います。

参考資料