VercelとGitHubActionsでフロントエンドのパフォーマンスを自動計測したい

## やりたいこと
Pull Requestを出した時にVercelが自動生成するプレビューサイトのパフォーマンスを、lighthouseで自動計測すること

## 技術スタック
- Vercel
- GitHubActions

## 元コードの実装の流れ
基本的には以下の記事を参考にしつつ、一部修正する部分を紹介します。

https://dev.to/oskarahl/automated-lighthouse-score-on-your-pr-with-vercel-and-github-actions-2ng2

まずは元のコードがどのように実装されているかをポイントごとに見ていきます。
コードを読めば理解できるという方は以下のリンクからご覧になってください。
読んでみて特に疑問点などなければこのパートはスキップしていただいて問題ありません。

完成したコードも下に掲載していますのでお急ぎの方はそちらをご覧ください。

https://github.com/OskarAhl/Lighthouse-github-action-comment/blob/main/.github/workflows/lighthouse-on-vercel-preview-url.yml

### ステップ１：トリガー
```yml
on:
  issue_comment:
    types: [edited]
```
まずこのワークフローが実行される時はPull Requestのコメントが編集された時です。
その理由は、Vercelのプレビュー生成のフローを見てみるとわかりやすいです。

1. コメントがつく
    Pull Requestを出した際にまず最初にVercelがコメントをつけます。
    ![スクリーンショット 2023-12-09 17.47.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3025861/31134717-2362-3b37-9986-270d056d7848.png)
    
    ここからプレビューのビルドが開始されるというわけですね。
　
2. ビルドが完了する
    ビルドが終わり次第先ほどのコメントが編集されて以下のようになります。
    ![スクリーンショット 2023-12-09 17.48.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3025861/c98ed09e-b87c-d597-f3cc-6bfc5048c67c.png)
    
    こうして完全なプレビューが見れるようになるというのが一連の流れです。
    
    このようにPreviewが生成されるのですが、私たちがパフォーマンスを確認したいのはビルドが完了して完全な状態になった後のPreviewなので、Pull Requestを出し始めた瞬間に計測しにいっても意味がないわけです。
    
    そのためビルドが完了し、Previewが完全な状態となった瞬間である、「Pull Requestのコメントが編集された時」にトリガーされる設定となっています。
    　
    ですが逆にコメントが編集された時ならいつでもトリガーされるのでその点には注意が必要です(コメントした人が誰かで条件分岐するなどの対策はありますが)

:::note warn
このトリガーはデフォルトブランチにコードが存在している時にしかトリガーされません。
作業ブランチから動作確認することができないので、毎回デフォルトブランチにマージして動作確認するようにしてください。
:::

### ステップ２：プレビューURLの取得
```yml
 - name: Capture Vercel preview URL
    id: vercel_preview_url
    uses: aaron-binary/vercel-preview-url-action@v0.0.3
    with:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
次に重要となるのはプレビューURLの取得です。

方法は至ってシンプルで、先ほど登場したVercelの自動コメントを読み取ってリンクを抽出するというものです。

これに関しては１行書くだけで中身を意識する必要はありません。

### ステップ３：lighthouseによるパフォーマンス計測
```yml
  - name: Audit preview URL with Lighthouse
    id: lighthouse_audit
    uses: treosh/lighthouse-ci-action@v3
    with:
      urls: |
        ${{ steps.vercel_preview_url.outputs.vercel_preview_url }}
      uploadArtifacts: true
      temporaryPublicStorage: true
