yarn v1→npmに移行した
こんにちは!CastingONEの大沼です。
はじめに
弊社ではずっと使い慣れたyarn v1を使っていましたが、相当古くなってしまい、いよいよstorybookのバージョンを上げられないという事態に直面しました😢
[Bug]: string-width dependency stops storybook from executing
普通に考えたらyarnをアップグレードする必要があるのですが、yarnの独自機能は使わずにアップグレードするのは結構手間であまりモチベーションが湧きませんでした。。(だからこそ今までv1で頑張っていたんですよね)
そんな状況で改めて標準搭載されているnpmの今のスペックを見ていたのですが、昔と違ってきちんとlockファイルを生成するし、workspaceも使えて、弊社の開発要件は満たせているので思い切ってnpmに乗り換えても良いなと思い始めました。
他の開発メンバーも「yarnをアップグレードするよりnpmに乗り換えた方が無難なのでは?」という意見をいただき、npmに乗り換えることを決心しました😊
移行作業
npmへの移行はパッと調べた感じ良さげな移行手順が見当たらなかったため、単純にnpm install
して乗り換えることにしました。一応以下のドキュメントや記事をみるとyarn.lock
も考慮してインストールされなそうな雰囲気はありましたが、できるだけパッケージの違いを小さくする対応を入れてからインストールするようにしました。
具体的には以下のような流れでやりました。
1. package.jsonのバージョンを固定
まずはパッケージのバージョンが大きくずれないように、package.json
で指定しているバージョンは全てキャレットを外して固定するようにしました。
{
"dependencies": {
- "@mui/material": "^5.14.0"
+ "@mui/material": "5.14.0"
}
}
これによってnpm install
で改めてインストールしてもバージョンが固定されます。ただし、さらにこのパッケージのdependenciesで指定されているものについてはこれだけだとずれてしまう可能性があるのは注意する必要があります。
例えば上の例で書いた@mui/material
は5.14.0と指定することはできましたが、これに依存している@babel/runtime
やreact-transition-group
などのパッケージはキャレット付きで指定されているため、マイナーバージョンがずれる可能性があります。
"@mui/material@5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.14.0.tgz#3d2afb4a3643774370cb5add873abcbbe8e7af27"
integrity sha512-HP7CP71NhMkui2HUIEKl2/JfuHMuoarSUWAKlNw6s17bl/Num9rN61EM6uUzc2A2zHjj/00A66GnvDnmixEJEw==
dependencies:
"@babel/runtime" "^7.22.5"
"@mui/base" "5.0.0-beta.7"
"@mui/core-downloads-tracker" "^5.14.0"
"@mui/system" "^5.14.0"
"@mui/types" "^7.2.4"
"@mui/utils" "^5.13.7"
"@types/react-transition-group" "^4.4.6"
clsx "^1.2.1"
csstype "^3.1.2"
prop-types "^15.8.1"
react-is "^18.2.0"
react-transition-group "^4.4.5"
これは少し気になるところですが、単純に全体的にパッケージのバージョンを上げたんだと考えることもできるので、全体的に触って問題なければこのままで進めることにしました。
npm install
を実行してエラーを潰していく
2. 実際にパッケージのバージョンを固定したので、次は実際にnpm install
をしてみて、エラーになってしまったところを潰していきました。対応した内容をいくつか挙げると以下のようなものがありました。
casone-libのpeerDependenciesを調整
弊社ではプライベートの外部パッケージがあるのですが、以下のようなエラーが出てそのパッケージのpeerDependenciesで指定しているTypeScriptのバージョンと合わないと言われました。yarn v1では特に言われていなかったので、npmだとpeerDependenciesもちゃんとみるんだなと思いました👀
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: tenant-app@0.1.0
npm ERR! Found: typescript@5.1.6
npm ERR! node_modules/typescript
npm ERR! dev typescript@"5.1.6" from tenant-app@0.1.0
npm ERR! apps/tenant
npm ERR! tenant-app@0.1.0
npm ERR! node_modules/tenant-app
npm ERR! workspace apps/tenant from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer typescript@"^4.0.2" from @casone-lib/dotenv@0.0.1
npm ERR! node_modules/@casone-lib/dotenv
npm ERR! @casone-lib/dotenv@"git+ssh://git@github.com:CastingONE/frontend-package-dotenv.git#dist-v0.0.1" from tenant-app@0.1.0
npm ERR! apps/tenant
npm ERR! tenant-app@0.1.0
npm ERR! node_modules/tenant-app
npm ERR! workspace apps/tenant from the root project
これは原因がハッキリとしているので外部パッケージのバージョンを全体的に上げてpeerDependenciesも5系に上げました。
@testing-library/react-hooksから@testing-library/reactに完全に乗り換えた
続いてのエラーは以下で、@testing-library/react-hooks
のpeerDependenciesとマッチしていないと言われました。
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: tenant-app@0.1.0
npm ERR! Found: react@18.2.0
npm ERR! node_modules/react
npm ERR! react@"18.2.0" from tenant-app@0.1.0
npm ERR! apps/tenant
npm ERR! tenant-app@0.1.0
npm ERR! node_modules/tenant-app
npm ERR! workspace apps/tenant from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.9.0 || ^17.0.0" from @testing-library/react-hooks@8.0.1
npm ERR! node_modules/@testing-library/react-hooks
npm ERR! dev @testing-library/react-hooks@"8.0.1" from tenant-app@0.1.0
npm ERR! apps/tenant
npm ERR! tenant-app@0.1.0
npm ERR! node_modules/tenant-app
npm ERR! workspace apps/tenant from the root project
調べてみるとどうやらそもそもReact18系では@testing-library/react-hooks
ではなく@testing-library/react
を使うのが正しいようなので、そちらに乗り換えました。yarn installでは気づかなかったので、npm installすることで事前に気づけるのは良いですね😊
tanstack-queryのバージョンを揃える
これでひとまずinstall自体は完了することができて、Next.jsを起動してみるのですが、tanstack周りでエラーが出て画面が表示されませんでした。詳細を見るとどうやら@tanstack/react-query
と@tanstack/react-query-devtools
でバージョンが4.32.0
と4.29.15
と微妙に違っていたのが原因でした。yarnだとよしなに調整していたんですかね🤔 そもそもバージョンは揃えるべきなので4.32.0
で揃えることで解消されました。
3. CIでも動くように調整
最後にCIでも動くように調整しました。ローカルではインストールできましたが、CI上でのインストールでもいくつかハマったのでそれについて挙げていきます。
sshを使った外部パッケージの指定方法がyarnとnpmで変わった
弊社ではプライベートの外部パッケージがありますが、これはローカルではSSHで認証して、CIではトークン付きのHTTPSで認証してインストールしています。
- name: Install
run: |
git config --global url."https://${{ secrets.CASONE_BOT_TOKEN }}:x-oauth-basic@github.com/CastingONE".insteadOf "git@github.com:CastingONE"
yarn install
ただこれを単純にnpm install
だけを変えても上手くリポジトリを見つけることができず、最終的には以下のように変えたら上手くいきました。insteadOf
をgit@github.com:CastingONE
からssh://git@github.com/CastingONE
に変えただけです。
- name: Install
run: |
- git config --global url."https://${{ secrets.CASONE_BOT_TOKEN }}:x-oauth-basic@github.com/CastingONE".insteadOf "git@github.com:CastingONE"
+ git config --global url."https://${{ secrets.CASONE_BOT_TOKEN }}:x-oauth-basic@github.com/CastingONE".insteadOf "ssh://git@github.com/CastingONE"
- yarn install
+ npm ci
理由はあんまり分かっていないですが、yarn.lock
がある状態でnpm installすると以下のような差分が出ていたため、指定するパスも:
から/
に変える必要があったのかなと思っています🤔(ssh://
も追加したのは念の為)
npm install
しないとfailした
ルートディレクトリでpackagesディレクトリ配下のパッケージはworking-directory
を指定して該当のディレクトリでインストールからテストの実行などのタスクを実行していましたが、上手くインストールできませんでした。具体的に言うとインストール自体はできていますが、その後のpostinstall
で自前のESLintルールをコンパイルしようとすると失敗していました。
jobs:
test:
runs-on: ubuntu-22.04
timeout-minutes: 10
defaults:
run:
working-directory: ./packages/components
steps:
- uses: actions/checkout@v3
with:
token: ${{ secrets.CASONE_BOT_TOKEN }}
- uses: actions/setup-node@v3
with:
node-version-file: "package.json"
cache: "npm"
# postinstallでeslintのカスタムルールをコンパイルしているのだが、それに失敗する
- name: Install
run: |
git config --global url."https://${{ secrets.CASONE_BOT_TOKEN }}:x-oauth-basic@github.com/CastingONE".insteadOf "ssh://git@github.com/CastingONE"
npm ci
- name: Run Testing
run: npm run test
インストールに成功するCIとそうでないCIがあって原因特定に苦労しましたが、どうやらnpmでインストールする場合はルートディレクトリで行わないといけないようで、サブディレクトリでインストールすると、そのディレクトリにあるpackage.json
のものしかインストールしないようです。yarn v1系ではどこにいてもルートのyarn.lockを見てインストールしてくれていましたが、npmだとその辺も注意する必要があるようです。
jobs:
test:
runs-on: ubuntu-22.04
timeout-minutes: 10
defaults:
run:
working-directory: ./packages/components
steps:
- uses: actions/checkout@v3
with:
token: ${{ secrets.CASONE_BOT_TOKEN }}
- uses: actions/setup-node@v3
with:
node-version-file: "package.json"
cache: "npm"
- name: Install
run: |
git config --global url."https://${{ secrets.CASONE_BOT_TOKEN }}:x-oauth-basic@github.com/CastingONE".insteadOf "ssh://git@github.com/CastingONE"
npm ci
+ # ルートディレクトリからじゃないと全てのパッケージをインストールしないため、ルートディレクトリを指定する
+ working-directory: ./
- name: Run Testing
run: npm run test
運用面での調整
以上で無事npmで動くようになりました!🎉 後は今後の運用にあたっていくつか設定を加えました。
only-allow npm
を設定
yarnでinstallされないように 今までyarnを使ってきたため、ついついyarn install
しちゃうと思うため、それを防止する設定を入れました。only-allowというパッケージを使うと特定のパッケージマネージャーのみに絞ることができるため、これをpreinstall
に仕込んでおきます。
{
"scripts": {
"preinstall": "npx only-allow npm"
}
}
パッケージの追加は今後固定のみ受け付けるようにlintを入れる
今回念の為package.json
に書かれているパッケージのバージョンを固定しましたが、今後は最初から固定になっていた方が安心なので以下の記事などを参考にして、バージョンが固定されているかをCIでチェックするようにしました。
ちなみに、デフォルトではnpm installでパッケージを追加すると^
がついてしまいますが、それを取りたい場合はnpm config set save-exact true
を実行するか、インストール時に-E
オプションをつけると^
なしでインストールできるようです。
その他
その他で作業中に気づいたこと以下に残します。
npm installするとyarn.lockも更新されるが、両方使うことは難しそう
npm installでもyarn.lock
を見てくれるという情報がありましたが、その影響かyarn.lock
ファイルがある状態でnpm installするとyarn.lock
ファイルに変更が入ります。
これによってyarn installでも同じパッケージがインストールされるなら移行期間を設けても良かったかなぁと思いましたが、以下のような変更が出て、併用するのは難しそうでした。
- registry先がyarnpkgからnpmjsに変わる
- registry先が変わってしまうため、そのままyarn installしようとしても上手くいかなかった
- workspaceのパッケージが追加されるが、場所がローカルファイルのパスで入ってしまう
- ローカルファイルパスをgit commitしたくないのでこのdiffは困る
"@mui/material@5.14.0":
version "5.14.0"
- resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.14.0.tgz#3d2afb4a3643774370cb5add873abcbbe8e7af27"
+ resolved "https://registry.npmjs.org/@mui/material/-/material-5.14.0.tgz"
+"@casone/components@file:[個人のファイルパス]":
+ version "1.0.0"
+ resolved "file:packages/components"
+ dependencies:
+ "@mui/x-date-pickers" "6.8.0"
+ "@tanstack/react-table" "8.15.3"
+ colord "2.9.3"
+ deep-object-diff "1.1.9"
+ lodash-es "4.17.21"
+ react "18.2.0"
+ react-colorful "5.6.1"
+ react-cropper "2.3.3"
+ react-device-frameset "1.3.4"
+ react-dom "18.2.0"
+ react-nl2br "1.0.4"
+ react-swipeable-views "npm:@gromy/react-swipeable-views@0.15.1"
+ swiper "10.3.1"
タスクの実行自体はyarnを使っても問題ない
yarn installを防ぐためにnpx only-allow npm
をpreinstallに入れましたが、これはあくまでinstall時にチェックが走るだけで、通常のタスク実行ではスルーされます。したがって yarn workspace @casone/components storybook
みたいにコマンドを叩いてもそのまま実行されてしまいますが、特に問題なく動いていました。パッケージを入れる時が重要なだけで、タスクの実行自体は何でやっても変わらないんですかね🤔 問題なさそうな感じだったので一旦はCIやpackage.json内で書かれているyarnを使ってタスクを実行しているものはそのままにして、徐々にnpmに変えていこうかなと思っています。
おわりに
以上がyarn v1からnpmに移行した流れでした。
npmの方がyarn v1よりも厳格で、パッケージの依存関係も綺麗になって良かったです。
また、npmで安定稼働を確認した後にstorybookのアップデートも試しましたが、無事アップデートすることができました😊
弊社ではいっしょに働いてくれるエンジニアを募集中です。社員でもフリーランスでも、フルタイムでも短時間の副業でも大歓迎なので、気軽にご連絡ください!
Discussion