【Node.js】lernaとnpmのworkspacesでMonorepoな環境を構築する

ネコニウム研究所

PCを利用したモノづくりに関連する情報や超個人的なナレッジを掲載するブログ

【Node.js】lernaとnpmのworkspacesでMonorepoな環境を構築する

2024-9-3 |

Node.jsでlernaとnpmのworkspacesを使ってMonorepoな環境を構築したい!

概要

今回の記事では、Node.jsでlernaとnpmのworkspacesを使ってMonorepoな環境を構築する手順を掲載する。

Monorepoとは、複数のプロジェクトを単一のリポジトリで管理する方法。共通の処理があったり、複数のアプリを合わせて、1つのサービスとするような感じのプロジェクト郡の管理に便利だったり。

lernaはバージョン管理やパッケージの公開に強い。以前は、Monorepoなパッケージ管理機能を持ってたんだけども。後述するnpmのworkspacesなどの登場でそれらを使うことを推奨してて現在のバージョンでは関連するコマンドが使えなくなってる。ちなみにReactやJestにも採用されてるらしい。

npmのworkspacesではMonorepoなパッケージ管理機能(共通の外部パッケージの効率的な管理や管理してるプロジェクト同士の依存関係の解決など)やプロジェクト管理を行うことができる。

仕様書

環境

  • Node.js 20.15.0
  • npm 10.8.1
  • lerna 8.1.8

手順書

lernaのプロジェクトを作るディレクトリーの中でlernaをインストールする。

npm install -d lerna

lernaでプロジェクトを初期化する。

npx lerna init

my_workspaceがプロジェクトだとして下記のような感じでディレクトリーとファイルが作られる。

my_workspace
    node_module
    .gitgnore
    lerna.json
    package-lock.json
    package.json

lerna.jsonの中身を確認する。

{
  "$schema": "node_modules/lerna/schemas/lerna-schema.json",
  "version": "0.0.0",
  "packages": [
    "packages/*"
  ]
}

packagespackages/*lernaで管理するプロジェクトの保管場所になるのでディレクトリーpackagesを作って、この中にプロジェクトを作ったり、移動したりする。

package.jsonにもworkspacesを追加する。書き方はlerna.jsonと同じ感じ。

{
  "name": "my_workspace"
  "private": true,
  "workspaces": [
    "packages/*"
  ],
  "description": ""
}

公開する予定もないので"private": trueも追記しておく。

管理してるプロジェクト同士の依存関係の簡単な設定の例

構成がこんな感じになってるとして

my_workspace
    packages
        main_app
        admin_app

main_appadmin_appがあって、admin_appmain_appに依存してるとする場合、はadmin_apppackage.jsondependenciesの中に"main_app": "^1.0.0"を追加する。

{
  "name": "admin_app",
  "version": "1.0.0",
  "private": true,
  ...
  "dependencies": {
    ...
    "main_app": "*"
  },
  ...
}

もしくはコマンドを使う。

npm install main_app --workspace=admin_app

admin_appからmain_app/src/share/my_compornent.tsxmain_app/src/share/logo.pngをインポートする例。

main_app
    src
        share
            index.ts
            logo.png
            MyCompornent.tsx

main_app/src/share/MyCompornent.tsxがこんな感じになってるとする。

const MyCompornent = () => {
    return (<h1>MyCompornent</h1>);
};

export default MyCompornent;

main_app/src/share/index.tsがエクスポートに使うファイルになる。

export * from './MyCompornent';
import logo from './logo.png';
export const Logo = logo;

export { default as MyCompornent } from './MyCompornent';

main_app/package.jsonexportsを設定する。

{
  "name": "main_app",
  ...
  "exports": {
    "./share": "./src/share/index.ts",
  },
  ...
}

インポートする側admin_appでは下記のような感じで使えるようになる。

import { MyCompornent, Logo } from 'main_app/share';

トラブルシューティング

lernaでプロジェクトを初期化する際に下記のようなエラーが発生する場合

npx lerna init
lerna ERR! Cannot initialize lerna because your package manager has not been configured to use `workspaces`, and you have not explicitly specified any packages to operate on
lerna ERR! See https://lerna.js.org/docs/getting-started#adding-lerna-to-an-existing-repo for how to resolve this

既に作成されたプロジェクトを含んでる状態で初期化しようとすると発生することがある。その場合は、管理したいプロジェクトがあるディレクトリーをオプションで指定して初期化する。

ディレクトリーがひとつの場合。

npx lerna init --packages="packages/*"

ディレクトリーが複数ある場合。

npx lerna init --packages="packages1/*" --packages="packages2/*"

まとめ(感想文)

メインのアプリがあって、管理アプリなどを別のプロジェクトで作る場合や共通のコードがある場合に使えるかもね!

参考文献・引用

下記の記事を参考にさせて頂きました。ありがとうございました。