```
プレビューURLが取得できたら、そのURLを使っていよいよパフォーマンスの計測を行なっていきます。

これもライブラリにURLを渡すだけなので特筆することはありません。

残りのステップは文字の整形などなので割愛します。

## 修正箇所
以上が元コードの説明なのですが、現在(少なくとも僕の環境では)このコードは動作しません。
ここからは、そんな元コードを何点か修正して動作するようにしていきます。
### 使用するライブラリの修正
これは動作するために必須かと言われると微妙なんですが、ライブラリの修正を行います。
具体的には以下の通りです。

:::note warn
vercel-preview-url-actionに関してはライブラリそのものが変更されていることに注意してください。スター数やメンテナンスの頻度的にこちらが良いと判断しました。
:::

:::note warn
バージョンは執筆当時のものですので適宜バージョンを変更してください
:::

<table>
  <caption>対応表</caption>
  <thead>
    <tr>
      <th>修正前</th> <th>修正後</th>
    </tr>
  </thead>
  <tr>
    <td> actions/checkout@v2 </td> <td>actions/checkout@v3</td>
  </tr>
  <tr>
    <td> <strong>aaron-binary</strong>/vercel-preview-url-action@v0.0.3 </td> <td><strong>aaimio</strong>/vercel-preview-url-action@v2.2.0</td>
  </tr>
  <tr>
    <td> marocchino/sticky-pull-request-comment@v1 </td> <td>marocchino/sticky-pull-request-comment@v2</td>
  </tr>
  <tr>
    <td> treosh/lighthouse-ci-action@v3 </td> <td>treosh/lighthouse-ci-action@v10</td>
  </tr>
</table>

### `secrets.GITHUB_TOKEN`の修正
コードの中で`secrets.GITHUB_TOKEN`という、GitHub APIにアクセスする際に必要なトークンを使用する箇所が何箇所かあるんですが、僕の場合これを変える必要がありました。

というのも、どうやら`secrets.GITHUB_TOKEN`では書き込み権限が付与されていないことがあるそうです。

つまり、URLの取得などのREADに関しては`secrets.GITHUB_TOKEN`で行えるが、コメントの追加などは行うことができないということです。

ということで、コメントの追加を行なっている箇所とスコアの整形を行なっている箇所の修正を行います。
まずは書き込み権限を持つトークンを発行しましょう。

1. [GitHub](https://github.com)にアクセスし、Settings > Developer Settingsと進む
1. サイドバーに表示されているPersonal access tokensを選択
1. Tokens(classic)を選択し、Generate new token > Generate new token(classic)を押す
1. Noteの欄に見分けが付く文言を入力(リポジトリ名とかが良いと思います)し、workflowにチェックを入れ、Generate tokenを押す
1. リダイレクト後にトークンが表示されるのでコピー

ここまでできたらコピーしたトークンをリポジトリで使えるようにするために登録しましょう。

1. リポジトリのSettingsからSecurity > Secrets and variables > Actionsと進む
1. New repository secretを押す
1. シークレット名(今回の場合は`SECRET_TOKEN`)と先ほどコピーしたトークンを入力しAdd secret

これで準備は完了したので、`secrets.GITHUB_TOKEN`を`secrets.SECRET_TOKEN`に修正すれば完了です！


### Vercelのリダイレクトによるスコアの低下を解消
バージョンを上げてトークンを登録すれば一旦動くようにはなると思うんですが、やけにスコアが低く表示される気がしました。
そこでlighthouseの結果をみるとこんな表示が,,,

![スクリーンショット 2023-12-09 18.36.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3025861/04ca1c35-dcec-5ead-4776-cf358ca9b9d0.png)

どうやらプレビューURLは別のURLへとリダイレクトされて表示されているようです。

実際に確認してみると元URLが
> https:// vercel.live/open-feedback/[リポジトリ名]-git-[ブランチ名]-[ユーザー名].vercel.app?via=pr-comment-visit-preview-link&passThrough=1

なのに対し、プレビューURLは
> https://[リポジトリ名]-git-[ブランチ名]-[ユーザー名].vercel.app/

と表示されていました。

ということでlighthouseで計測するURLをリダイレクト後のものに修正します。

方法は至って簡単で、「元URLにアクセスして、リダイレクトで最終的にたどり着いたURLを取得する」と言った方法です。

下のコードを「Capture Vercel preview URL」と、「Audit preview URL with Lighthouse」の間のステップに追加してください。

```yml
  - name: Capture Redirected URL
    id: get_redirected_url
    run: |
      PREVIEW_URL="${{ steps.capture_preview_url.outputs.vercel_preview_url }}"
      REDIRECTED_URL=$(curl -Ls -o /dev/null -w %{url_effective} "$PREVIEW_URL")
      echo "REDIRECTED_URL=$REDIRECTED_URL" >> "$GITHUB_OUTPUT"
```

加えて、リダイレクト後のURLで計測するように「Audit preview URL with Lighthouse」のコードを以下の様に修正してください
```yml
  - name: Audit preview URL with Lighthouse
    id: lighthouse_audit
    uses: treosh/lighthouse-ci-action@v10
    # 環境変数からリダイレクト後のURLを取得する
    env:
      REDIRECTED_URL: ${{ steps.get_redirected_url.outputs.REDIRECTED_URL }}
    with:
      urls: |
        # リダイレクト後のURLを使う
        $REDIRECTED_URL
      uploadArtifacts: true
      temporaryPublicStorage: true
