見出し画像

Monorepo 構成の React アプリをFirebase で複数サイトにデプロイする手順と GitHab Actions により自動化

この記事では、Monorepo 構成の React アプリを Firebase ホスティングにデプロイし、ターゲットを使用して各パッケージを異なるサイトにホストする方法を探ります。

この記事は Monorepo を対象に書いていますが、設定に若干の変更することで、モノリシックなリポジトリにも適用できます。

さらに、GitHub actionsを使用してCI/CD(継続的インテグレーション/継続的デプロイメント)パイプラインを設定し、プロセスを自動化します。簡単にするため、この記事全体を3つのセクションに分けています。

  1. React Monorepoアプリのセットアップ

  2. Firebaseプロジェクトのセットアップ

  3. GitHub Actionsのセットアップ

React Monorepoアプリのセットアップ

すでに Monorepo アプリをお持ちの場合は、このセクションをスキップして構いません。このリポジトリを使うこともできます。
n3rdkid/devops-guide-react-monorepo-firebase
リポジトリをクローンし、依存関係をインストールすれば、すべてが設定されるはずです。

完全に新しく始めたい場合は、firebase.json、.firebaserc、.github/workflowsを削除しても構いません。

yarn install

アプリが正しく動作していることを確認するには、ターミナルで次のコマンドを実行してください。

yarn start

これにより、2つのシンプルなパッケージ「admin」と「user」が実行され、ブラウザに次のタブが開くはずです。

ユーザーパッケージ
管理者パッケージ

これでMonorepoアプリのセットアップが完了し、実行されています。

Firebaseプロジェクトのセットアップ

アプリを後でデプロイしてホストできるように、Firebaseプロジェクトをセットアップし、設定しましょう。そのために、Firebaseコンソールにサインインし、新しいプロジェクトを設定します。

ステップ 1: プロジェクトの作成

プロジェクトが作成されると、「プロジェクトの概要」にリダイレクトされます。ホスティングに移動し、「開始する」を押します。

ステップ 2.1: ホスティングのセットアップ

Firebaseホスティングのセットアップガイドが表示されます。

セットアップ 2.2: Firebase CLIのセットアップ

「firebase-tools」をインストールした後、CLIからFirebaseコンソールにログインし、以下に示すコマンドを使用してプロジェクトを初期化します。

セットアップ 2.3: プロジェクトの初期化

firebase initを入力すると、次のメニューが表示されます。

firebase init メニュー

現在は「Hosting」のみを使用しますが、アプリに適用したい他の機能を選択できます。

Firebaseプロジェクトのセットアップ

すでにプロジェクトを設定しているので、「既存のプロジェクトを使用する」を選択し、プロジェクトを選択します。また、「ホスティングのセットアップ」については、ほとんどを書き直すので、この段階では適当に回答してくだし。

Firebaseホスティングのセットアップ

自動的に作成される設定ファイルは次のようになります。

