🎡

npm workspaceでmonorepoと戦う

2024/01/26に公開

はじめに

フロントエンドのみならずバックエンドもJavaScriptで書く、そんな現場にジョインすることになりました。
Lambdaのような小さな処理をJSで実装した経験はありましたが、BFF(backend for frontend)なAPI基盤を一揃いnodeで実装するというのはフロントエンドエンジニアの自身には非常に新鮮でした。

バックエンド・フロントエンドの棲み分け

バックエンド系ライブラリはWebフレームワークとしてスタンダードなexpressやDBIであるprisma、フロントエンドはreact/styled-components、同じJSではありつつも、ライブラリは大きく異なっています

npmによる一緒くたの実装もできるのですが、tsconfigやlinter、prettier等の設定などを考えるとこれらにある程度線引をしたほうが見通しのよいプロダクトになる気がします。

リポジトリを分割するのも一つの手段です。
ただし、デプロイなどを含めた開発体験により手数が増える傾向があり、これをmonorepo(モノレポ)で実現したいという開発者も多くいます。

npm workspaceを使う

monorepo におけるフロントエンド・バックエンド開発に npm workspaces を取り入れることで非常に見通しの良い開発体験が実現できそうです。

ディレクトリ構成

新規プロダクトを立ち上げ、以下のような構成を作ってみました。

./<project-root>
├── backend
│   └── package.json
├── dist # ビルドしたファイルの出力ディレクトリ
├── frontend
│   └── package.json
├── package-lock.json
└── package.json

各ファイルは以下の通りです。

// ./package.json**
{
  "name": "project",
    :
  "workspaces": [
    "backend",
    "frontend"
  ],
  "devDependencies": {
    "eslint": "^8.56.0",
    "prettier": "^3.1.1",
      :
  }
}

// ./backend/package.json
{
  "name": "project_backend",
    :
  "dependencies": {
    "@prisma/client": "^5.7.1",
    "express": "^4.18.2",
      :
  },
  "devDependencies": {
    "@types/express": "^4.17.21",
    "prisma": "^5.7.1"
      :
  },
}

// ./frontend/package.json
{
  "name": "project_frontend",
    :
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
      :
  },
  "devDependencies": {
    "@types/react": "^18.2.45",
    "@types/react-dom": "^18.2.18",
      :
  },
}

長々とコードを貼りましたが要点としては、以下のような配置をおこないました。

  • バックエンドのpackage.jsonにはバックエンド専有のパッケージを持つ
  • フロントエンドのpackage.jsonにはフロントエンド専有のパッケージを持つ
  • プロジェクト直下のpackage.jsonには共用のパッケージを持つ

ちなみに、ロックファイルは直下にしかできません。
npm install もディレクトリ別ではなく、一元的に実施されます。

各設定ファイルの配置

tsconfig / linter / prettier

tsconfig についてはビルド作法の違いから個々に設けました。linterもフロントエンド・バックエンドで取り込むべきconfigが異なります。
これらのファイルを個々のディレクトリに配置することで、非常に見通しが良くなりますね。

prettierの設定ファイルについては同内容だったのですが、コマンド実行時に直下のファイルを参照するため個々のディレクトリに配置しています。(もう少しよい方法があるかもしれません…)

ディレクトリ構成

./<project-root>
├── backend
│   ├── eslint.js
│   ├── tsconfig.json
│   ├── .prettierignore
│   └── .prettierrc
├── frontend
│   ├── eslint.js
│   ├── tsconfig.json
│   ├── .prettierignore
│   └── .prettierrc
:

VSCodeのESLintがうまく認識しない問題発生

VSCodeにて、ESLintが誤検知する問題がありました。(2024年1月時点)
基本的に展開したディレクトリがドキュメントルートになるため、個別に設定を行いVSCodeにlinterのルートを教えてあげる必要があるようです。

こちらの記事を参考にさせていただきました。
https://tkzawa.netlify.app/201021/

上の記事にあるように、npm workspacesの認識はsetting.jsonを設定することで解決しました。

./.vscode/settings.json

{
  "eslint.workingDirectories": ["backend", "frontend"]
}

開発体験は別リポジトリの如く

npmコマンドについては以下のように動作します。

npm install react -w frontend // workspacesを指定(frontendにインストールされる)
cd frontend && npm install react // ディレクトリを移動(上と同じ)
npm run dev -w frontend // workspacesを指定(frontend下でコマンドを実行)
cd frontend && npm run dev // ディレクトリを移動(上と同じ)

上記を見てわかるように、monorepo構成であってもディレクトリ階層を移動してしまえば個別のリポジトリを操作しているような体験を得ることができます

-w はワークスペースを指定するオプションですが、こちらを頻繁に用いる必要はなく、基本的には作業中のディレクトリに移動して操作を行っています。
親ディレクトリにスクリプトを実装する際には上記オプションで簡単に子ディレクトリのコマンドが実行できます。

{
    :
  "scripts": {
    "build": "npm run build --w backend && npm run build --w frontend"
    "build": "npm run --workspaces build" // コマンドが同一ならこちらのようにも書ける
    :
  },
}

総括

workspaceを活用することで複数のnpm環境をシンプルに統括できそうです。

  • リポジトリが単一のため、コード管理がシンプル。
  • npm のロックファイルもひとつのためバージョン管理もシンプル、インストールコマンドも一度で済む。
  • ディレクトリ階層を移動するだけで、専有リポジトリと同じ開発体験を享受できる。

package.jsonにはStorybook、E2E、openapi...など、プロジェクト本体以外のそれを取り巻くツールが盛り込まれるようになってきました。
これをスマートに管理するために、この機能が活用されるとよいですね!

Discussion