pnpmで管理しているWebフロントエンドをGitHub ActionsでGitHub Pagesにデプロイする

# やりたいこと
`pnpm` でパッケージ管理をしている Web フロントエンドアプリケーションを、GitHub Actions を用いることで、`main` ブランチへ `push` されたタイミングで自動的にデプロイします。

# 前提
[こちらの GitHub レポジトリ](https://github.com/Yuki-Imajuku/pnpm-ghpages-deploy) も参照してください。

- Ubuntu (22.04.4)
- Node.js (20.11.1)
- pnpm (8.15.4)

```txt:ディレクトリ構成
pnpm-ghpages-deploy/
├── .github/workflows/ghpages-deploy.yml
├── frontend/
├── .gitignore
├── LICENSE
└── README.md
```

# プロジェクト作成
下記のコマンドでテンプレートの React アプリケーションを作成します。ビルドツールには Vite を採用しています。
```sh
pnpm create vite@latest --template react-swc-ts frontend
```

デプロイ先の URL は `https://<user name>.github.io/<repository name>/` となるので、それに合わせてプロジェクトも変更します。
`<user name>` には GitHub のアカウント名（を小文字にしたもの）が、`<repository name>` には GitHub レポジトリの名前がそのまま入ります。
今回は [https://yuki-imajuku.github.io/pnpm-ghpages-deploy/](https://yuki-imajuku.github.io/pnpm-ghpages-deploy/) となるので、以下のように設定を変更します。
```diff_tsx:frontend/vite.config.ts
export default defineConfig({
  plugins: [react()],
+ base: '/pnpm-ghpages-deploy/'
})
```

# GitHub レポジトリの設定
`Settings` タブから設定画面を開き、`Pages` タブをクリックし、`Build and deployment` の `Source` という部分を `Deploy from a branch` から `GitHub Actions` に変更します。
設定が完了すると、以下のスクリーンショットのような画面になります。
![GitHub Pages Setting.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2962238/1292d626-8f3d-3b59-d88d-86411aa618c9.png)

# GitHub Actions の設定
`.github/workflows` 以下に YAML ファイルを書くことで設定できます。
今回は `ghpages-deploy.yml` としました。
まず、全体像は以下の通りです。
```yaml:ghpages-deploy.yml
name: GitHub Pages Deploy

on:
  push:
    branches:
      - main
    paths:
      - .github/workflows/ghpages-deploy.yml
      - frontend/**

jobs:
  build:
    runs-on: ubuntu-22.04

    defaults:
      run:
        working-directory: ./frontend

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20.11.1

      - name: Install pnpm
        uses: pnpm/action-setup@v3
        with:
          version: 8.15.4
          run_install: false

      - name: Get pnpm store directory
        shell: bash
        run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

      - name: Setup pnpm cache
        uses: actions/cache@v4
        with:
          path: ${{ env.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: ${{ runner.os }}-pnpm-store-

      - name: Install dependencies
        run: pnpm install

      - name: Build
        run: pnpm run build

      - name: Upload Pages artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./frontend/dist

  deploy:
    needs: build

    permissions:
      pages: write
      id-token: write

    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}

    runs-on: ubuntu-22.04

    steps:
      - name: Deploy to GitHub Pages
        uses: actions/deploy-pages@v4
        id: deployment

```

それぞれ説明していきます。

1. 名前
    表示される名前です。
    ```yaml
    name: GitHub Pages Deploy
    ```
    以下のスクリーンショットを見ると、名前が適用されている様子が分かります。
    ![GitHub Actions Name.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2962238/35a21a46-ee7c-0ebe-7d48-0aacf007ef56.png)

2. 実行条件
    `main` ブランチに `push` されたタイミングで実行します。
    `README.md` など影響がないファイルを除外するために `paths` も設定しています。
    ```yaml
    on:
      push:
        branches:
          - main
        paths:
          - .github/workflows/ghpages-deploy.yml
          - frontend/**
    ```
    以下のスクリーンショットを見ると、`README.md` だけ更新した場合は実行されていない様子が分かります。
    ![GitHub Actions Sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2962238/bb0b9516-5820-29fb-3260-1a22b01b2a7c.png)

3. ビルドジョブの設定
    `build` というのがそのまま名前になります。
    今回は Ubuntu 環境を使いました。
    また、`pnpm install` などを行うため、`frontend` ディレクトリで `run` するように設定を書きます。
    ```yaml
    jobs:
      build:
        runs-on: ubuntu-22.04

        defaults:
          run:
            working-directory: ./frontend
    ```

4. ブランチのチェックアウト
    [actions/checkout](https://github.com/actions/checkout) を使います。
    ```yaml
    jobs:
      build:
        :
        steps:
          - name: Checkout
            uses: actions/checkout@v4
    ```

5. Node.js のインストール
    [actions/setup-node](https://github.com/actions/setup-node) を使います。
    ```yaml
    jobs:
      build:
        :
        steps:
          :
          - name: Setup Node.js
            uses: actions/setup-node@v4
            with:
              node-version: 20.11.1
    ```

6. pnpm のインストール
    [pnpm/action-setup](https://github.com/pnpm/action-setup) を使います。
    公式の README にある通り、キャッシュを利用してインストール時間を短縮します。
    ```yaml
    jobs:
      build:
        :
        steps:
          :
          - name: Install pnpm
            uses: pnpm/action-setup@v3
            with:
              version: 8.15.4
              run_install: false

          - name: Get pnpm store directory
            shell: bash
            run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

          - name: Setup pnpm cache
            uses: actions/cache@v4
            with:
              path: ${{ env.STORE_PATH }}
              key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
              restore-keys: ${{ runner.os }}-pnpm-store-

        　- name: Install dependencies
            run: pnpm install
    ```

7. ビルド
    `frontend/package.json` に設定してある通りビルドしていきます。
    ```yaml
    jobs:
      build:
        :
        steps:
          :
          - name: Build
            run: pnpm run build
    ```

8. ビルド済みのコードをアップロード
    [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) を使います。
    `path` によってビルド済みのコードの格納場所を指定していきます。
    `working-directory` で設定したパスは関係ないので、`./frontend/` の部分から指定します。
    ```yaml
    jobs:
      build:
        :
        steps:
          :
          - name: Upload Pages artifact
            uses: actions/upload-pages-artifact@v3
            with:
              path: ./frontend/dist
    ```


9. デプロイジョブの設定
    `build` ジョブが終了してから実行するようにします。こちらも Ubuntu 環境を使いました。
    [actions/deploy-pages](https://github.com/actions/deploy-pages) の README にある通り、最小限の権限を付与します。
    ```yaml
    jobs:
      :
      deploy:
        needs: build

        permissions:
          pages: write
          id-token: write

        environment:
          name: github-pages
          url: ${{ steps.deployment.outputs.page_url }}

        runs-on: ubuntu-22.04
    ```

10. GitHub Pages にデプロイ
    [actions/deploy-pages](https://github.com/actions/deploy-pages) を使います。
    ```yaml
    jobs:
      :
      deploy:
        :
        steps:
          - name: Deploy to GitHub Pages
            uses: actions/deploy-pages@v4
            id: deployment
    ```

# 参考文献
1. [GitHub Actionsでpnpmを使う](https://zenn.dev/vew94/articles/how-to-use-githubactions-with-pnpm)
2. [pnpmで作ったWebフロントをGitHub ActionsでGitHub Pagesへデプロイする](https://zenn.dev/drumath2237/articles/13c51f94b3e513)
3. [GitHub ActionsでReactアプリをGitHub Pagesにデプロイする](https://zenn.dev/acha_n/articles/deploy-react-app-with-github-actions)
4. [ReactをGitHub Pagesにデプロイしよう〜Vite編〜](https://qiita.com/tat_mae084/items/4051c61926dc8165e80b)
5. [Deploying a React App to GitHub Pages automatically
](https://isqua.ru/en/blog/2022/11/23/deploy-react-app-to-github-pages/)