18リポジトリをnpmからpnpmに移行した際の学び
はじめに
こんにちは、エンジニアの力石です。
フォルシアでは商品販売プラットフォームwebコネクトを提供しており、私はその中の検索システム(検索領域)の開発・運用保守を行っています。
検索領域はマイクロサービスアーキテクチャで構築されており、機能毎(コンポーネント毎)にリポジトリを分けるマルチリポジトリ構成を採用しています。
そして最近、検索領域では npm を利用している全 18 リポジトリのパッケージマネージャーを pnpm へと移行しました。
本記事では移行の際に躓いたエラーや今回の移行の反省点・今後に向けての改善点について紹介します。
(移行手順についてはさまざまな記事が出ているので、本記事では割愛します)
移行の作業内容
今回の移行では以下のような作業を 18 リポジトリで行いました。
作業内容(一部)
- README や開発環境の整備
- Volta の導入
-
npm run
をpnpm run
に移行 - CI を pnpm に対応
- キャッシュとして
.pnpm-store
を使うようにするなど
- キャッシュとして
- Dockerfile を pnpm に対応
-
pnpm deploy
を利用したマルチステージビルドの余分なステージ削減など-
pnpm i
の回数も削減
-
-
- git submodule 周りの build を pnpm に対応
- ライブラリの依存関係の解決・効率化
- image build, アプリ build, 型 build, CI, linter, トランスパイルでのエラーへの対応
バージョン情報
本記事は以下のバージョンで移行したログになります。
また 17 リポジトリは Node.js のバージョン v18.17.1 を利用し、1 リポジトリのみ Node.js のバージョン v18.19.1 を利用しました。
Node.js のバージョン管理には Volta を利用しており、Node.js に同梱されている Corepack で pnpm を管理しています。
> volta -v
1.1.1
> volta list all
⚡️ User toolchain:
Node runtimes:
v18.17.1 (current @ /path/to/repository/package.json)
v18.19.1
Package managers:
Packages:
corepack@0.29.4 (default)
binary tools: corepack, pnpm, pnpx, yarn, yarnpkg
platform:
runtime: node@18.17.1
package manager: npm@built-in
> pnpm -v
9.1.4
移行の際に躓いたエラー
移行にあたってたくさんのエラーを踏みましたが、大体はライブラリ関連のエラーで検索すれば解決方法がでてきたため、ここでは pnpm に関連したエラーをいくつかピックアップして紹介します。
pnpm i
が失敗する
GitLab CI で 開発環境では特に失敗することがなかったpnpm i
ですが、GitLabCI 上では以下のエラーとともに稀に失敗しました。
ERR_PNPM_PREPARE_PACKAGE Failed to prepare git-hosted package fetched from "{モジュールのURL}.git": {モジュール名}@0.1.0 pnpm-install: `pnpm install`
エラーをよく見るとオンプレ GitLab 上にある社内製モジュールのインストールに失敗していました。
pnpm の issue を見ると、以前のバージョンでも同様の問題が報告にあがっていたみたいです。(v9 より解消したとのことですが 🤔)
こちらは GitLab Package Registry 経由のインストールに変更することで解決しました。
Corepack の runtime のバージョンが変更されず、 npm-scripts の実行に失敗
一部アプリで型定義の生成に Node.js の Command-line API の import オプションを使っています。
検索領域のほぼ全てのリポジトリは v18.17.1 を利用していますが(24 年 10 月現在)、こちらのオプションは Node.js v18.18.0 からの機能のため、型生成方法変更の際に一部アプリだけ v18.19.1 を利用しています。
package.json では以下のように推奨バージョンをまとめており、そのリポジトリにあった Node.js のバージョンが Volta によって設定されます。
{
"engines": {
"node": "=18.19.1",
"pnpm": ">=9.1.4",
"npm": "use pnpm instead",
"yarn": "use pnpm instead"
},
"packageManager": "pnpm@9.1.4+sha512.9df9cf27c91715646c7d675d1c9c8e41f6fce88246f1318c1aa6a1ed1aeb3c4f032fcdf4ba63cc69c4fe6d634279176b5358727d8f2cc1e65b65f43ce2f8bfb0",
"volta": {
"node": "18.19.1"
}
}
しかしこの時、pnpm run
経由で型生成をすると失敗してしまいました。
> pnpm build:types
Error: Command failed: node --import {型生成コマンド}
node: bad option: --import
at ChildProcess.exithandler (node:child_process:419:12)
at ChildProcess.emit (node:events:514:28)
at maybeClose (node:internal/child_process:1091:16)
at Socket.<anonymous> (node:internal/child_process:449:11)
at Socket.emit (node:events:514:28)
at Pipe.<anonymous> (node:net:323:12) {
code: 9,
killed: false,
signal: null,
cmd: 'node --import {型生成コマンド}'
}
これは pnpm を Corepack 経由で管理・実行しているため、pnpm run
では Corepack の runtime のバージョンの Node.js でコマンドが実行されてしまうことが原因のようです。
runtime のバージョンはvolta install corepack
を実行した際の Volta の default の Node.js のバージョンが参照されるようです。
このため、runtime のバージョンを変更するには以下の手順が必要になります。
> volta install node@v18.19.1 # default の node の version を変更
> volta install corepack # Corepack の runtime を default の node の version に変更
> volta list all # version 確認
⚡️ User toolchain:
Node runtimes:
v18.17.1
v18.19.1 (current @ /path/to/repository/package.json)
Package managers:
Packages:
corepack@0.29.4 (default)
binary tools: corepack, pnpm, pnpx, yarn, yarnpkg
platform:
runtime: node@18.19.1
package manager: npm@built-in
一旦 Volta の default の Node.js のバージョンを変更する必要があるのは少し面倒ですね..(issueも立てられているようです)
pnpm i
が失敗する
Docker in Docker でGitLab CI の Docker in Docker でコンテナ内コンテナを立ち上げ、その中でpnpm i
を実行すると必ず job が失敗しました。
エラーも毎回同じというわけではなく、以下のようなエラーが主に出ました。(一部抜粋)
Progress: resolved 712, reused 0, downloaded 712, added 712, done
省略
+ typescript 4.9.5
Done in 28.8s
bash: line 7: 57 Aborted (core dumped) pnpm i
ERROR: Job failed: exit code 134
Progress: resolved 1, reused 0, downloaded 0, added 0
node[57]: ../src/node_mutex.h:229:node::ConditionVariableBase<Traits>::ConditionVariableBase() [with Traits = node::LibuvMutexTraits]: Assertion `(0) == (Traits::cond_init(&cond_))' failed.
1: 0xb83f50 node::Abort() [node]
2: 0xb83fce [node]
3: 0xbf4e0a node::TaskQueue<v8::Task>::TaskQueue() [node]
4: 0xbf1f9e node::PerIsolatePlatformData::PerIsolatePlatformData(v8::Isolate*, uv_loop_s*) [node]
5: 0xbf214c node::NodePlatform::RegisterIsolate(v8::Isolate*, uv_loop_s*) [node]
6: 0xac89a8 node::NewIsolate(v8::Isolate::CreateParams*, uv_loop_s*, node::MultiIsolatePlatform*, bool) [node]
7: 0xc4f5d9 node::worker::Worker::Run() [node]
8: 0xc507f8 [node]
9: 0x7fbc5c38f044 [/lib/x86_64-linux-gnu/libc.so.6]
10: 0x7fbc5c40e860 __clone [/lib/x86_64-linux-gnu/libc.so.6]
bash: line 7: 57 Aborted (core dumped) pnpm i
ERROR: Job failed: exit code 134
検索しても似た事象に遭遇している事例や issue などは見つかりませんでした。
色々試行錯誤してみた結果、コンテナ内コンテナではなく
test:
image: docker:24.0.5-dind
script:
- docker-compose -f docker-compose.ci.yml up -d
- |-
docker-compose -f docker-compose.ci.yml exec -T app bash -c '
cd /path/to/repository && \
corepack enable && \
corepack prepare pnpm@9.1.4 --activate && \
pnpm config set store-dir .pnpm-store && \
pnpm i'
- { テストの実行 }
外側のコンテナでpnpm i
を実行することでインストールに成功しました。
test:
image: docker:24.0.5-dind
script:
- corepack enable
- corepack prepare pnpm@9.1.4 --activate
- pnpm config set store-dir .pnpm-store
- pnpm i
- docker-compose -f docker-compose.ci.yml up -d
- { テストの実行 }
今回の移行の反省点・今後に向けての改善点
build 生成物の削除をたびたび忘れた
build 生成物は .gitignore
に追加することで、git 管理しないようにすることが多いかと思います。
build コマンドにもよりますが、前回の生成物が残っていると build 方法変更後に build に失敗してもすぐには気づけないかと思います。
npm-scripts や shell で build コマンドを wrap する際には、git clean
を使って前回生成物を削除するようにした方が良さそうです。
作業コスト・レビューコストが高かった
移行作業の大半は、「同じ作業を複数のリポジトリで行う」でした。
マルチリポジトリでは今回のような保守・改善作業の作業コストやレビューコストは高くなってしまいます。
(これはマルチリポジトリの明確なデメリットだと思われます)
Monorepo にするか、リポジトリは分割するか、改めて考えさせられる作業でした。
最後に
本記事では移行の際に躓いたエラーや今回の移行の反省点・今後に向けての改善点について紹介しました。
pnpm 移行は大変でしたが、その分環境構築や CI のスピードはかなり早くなりました。
本記事がこれから pnpm 移行する方のお役に少しでも立てば幸いです。
この記事を書いた人
力石 康平
2021 年新卒入社
最近、人生で初めて自作キーボードを購入しました。
Discussion