📦

18リポジトリをnpmからpnpmに移行した際の学び

はじめに

こんにちは、エンジニアの力石です。
フォルシアでは商品販売プラットフォームwebコネクトを提供しており、私はその中の検索システム(検索領域)の開発・運用保守を行っています。
検索領域はマイクロサービスアーキテクチャで構築されており、機能毎(コンポーネント毎)にリポジトリを分けるマルチリポジトリ構成を採用しています。
そして最近、検索領域では npm を利用している全 18 リポジトリのパッケージマネージャーを pnpm へと移行しました。
本記事では移行の際に躓いたエラーや今回の移行の反省点・今後に向けての改善点について紹介します。
(移行手順についてはさまざまな記事が出ているので、本記事では割愛します)

移行の作業内容

今回の移行では以下のような作業を 18 リポジトリで行いました。

作業内容(一部)
  • README や開発環境の整備
  • Volta の導入
  • npm runpnpm 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 に関連したエラーをいくつかピックアップして紹介します。

GitLab CI で pnpm i が失敗する

開発環境では特に失敗することがなかった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も立てられているようです)

Docker in Docker でpnpm iが失敗する

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 年新卒入社
最近、人生で初めて自作キーボードを購入しました。

FORCIA Tech Blog

Discussion