```

以上のことをすればリダイレクト先のURLで計測してくれるようになっているはずです。

## 実装のステップ
かなり断片的に紹介してしまったので改めて全体的な実装の流れです。
1. .github/workflowsディレクトリにymlファイルを追加(今回であればcheck-lighthouse-score.ymlなど)
1. そのymlファイルに今までのコードを格納
1. デフォルトブランチにそのコードをマージ
1. 適当にPull Requestを出してみて、計測がなされることを確認

## 課題点
最後に、現状できていない課題点です。

- SSGに弱い
    - 現状プレビューのURLが毎回異なるので、キャッシュを用いるサイトだとどうしてもパフォーマンスが低く出てしまいます。
    - できればURLを固定できたら良いなと考えています
- モバイルでしか計測できない
    - lighthouseのデフォルト設定がモバイルなのか、現状だとモバイルの設定で計測しています
    - できれば両方もしくは切り替えができるようにして計測したいと考えています。
- 誰のコメントでもトリガーされてしまう
    - 本来ユーザーで条件分岐するのがベストなんですが、現状だと全てのコメントに対してトリガーされているのでそれを修正したいです。

## 完成コード
先ほどあげた修正点以外にも日本語化をしたり多少のマイナーチェンジをしています。
このコードをコピペしてもらえれば一旦動くと思われます。

```yml
name: PRのLighthouseスコアチェック

on:
  issue_comment:
    types: [edited]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    name: Lighthouse分析と結果コメント投稿
    steps:
      - name: チェックアウト
        uses: actions/checkout@v3

      - name: プレビューURLを取得
        id: capture_preview_url
        uses: aaimio/vercel-preview-url-action@v2.2.0
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: リダイレクト後のURLを取得
        id: get_redirected_url
        run: |
          PREVIEW_URL="${{ steps.capture_preview_url.outputs.vercel_preview_url }}"
          REDIRECTED_URL=$(curl -Ls -o /dev/null -w %{url_effective} "$PREVIEW_URL")
          echo "REDIRECTED_URL=$REDIRECTED_URL" >> "$GITHUB_OUTPUT"

      - name: コメントを追加
        id: loading_comment_to_pr
        uses: marocchino/sticky-pull-request-comment@v2
        with:
          GITHUB_TOKEN: ${{ secrets.SECRET_TOKEN }}
          number: ${{ github.event.issue.number }}
          header: lighthouse
          message: |
            Lighthouseで分析中...

      - name: Lighthouseでページを分析
        id: lighthouse_audit
        uses: treosh/lighthouse-ci-action@v10
        env:
          REDIRECTED_URL: ${{ steps.get_redirected_url.outputs.REDIRECTED_URL }}
        with:
          urls: |
            $REDIRECTED_URL
          uploadArtifacts: true
          temporaryPublicStorage: true

      - name: スコアを整形する
        id: format_lighthouse_score
        uses: actions/github-script@v3
        with:
          github-token: ${{secrets.SECRET_TOKEN}}
          script: |
            const result = ${{ steps.lighthouse_audit.outputs.manifest }}[0].summary
            const links = ${{ steps.lighthouse_audit.outputs.links }}
            const formatResult = (res) => Math.round((res * 100))
            Object.keys(result).forEach(key => result[key] = formatResult(result[key]))
            const score = res => res >= 90 ? '🟢' : res >= 50 ? '🟠' : '🔴'
            const comment = [
                `[Lighthouse分析結果](${Object.values(links)[0]}):`,
                '| 項目 | スコア |',
                '| --- | --- |',
                `| ${score(result.performance)} パフォーマンス | ${result.performance} |`,
                `| ${score(result.accessibility)} アクセシビリティ | ${result.accessibility} |`,
                `| ${score(result['best-practices'])} ベストプラクティス | ${result['best-practices']} |`,
                `| ${score(result.seo)} SEO | ${result.seo} |`,
                `| ${score(result.pwa)} PWA | ${result.pwa} |`,
                ' ',
                `*分析したURL: [${Object.keys(links)[0]}](${Object.keys(links)[0]})*`
            ].join('\n')
            core.setOutput("comment", comment);

      - name: Lighthouse結果をコメント
        id: comment_to_pr
        uses: marocchino/sticky-pull-request-comment@v2
        with:
          GITHUB_TOKEN: ${{ secrets.SECRET_TOKEN }}
          number: ${{ github.event.issue.number }}
          header: lighthouse
          message: |
            ${{ steps.format_lighthouse_score.outputs.comment }}

```