{
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

public : これはファイルが提供されるフォルダです。
rewrites : シングルページアプリを作成しているため、すべてのルートを./indexにリダイレクトしています。

そして、プロジェクト情報は次のようになります。

{
  "projects": {
    "default": "devops-react-monorepo-9de22"
  }
}

2つの異なるパッケージを異なるサイトにデプロイしようとしているため、これらの設定ファイルを変更する必要があります。「Firebaseコンソール」に戻り、サイトを設定しましょう。「Hosting」で、下にスクロールして詳細セクションに移動します。

ホスティングの詳細

「admin」と「user」パッケージ用に2つのサイトを追加しましょう。

パッケージ用のサイトの追加

では、設定ファイルを更新して、これらのサイトとパッケージをリンクさせましょう。

$ firebase target:apply hosting admin monorepo—admin
$ firebase target:apply hosting user monorepo—user

これにより、.firebasercファイルが次の内容で更新されます。


{
  "projects": {
    "default": "devops-react-monorepo-9de22"
  },
  "targets": {
    "devops-react-monorepo-9de22": {
      "hosting": {
        "admin": [
          "monorepo--admin" // This should be exact site name provided in the firebase console 
        ],
        "user": [
          "monorepo--user"
        ]
      }
    }
  }
}

ここで「targets」は、Firebaseプロジェクト内のFirebaseリソースの識別子であるDeploy targetsを指します。
admin」と「user」はターゲット名で、firebase.jsonで指定します。「monorepo — admin」と「monorepo — user」は、「Firebaseコンソール」でサイトに付けた名前です。
次に、firebase.jsonを更新して、それぞれのビルドファイルをサイトに提供するようにしましょう。複数のサイトをホストしようとしているため、「hosting」属性をオブジェクトではなくオブジェクトの配列に更新します。ここで、.firebasercのターゲット名と一致する新しい「target」プロパティを追加し、また、publicディレクトリをそれぞれのプロジェクトのビルドフォルダに更新しています。

{
  "hosting": [
    {
      "target": "admin",
      "public": "./packages/admin/build", 
      "ignore": [
        "firebase.json",
        "**/.*",
        "**/node_modules/**"
      ],
      "rewrites": [
        {
          "source": "**",
          "destination": "/index.html"
        }
      ]
    },
    {
      "target": "user",  
      "public": "./packages/user/build",  
      "ignore": [
        "firebase.json",
        "**/.*",
        "**/node_modules/**"
      ],
      "rewrites": [
        {
          "source": "**",
          "destination": "/index.html"
        }
      ]
    }
  ]
}

これで、最終的にアプリをFirebaseホスティングにデプロイできます。コンソールで次のコマンドを入力すると、アプリが起動し実行されるはずです。

ステップ 2.4 Firebaseデプロイ


おめでとうございます。React MonorepoアプリをFirebaseホスティングに複数のサイトでデプロイすることに成功しました。

GitHub Actionsのセットアップ

さて、CI/CDパイプラインをセットアップする時が来ました。
これはGitHubリポジトリのアクションタブから直接行うことができます。

GitHubからのアクションの設定

アクションを設定する別の方法は、ルートディレクトリの「.github」フォルダ内に手動で「workflows」フォルダを作成することです。

workflowsの中で、CI/CDパイプラインのワークフローを指定します。GitHubが生成したアクションを見てみましょう。GitHubには使用できる多くのアクションがありますが、これは最も基本的なものです。

# This is a basic workflow to help you get started with Actions

name: CI

# Controls when the action will run. 
on:
  # Triggers the workflow on push or pull request events but only for the master branch
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v2

      # Runs a single command using the runners shell
      - name: Run a one-line script
        run: echo Hello, world!

      # Runs a set of commands using the runners shell
      - name: Run a multi-line script
        run: |
          echo Add other actions to build,
          echo test, and deploy your project.

[3] name: これはワークフローの名前で、GitHubはリポジトリのアクションページに表示します。このフィールドを省略すると、GitHubはワークフローのファイル名を名前として設定します。
[6] on: これには、ワークフローをトリガーするGitHubイベントの名前が含まれます。現在、このワークフローは「master」または「main」(デフォルトのブランチによって異なります)に新しいコミットがプッシュされたり、プルリクエストが開かれたりしたときにトリガーされます。

[workflow_dispatch] 新しいworkflow_dispatchイベントを使用して、ワークフローを手動でトリガーできます。その後、アクションタブに「ワークフローを実行」ボタンが表示され、簡単に実行をトリガーできるようになります。

ワークフローの手動トリガー

[17] jobs: ワークフロー実行は、デフォルトで並行して実行される1つ以上のジョブで構成されています。ジョブを順次実行するには、ジョブのIDとneedsキーワードを使用して、他のジョブへの依存関係を定義できます。
ここでは、Ubuntuで実行され、「steps」に従う「build」ジョブを指定しています。複数のOS環境でジョブを実行することもできます。
各ステップで、既存のアクションを再利用したり、独自のコマンドを書いたりすることができます。
[26] uses: ジョブのステップの一部として実行するアクションを選択します。withを使用してアクションにパラメータを渡すことができます。これについては後で見ていきます。

「ワークフローと同じリポジトリ、公開リポジトリ、または公開されたDockerコンテナイメージで定義されたアクションを使用できます。」

[29,33] ここでは、ワークフローで1行のスクリプトまたは複数行のスクリプトを実行する方法を確認できます。
コミットしてプッシュすると、GitHubリポジトリのアクションタブでワークフローが実行されているのが確認できるはずです。

GitHubアクションタブ

ジョブの各ステップの詳細情報も取得できます。コミットメッセージをクリックしてジョブを選択すると、すべてのステップのログが表示されます。

さて、Firebaseにビルドとデプロイを行う独自のワークフローを設定しましょう。プロジェクトのビルドとデプロイに次のようなワークフローを設定します。必要に応じて、「リンティング」や「./build以外のファイルの削除」などのステップを追加することもできます。

現在、私たちは「master」ブランチへのプッシュに対してワークフローを実行しますが、プロジェクトのニーズに応じてワークフローのトリガーを設定することができます。例えば、「master」以外のブランチでは、デプロイを行わないため、ビルドのみを実行するように設定できます。
プロジェクトを実行するために環境変数が必要な場合、まずそれらをリポジトリに追加しましょう。「設定」タブに移動し、「シークレット」を選択します。
「本番環境」、「ステージング環境」、「開発環境」など、複数の環境にデプロイする場合は、「環境」を使用してそのようなシナリオを管理することもできます。ただし、プライベートリポジトリを使用している場合、「環境」機能はGitHub Enterpriseプランでのみ利用可能です。
ここでは、リポジトリのシークレットのみを設定します。

GitHubリポジトリの設定タブ

シークレットセクションで、「新しいリポジトリシークレット」をクリックします。

GitHubリポジトリのシークレット
GitHubリポジトリシークレットの追加

また、Firebase Hostingにデプロイする権限を持つService Accountを追加する必要があります。手動で設定することもできますが、Firebase CLIを使用して設定しましょう。すでにプロジェクトのホスティングを設定しているため、GitHub Actionsの部分だけを設定する必要があります。ルートディレクトリで次のコマンドを入力してください。

$ firebase init hosting:github
GitHub ActionsによるFirebase Hosting

これによって生成されたワークフローファイルは削除してください。Firebase Hostingにデプロイするための独自のワークフローファイルを用意するためです。これで、リポジトリのシークレットに次のシークレットが追加されているはずです。

シークレット」を設定したら、ワークフローファイルの設定を始めましょう。

name: Build and Deploy
on:
  push:
   branches: 
   - master

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    # One
      - uses: actions/checkout@v2

      - name: Use Node.js
        uses: actions/setup-node@v2
        with:
          node-version: 14.x
    # Two
      - name: Get yarn cache directory path
        id: yarn-cache-dir-path
        run: echo "::set-output name=dir::$(yarn cache dir)"

      - name: Check dependencies in cache 
        id: yarn-cache 
        uses: actions/cache@v2
        with:
          path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
          key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-yarn-
    # Three 
      - name: Initializing dependencies
        if: steps.npm-cache.outputs.cache-hit != 'true'
        run:  yarn bootstrap
    # Four 
      - name: Creating & Initializing env variables for Admin Package
        run: |
          cd packages/admin
          pwd
          echo "REACT_APP_FIREBASE_API_KEY= ${{secrets.REACT_APP_FIREBASE_API_KEY}}
          REACT_APP_FIREBASE_AUTH_DOMAIN= ${{secrets.REACT_APP_FIREBASE_AUTH_DOMAIN}}
          REACT_APP_FIREBASE_PROJECT_ID= ${{secrets.REACT_APP_FIREBASE_PROJECT_ID}}
          REACT_APP_FIREBASE_STORAGE_BUCKET= ${{secrets.REACT_APP_FIREBASE_STORAGE_BUCKET}}
          REACT_APP_FIREBASE_MESSAGING_SENDER_ID= ${{secrets.REACT_APP_FIREBASE_MESSAGING_SENDER_ID}}
          REACT_APP_FIREBASE_APP_ID= ${{secrets.REACT_APP_FIREBASE_APP_ID}}" > .env
      - name: Creating & Initializing env variables for User Package
        run: |
          cd packages/user
          pwd
          echo "REACT_APP_FIREBASE_API_KEY= ${{secrets.REACT_APP_FIREBASE_API_KEY}}
          REACT_APP_FIREBASE_AUTH_DOMAIN= ${{secrets.REACT_APP_FIREBASE_AUTH_DOMAIN}}
          REACT_APP_FIREBASE_PROJECT_ID= ${{secrets.REACT_APP_FIREBASE_PROJECT_ID}}
          REACT_APP_FIREBASE_STORAGE_BUCKET= ${{secrets.REACT_APP_FIREBASE_STORAGE_BUCKET}}
          REACT_APP_FIREBASE_MESSAGING_SENDER_ID= ${{secrets.REACT_APP_FIREBASE_MESSAGING_SENDER_ID}}
          REACT_APP_FIREBASE_APP_ID= ${{secrets.REACT_APP_FIREBASE_APP_ID}}" > .env
    # Five 
      # - name: Run Tests 
      #   run: |
      #     yarn tests
    # Six 
      - name: Build the project
        run: |
          yarn build
    # Seven 
      - name: Deploy to firebase 
        uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_DEVOPS_REACT_MONOREPO_9DE22 }}'
          projectId: devops-react-monorepo-9de22
          channelId: live

