🚆

特定の拡張子のファイル変更に応じてGithub Actionsを実行する

2023/04/04に公開

背景

フロントエンドのCI/CD設定と整備が必要となり、その過程で得た知見をまとめます。

前提

  • Typescript
  • Vue.js
  • CI/CDの実現にはGithub Actionsを利用する

これまでの状況

ローカル環境でコミット前(precommit)にlint-stagedを利用し、linterやtranspileのコマンドを実行。処理が正常に完了しないとコミットできない状況でした。

課題感

以下の課題を解決するために、まずはCI/CDの導入を始めました。

  1. コミット時にvue-tscやtscの実行が必要で、待機時間が長く、気軽にコミットやプッシュができない。(体感で約1-2分かかっていました。)
  2. Github上でのCode Suggestを直接コミットする際、チェックが行われない。
  3. ローカル環境のため、実行の可否を任意に操作できる。

実現したい仕様

  1. pushやPR作成時にジョブを実行する。
  2. 特定の拡張子に応じて、実行するジョブ及び、Stepを定義する。(例: .vueファイルが変更された場合はvue-tsc.tsファイルが変更された場合はtscを実行する。)
  3. 変更内容に関わらず、ユニットテストを実行。

コード例(Sample)

以下は実際のYAMLファイルをもとに作成したサンプルです。

name: Github Actions For Front-End
run-name: Executed Github Actions For Front-End
on:
  pull_request:
    types:
      - opened
      - synchronize

permissions:
  contents: read
  pull-requests: read

env:
  TZ: "Asia/Tokyo"

jobs:
  # install-dependenciesによって、node_modulesを取得し、キャッシュしています。
  # キャッシュが使用できる場合は処理をスキップします。
  install-dependencies:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js
        uses: actions/setup-node@v3
        with:
          node-version-file: "package.json"
          cache: npm
      - uses: actions/cache@v3
        id: npm-cache
        with:
          path: node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('**/.node-version') }}-${{ hashFiles('**/package-lock.json') }}
      - name: clean-install if cache-hit
        run: |
          if [ "${{ steps.npm-cache.outputs.cache-hit }}" = "true" ]; then
            echo "skip npm clean-install"
          else
            npm ci
          fi

  get-changed-files:
    runs-on: ubuntu-latest
    outputs:
      changed_files: ${{ steps.changed-files.outputs.all_changed_files }}
      any_changed: ${{ steps.changed-files.outputs.any_changed }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Get changed files
        uses: tj-actions/changed-files@v35.7.1
        id: changed-files
        with:
          files: |
            **/*.{js,jsx,ts,tsx,vue}
          files_separator: "\n"
      - name: Output changed files
        run: echo ${{ steps.changed-files.outputs.all_changed_files }}

  # js,jsx,ts,tsx,vueに対するlintの実行
  # 変更があったファイルにのみ適用します。
  execute-lint:
    runs-on: ubuntu-latest
    needs: [install-dependencies, get-changed-files]
    steps:
      - uses: actions/checkout@v3
      - name: Restore node_modules from cache
        uses: actions/cache@v3
        with:
          path: node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('**/.node-version') }}-${{ hashFiles('**/package-lock.json') }}
      - name: Execution
        if: needs.get-changed-files.outputs.any_changed == 'true'
        run: npm run lint:fix ${{ needs.get-changed-files.outputs.changed_files }}

  # tsc, vue-tscの実行
  # それぞれの拡張子を持つファイルに変更があった場合のみ実行します。
  execute-transpile:
    runs-on: ubuntu-latest
    needs: [install-dependencies, get-changed-files]
    steps:
      - uses: actions/checkout@v3
      - name: Restore node_modules from cache
        uses: actions/cache@v3
        with:
          path: node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('**/.node-version') }}-${{ hashFiles('**/package-lock.json') }}
      - name: Output changed files.
        run: echo ${{needs.get-changed-files.outputs.changed_files}}
      - name: Build if changes in Vue files.
        if: ${{contains(needs.get-changed-files.outputs.changed_files, '.vue')}}
        run: npm run vuetsc
      - name: Build if changes in Typescript files.
        if: ${{contains(needs.get-changed-files.outputs.changed_files, '.ts')}}
        run: npm run tsc

  # ユニットテストの実行
  execute-unit-tests:
    runs-on: ubuntu-latest
    needs: install-dependencies
    steps:
      - uses: actions/checkout@v3
      - name: Restore node_modules from cache
        uses: actions/cache@v3
        with:
          path: node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('**/.node-version') }}-${{ hashFiles('**/package-lock.json') }}
      - name: Execution
        run: |
          npm run test:unit

工夫した点

  1. node_modulesをキャッシュ化し、再利用する。
    • 各ジョブで新規取得すると処理時間がかかるため、一度取得した内容を他のジョブでも利用できるようにする。
  2. 各ジョブの依存関係を整理する。
    • 「Aのジョブ終了後にBの処理を実行する」といった仕様を実現する。
    • needsオプションを利用する。
  3. nodeのバージョンはpackage.jsonに依存させる。
  4. 取得したファイルをもとに、各ジョブで処理の条件分岐や変更ファイルを利用する。
    • 変更されたファイルの取得にはChanged Filesを利用。

成果

  1. 課題の解決には大いに役立ち、コード品質の向上や開発の生産性が向上しました。
  2. node-modulesのキャッシュ化や処理の条件分岐などを実装することで、ワークフロー全体の処理時間を初期の実装に比べて約50%程度削減することができました。

さいごに

これまであまり馴染みのない実装で詰まるところもありましたが、ドキュメントや記事を読みながら何とか作ることができました。(感謝🙏)生産性向上のために色々できることがあると思うので、今後も機会があれば触っていきたいと思います🙆

Discussion