🚝

yarn v1 → pnpm + monorepo への移行で出来るだけ依存関係を保ちたい

2023/11/25に公開

TL;DR

の2つでどうにかしました!😊

背景

いま関わっているプロジェクトで、yarn v1 の monorepo から pnpm monorepo への構成変更をする必要がありました。

悩みまくって丸1日費やしてしまったので、この記事をもって誰かのお役に立てれば嬉しい。

この記事の前提

pnpm や yarn の詳細は記載しません。公式docをどうぞ🙇‍♂️

タスクの前提

既存リポジトリは中途半端なTypeScript実装でかなり危うい状態。
また、自動テストも殆どなく、カバレッジも低い。
ただし、本番で動いているコードなので、実装内容へ出来る限り手は付けたくない。

現構成

構成を見ていきます。

ディレクトリツリー

├── hoge
│   ├── package.json
│   └── yarn.lock
├── fuga
│   ├── package.json
│   └── yarn.lock
├── hoo
│   ├── package.json
│   └── yarn.lock
├── package.json
└── yarn.lock

yarn.lockが散らばってる状態。
また、workspaceがカテゴライズされてなくて分かりづらい。

/package.json

  "scripts": {
    "hoge:build": "yarn --cwd ./hoge build",
    "fuga:build": "yarn --cwd ./fuga build",
  }

workspaces のプロパティ無し。
yarn --cwd でディレクトリだけ変更してスクリプトを通しているような状況。

現構成のメリデメ

良く言えば疎結合。
悪く言えばmonorepoのメリットはあんまりない。本当に git 一元管理のためだけ、という感じ。

構成変更によるメリット

ここで一旦、pnpm + workspace によるメリットをおさらい。

  1. パッケージのディスクスペース節約
  2. パッケージ依存関係の整理(不整合がなくなる)
  3. workspaceに対するscript実行の容易さ
  4. pnpm によるインストール時間の短縮
  5. コード共有の容易さ
  6. IDEへの統合

pnpm のメリットとworkspace対応のメリット両方含まれてます。
移行するメリットは大きいと思っています。

やり方1「単純なworkspace化」 ※ 失敗例

一番最初に思いつく方法として、単純なworkspaceへの移行。
祈りながら実施。

ステップ

  1. pnpm-workspace.yaml
packages:
  - 'hoge'
  - 'fuga'
  - 'hoo'
  1. install
pnpm i
  1. build
pnpm -r build

結果

TypeScriptのエラーが出まくった。🥶

src/api/devices.ts:10:36 - error TS7016: Could not find a declaration file for module 'lodash'. '/root/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/lodash.js' implicitly has an 'any' type.
  Try `npm i --save-dev @types/lodash` if it exists or add a new declaration (.d.ts) file containing `declare module 'lodash';`

10 import { isNaN, isUndefined } from 'lodash'
                                      ~~~~~~~~

src/hoge.ts:4:20 - error TS7016: Could not find a declaration file for module 'lodash'. '/root/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/lodash.js' implicitly has an 'any' type.
  Try `npm i --save-dev @types/lodash` if it exists or add a new declaration (.d.ts) file containing `declare module 'lodash';`

4 import * as _ from 'lodash'

... 以下大量 ...

yarn だと出ないのにどうして...

ここで、全部のType定義を直していくという選択肢も考えましたが、多すぎる。
また、ロジックに手を付ける必要もあった。断念。

原因の推測

恐らく下記のあたりが原因だろうと予想しました。

  1. yarn.lockとpnpm-lock.yaml でパッケージ依存バージョンが変わったため
  2. pnpm と yarn でパッケージの依存解決手法が違うため

やり方2「pnpm import と node-linker: hoisted適用」

成功したやつ。
pnpm の下記2つを利用します。

上記の公式docが詳しい。

ステップ

  1. .npmrc
node-linker=hoisted
strict-peer-dependencies=false

※ 互換性を上げるため、セオリー通りstrict-peer-dependenciesも定義してます。インストール自体は完遂してたので本来は不要のはず。

  1. pnpm-workspace.yaml
packages:
  - 'hoge'
  - 'fuga'
  - 'hoo'
  1. import
pnpm import

これで、project rootに pnpm-lock.yaml ができる

  1. install
pnpm i
  1. build
pnpm -r build

結果

通った!😊

hoge build$ tsc
└─ Done in 9.2s
fuga build$ tsc
└─ Done in 13.2s
hoo build$ react-app-rewired build
│ Find out more about deployment here:
│   https://cra.link/deployment
└─ Done in 16.7s
...

結論

今回は node-linker=hoisted がいい動きをしてくれました。
pnpm import は補佐的な役割。

もし yarn workspace を元々つかっていて、pnpm に移行するという場合は、この手順で問題は起きないはず。

補足

勘のいい人は気づいていると思いますが、結局それぞれのworkspaceにあった yarn.lock のバージョン設定は破棄された形になりました。
依存関係が正しく設定されていれば問題は起きないはずですが、障害になる確率もゼロではないです。

Discussion