[ #One] :リポジトリをチェックアウトしています。これにより、作業ディレクトリがプロジェクトのルートディレクトリに設定されます。
[ #Two]: yarn の依存関係用にキャッシュディレクトリを設定し、yarn.lock ファイルを比較して以前の依存関係と現在の依存関係に差異がない場合にそれらを復元するキーを設定しています。このアクションにより、ジョブが正常に実行された際にキャッシュに依存関係を記録します。これにより、毎回依存関係をインストールする必要がなくなり、ビルド時間が短縮されます。

ここでは、actions/cache@v2を再利用しています。GitHub Actionsの優れた点の1つは、コミュニティによって書かれ、共有されている数多くのアクションがあることです。withを使用して様々な入力パラメータを提供しているのがわかります。

[#Three]:キャッシュをチェックし、依存関係が存在するか確認します。存在しない場合は依存関係をインストールし、存在する場合は何もしません。最初のビルドは通常遅くなりますが、この設定により、次回以降のビルドは比較的速くなります。
[ #Four]:両方のパッケージに対して .env ファイルを作成します。ファイルを作成する前にディレクトリを変更している点や、${{secrets.SECRET_NAME}} を使用してリポジトリに事前に追加したシークレットにアクセスしている点に注目してください。
[#Five]:現在、これらのパッケージにはテストが書かれていませんが、テストを追加してスクリプトを更新し、実行することができます。プロジェクトをビルドする前に、テストが正常に動作していることを常に確認する必要があります。エラーのあるコードを本番環境にデプロイしても意味がありません。
[ #Six]:ここでプロジェクトをビルドします。これにより、firebase.json ファイル内で公開フォルダとして指定した両方のパッケージ用の /build が生成されます。これで、プロジェクトはデプロイの準備が整いました。
[#Seven]: コードをFirebase Hostingのライブチャンネルにデプロイします。必要に応じて、プレビューチャンネルにコードをホストすることもできます。
変更を加えて、設定したmasterまたはmainブランチにコミットをプッシュしてください。すべてが正常に動作していれば、コードがFirebaseにデプロイされます。

ビルド&デプロイワークフロー

この記事は、2021年3月に弊社のエンジニア Saurav Adhikari が執筆した内容を日本語に翻訳したものです。
英語版はこちらをご覧ください。
https://articles.wesionary.team/ci-cd-with-github-actions-react-monorepo-to-firebase-hosting-14e02e30ca90


採用情報

私たちはプロダクト共創の仕組み化に取り組んでいます。プロダクト共創をリードするプロダクト・マネージャー、そして、私たちのビジョンを市場に届ける営業メンバーを募集しています!


開発パートナーをお探しの企業様へ

弊社は、グローバル開発のメリットを活かし、高い費用対効果と品質を両立しています。経験豊富で多様性のあるチームが、課題を正しく理解し、最適なシステムと優れた体験を実現します。業務システムの開発、新規事業の開発、業務効率化やDX化に関するお困りごと、ぜひ弊社にご相談ください。