Node.js 14から18へアップデートする方法について
Corepackを使ってNode.jsをアップデートする ⬆️⬆️というタイトルで、Node.js 14からNode.js 18へのアップデートする方法について話した。
Node.js 14は4月末でEOLで、Node.js 18までアップデートする必要があるけど、npmの変更が混ざって大変です。
— azu (@azu_re) April 28, 2023
Corepackを使うことで、Node.jsとnpmのアップデートを同時にやらなくても良くなり、問題を分割して対応できます!
Corepackを使ってNode.jsをアップデートする ⬆️⬆️https://t.co/mRHsBcYbpn pic.twitter.com/HiiCe7c5YE
Note: Node.js 14は2023-04-30でEOLで、Node.js 16は2023-09-11でEOLなので、長期間動かすアプリケーションなら実質的にNode.js 18へのアップデートが必要になります。
Node.jsのアップデートの難しさはライブラリにある
Node.jsをアップデートするときに、一緒に直さないといけない問題は次のようなものがあると思います。
- OSの問題
- Node.js 18はglibc 2.28+が必要になるので、古いOSだとNode.jsがインストールできないことがあります
- Native Addonの問題
- Node-APIという抽象レイヤーがあるとはいえ、Native AddonはNode.jsのバージョンアップでよく壊れます
- peerDependenciesの問題
- これはNode.jsではなく、npmが6から8で
peerDependencies
の扱い方が変わったことに起因する問題です
- これはNode.jsではなく、npmが6から8で
- Node.js Runtimeの問題
- Node.jsのRuntimeが変わったり、
fs
といったコアモジュールの変更による影響を受けることがあります
- Node.jsのRuntimeが変わったり、
- 利用してるライブラリがサポートしていない問題
- Node.js Runtimeが変わったことで、動かなくなるライブラリがあります
Corepackを使ってNode.jsをアップデートする ⬆️⬆️のスライドでも話していますが、実際にアップデートしてみると、大体はライブラリの問題にぶつかることが多いです。 そのため、根本的な対応はライブラリのアップデートをしましょうという話になったりします。
- OSの問題 → OSの問題
- Native Addonの問題 → ライブラリの問題
- peerDependenciesの問題 → ライブラリの問題
- Node.js Runtimeの問題 → アプリケーションコードの問題
- 利用してるライブラリがサポートしていない問題 → ライブラリの問題
しかし、ライブラリが壊れてるという状況は古いバージョンのライブラリを利用していることがほとんどです。 そのため、ライブラリをアップデートするにはメジャーバージョンを上げる必要があったりして、結構大変になります。
特に今回のNode.js 18へのアップデートは、npmの変更が混ざっているため、npmの変更による影響を受けるライブラリが多いです。 Node.jsにはnpmが同梱されており、Node.jsとnpmのアップデートは同時に行われてしまい、問題の影響範囲が大きくなります。 これに対して、Node.jsのアップデートとnpmのアップデートを分けて行うと、一度に対応しないといけない問題が減ります。
また、Node.jsと同梱されているnpmのバージョンを見てみるとわかりますが、Node.jsのメジャーアップデート と npmのメジャーアップデートのタイミングは一致しません。Node.js 18のminor updateで、npmのmajor updateであるnpm 9がバックポートされたりしています。
これができる条件などは次のページにまとまっていますが、ユーザー的にはnpmのBREAKING CHANGEがNode.jsのminor updateに含まれる形にはなります。
Node.jsのアップデートとは別の問題として、チーム間でNode.js 18を使うといっても人によってnpmのバージョンが違うという問題も起きやすいです。Node.js自体のバージョン管理は.node-version
や.nvmrc
などで管理しているプロジェクトも多いと思います。
同じようにnpmのバージョンを管理する仕組みとして、Corepackがあります。
Corepackを使ってnpmのバージョンを管理する
Corepackは、パッケージごとに利用するパッケージ管理ツール(npmやyarnやpnpm)とバージョンを指定できる仕組みです。Corepackではpackage.json
にpackageManager
に利用するnpmのバージョンなどを指定できます。
{
"name": "my-project",
"version": "1.0.0",
"packageManager": "[email protected]"
}
また、Corepackを使うことで、Node.jsとnpmのアップデートを同時にやらなくても良くなり、問題を分割して対応できます。 たとえば、npm 6のまま、Node.js 14からNode.js 18へアップデートするという動き方も可能です。
CorepackはNode.jsに同梱されていますが、まだExperimentalというステータスなので、デフォルトでは無効となっています。そのため、利用するにはcorepack enable npm yarn pnpm
というコマンドを実行して有効化する必要があります。
$ corepack enable npm yarn pnpm
peerDependenciesの問題を回避する
Corepackを使ってNode.jsをアップデートする ⬆️⬆️のスライドの話に戻ると、CorepackはpeerDependenciesの問題
への対処として、npm 6のままNode.jsをアップデートできます。
別の方法として、--legacy-peer-deps
オプションがnpmには用意されています。
おそらく大体の人は、次のようなpeerDependenciesの問題
から発生するcode ERESOLVE
のエラーをみると、--legacy-peer-deps
オプションを使ってしまうと思います。
$ npm install
npm ERR! code ERESOLVE
npm ERR! ERESOLVE could not resolve
npm ERR!
npm ERR! While resolving: @shiftcoders/[email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/tslib
npm ERR! tslib@"^2.1.0" from the root project
npm ERR! tslib@"^2.0.0" from @aws-sdk/[email protected]
npm ERR! node_modules/@aws-sdk/abort-controller
npm ERR! @aws-sdk/abort-controller@"3.20.0" from @aws-sdk/[email protected]
npm ERR! node_modules/@aws-sdk/node-http-handler
npm ERR! @aws-sdk/node-http-handler@"3.21.0" from @aws-sdk/[email protected]
npm ERR! node_modules/@aws-sdk/client-sso
npm ERR! @aws-sdk/client-sso@"3.21.0" from @aws-sdk/[email protected]
npm ERR! node_modules/@aws-sdk/credential-provider-sso
npm ERR! 1 more (@aws-sdk/client-sts)
npm ERR! 46 more (@aws-sdk/client-sso, @aws-sdk/client-sts, ...)
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer tslib@"^1.10.0" from @shiftcoders/[email protected]
npm ERR! node_modules/@shiftcoders/dynamo-easy
npm ERR! dev @shiftcoders/dynamo-easy@"^7.1.0" from the root project
npm ERR!
npm ERR! Conflicting peer dependency: [email protected]
npm ERR! node_modules/tslib
npm ERR! peer tslib@"^1.10.0" from @shiftcoders/[email protected]
npm ERR! node_modules/@shiftcoders/dynamo-easy
npm ERR! dev @shiftcoders/dynamo-easy@"^7.1.0" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR!
--legacy-peer-deps
もこのpeerDependenciesの問題を回避するためのオプションです。
しかし、npm i --legacy-peer-deps
のように毎回つけている場合は、つけ忘れの問題があります。(つけ忘れると当然peerDependenciesの問題が復活します。)
--legacy-peer-deps
を使う場合は、npm config --location=project set legacy-peer-deps=true
を実行して.npmrc
に設定した方が良いでしょう。
npm config --location=project set legacy-peer-deps=true
を実行すると、次のような.npmrc
が作成されます。
この.npmrc
があるディレクトリでは、npm
コマンドが自動的に設定を読み取るようになります。
legacy-peer-deps=true
--legacy-peer-deps
も宣言的に設定するためには、.npmrc
を利用する必要があります。
Corepackもpackage.json
にpackageManager
フィールドの追加が必要です。
peerDependenciesの問題
の一時的な対処として、何かしらの設定を足す点はどちらも同じです。
そのため、どのような流れでアップデートしていくかは好みの問題と言えます。
どちらの場合も最終的な根本対応は、ライブラリをアップデートするに行き着くと思います。
アップデートを小さく進める
Node.jsのアップデートする際に、Node.jsとライブラリを同時にアップデートしてしまう方法も選べると思います。 小さなアプリケーションならこの方法が簡単で良いですが、大きなアプリケーションになるとデメリットが目立ってきます。
実際にNode.js 18へのアップデートをいくつかやってみて、Corepackを使った方法が実際の差分やPRの小さく作って進められたのでよかったと思います(--legacy-peer-deps
も多分同じステップを取れますが、好みではなかった)。
実際の作業も、次のようなステップに分けて進めることができ、実際に出したPull Requestもそれぞれのステップごとにできました。
- (Node.js 14のまま): プロジェクトのインストールステップやDockerfileでcorepackを有効化
- (Node.js 14のまま): Node.js 18では動かなかったライブラリをアップデート
- 具体的にはJestがReferenceError: AbortSignal is not definedで壊れていました
- CI: Ubuntuマシンイメージのアップデート
- Node.js のバージョン番号だけを変更する
- Dockerfileの
FROM node:14.x.x
をFROM node:18.16.0
に変更 - CIのバージョン指定を変更
ライブラリも一緒にアップデートしてしまうと、Node.js 18にアップデートして問題があったのかライブラリに問題があったのか切り分けが難しくなります。 そのため、Node.js 18へのアップデートは、Node.jsを14 → 18とするだけのPRを出せるようにすると良いと思います。
これができるのは、Node.js 14では動くけど、Node.js 18では動かないライブラリはかなり少ないからです。 Node.js 14のままNode.js 18でも動くアプリケーションにしてから、Node.jsだけをアップデートすると問題があった時のrevertも簡単です。
実際にスライドでも出てきたNode.js 15+でのUnhandled Rejectionの挙動変更は、デプロイしようとして気付いた問題でした。 その時は、すぐrevertしてNode.js 14に戻すことができたので、バージョンアップ作業のPRを分割しておいてよかったなと思いました。
まとめ
Corepackを使ってNode.jsをアップデートする ⬆️⬆️というスライドの補足的な記事でした。
Node.jsのアップデート時にライブラリもコードもまとめてアップデートしたくなってしまいますが、アプリケーションがおおきくなるほど大変です。 そのため、問題を分割してアップデートしていくのが、アップデートする人、レビューする人、アプリケーションにとっても良いと思います。 Node.jsではCorepackという、Node.jsとnpmの問題の切り分けに使えるツールがあります。
npm 6からnpm8には、かなり大きな影響がある変更が含まれているので、Corepackを使って問題を切り分けて進めるのはどうでしょうか?という話でした。
お知らせ欄
JavaScript Primerの書籍版がAmazonで購入できます。
JavaScriptに関する最新情報は週一でJSer.infoを更新しています。
GitHub Sponsorsでの支援を募集しています。