🐧

monorepo構成で、ReactアプリとFirebase Functionsを同居させる

2022/05/04に公開約5,600字

TL;DR

monorepo構成でReactアプリとFirebase Functionsを同じリポジトリで管理し、共通のロジックや型を使いまわせるようにします。

https://github.com/Alesion30/monorepo-react-app

モノレポ(monorepo)とは?

モノレポとは、同一のリポジトリに複数のアプリやパッケージを管理する手法のことを指します。複数のアプリのPRやコード差分などを1つのリポジトリで管理するため煩雑になりやすいというデメリットはありつつも、共通のロジックを使いまわせたり、単一の依存ファイルで管理できるといった恩恵を得ることができます。

モノレポについては、CircleCIのブログにわかりやすくまとめられています。

https://circleci.com/ja/blog/monorepo-dev-practices/

今回は、yarnのworkspacesという機能を用いて、ReactアプリとFirebase Functionsをモノレポで管理していこうと思います。

https://classic.yarnpkg.com/lang/en/docs/workspaces/

セットアップ

monorepo-react-appという名前で、実装を進めていきます。また、yarnのバージョンは3.0.0を使用します。

$ mkdir monorepo-react-app
$ cd monorepo-react-app
monorepo-react-app:$ yarn init -y
monorepo-react-app:$ yarn set version 3.0.0
.yarnrc.yml
# nodeLinkerを追加する
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.0.0.cjs

.gitignoreファイルを作成します。

monorepo-react-app:$ touch .gitignore
.gitignore
# dependencies
**/node_modules
**/.pnp
**/.pnp.js

# testing
**/coverage

# yarn
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

# misc
**/.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

# log
**/npm-debug.log*
**/yarn-debug.log*
**/yarn-error.log*

packagesフォルダにそれぞれのアプリ(React・Firebase Functions)を管理していきます。

monorepo-react-app:$ mkdir packages
./package.json
{
  "name": "monorepo-react-app",
  "private": true,
  // workspacesを追加する
  "workspaces": {
    "packages": [
      "packages/*"
    ]
  },
  "packageManager": "yarn@3.2.0"
}

Reactの環境構築

本記事では、create-react-appではなく、viteでアプリを構築します。

https://ja.vitejs.dev/
monorepo-react-app:$ yarn create vite packages/client --template react-ts

package.jsonのnameclientになっていることを確認します。

packages/client/package.json
{
  "name": "client",
  "private": true,
  "version": "0.0.0",
  // ...
}
monorepo-react-app:$ yarn install
monorepo-react-app:$ yarn workspace client dev

Firebase Functionsの環境構築

firebase cliを導入していない方は、先にセットアップしておく必要があります。

https://firebase.google.com/docs/cli

以下のコマンドを叩くと、functionsフォルダ・.firebasercfirebase.jsonが作成されます。

monorepo-react-app:$ firebase init functions

functionsフォルダをpackagesフォルダに移動させます。

firebase.jsonを以下のように修正します。

firebase.json
{
  "functions": {
    "predeploy": "yarn workspace functions run build",
    "source": "packages/functions"
  }
}

src/index.tsにhelloWorld関数を追加しておきます。

packages/functions/src/index.ts
import * as functions from "firebase-functions";

// Start writing Firebase Functions
// https://firebase.google.com/docs/functions/typescript
export const helloWorld = functions.https.onRequest((request, response) => {
  functions.logger.info("Hello logs!", {structuredData: true});
  response.send("Hello from Firebase!");
});
monorepo-react-app:$ yarn workspace functions build

TypeScriptのバージョンを統一する

clientとfunctionsのTypeScriptのバージョンが一致していないので、ルートのpackage.jsonでTypeScriptのバージョンを管理するようにします。

まず、すでにインストールされているTypeScriptを削除します。

monorepo-react-app:$ yarn workspace client remove typescript
monorepo-react-app:$ yarn workspace functions remove typescript

TypeScriptをインストールします。

monorepo-react-app:$ yarn add -D typescript

eslint・prettierを導入

monorepo-react-app:$ yarn add -D eslint eslint-config-prettier eslint-plugin-import eslint-plugin-react eslint-plugin-sort @typescript-eslint/eslint-plugin @typescript-eslint/parser prettier prettier-plugin-sort-json

eslintとprettierの設定ファイルを作成します。

monorepo-react-app:$ touch .eslintrc .eslintignore .prettierrc .prettierignore
.eslintrc
{
  "env": {
    "browser": true,
    "es2017": true,
    "node": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:import/recommended",
    "plugin:react/recommended",
    "plugin:sort/recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 2021,
    "sourceType": "module"
  },
  "plugins": ["import", "sort", "@typescript-eslint"],
  "root": true,
  "rules": {
    "@typescript-eslint/no-unused-vars": [
      "error",
      {
        "argsIgnorePattern": "^_"
      }
    ],
    "import/no-unresolved": [
      "error",
      {
        "ignore": ["\\?raw$"]
      }
    ],
    "import/order": [
      "error",
      {
        "alphabetize": {
          "order": "asc"
        },
        "newlines-between": "always"
      }
    ],
    "react/jsx-sort-props": [
      "error",
      {
        "reservedFirst": true,
        "shorthandFirst": true
      }
    ],
    "react/prop-types": ["off"],
    "react/react-in-jsx-scope": "off",
    "sort/imports": ["off"]
  },
  "settings": {
    "import/resolver": {
      "node": {
        "extensions": [".js", ".jsx", ".ts", ".tsx"]
      }
    },
    "react": {
      "version": "detect"
    }
  }
}
**/node_modules/*
**/dist/*
**/lib/*
.yarn
.prettierrc
{
  "jsonRecursiveSort": true,
  "trailingComma": "all"
}
**/node_modules/*
**/dist/*
**/lib/*
.yarn
*.md

参考記事

https://ito-u-oti.com/react-monorepo/
https://zenn.dev/takanori_is/articles/tsconfig-in-monorepo
https://qiita.com/sisisin/items/954cf6b3464233e2d010
https://blog.cybozu.io/entry/2020/04/21/080000
GitHubで編集を提案

Discussion

ログインするとコメントできます