「起動が遅い」というレビューがついたまま、しばらく手を付けられずにいたアプリがあります。Expo 側で起動時間の退行が直ったパッチが出たと知り、これでひとつ片づくと思って package.json を開きました。
そこには "expo": "~57.0.9" と書いてありました。チルダが付いているのだから、パッチは自動的に新しいものが入っているのだろうと考え、何も変えずにビルドを回しました。
結果は変わりませんでした。入っていたのは、9つ前の 57.0.9 のままでした。
レンジの書き方を覚えているつもりで、実際に何が入るのかを一度も確かめていなかったのだと気づきました。恥ずかしい話ですが、個人開発で複数のアプリを抱えていると、この種の思い込みは静かに何か月も残ります。npm のふるまいを実際に走らせて確かめた記録を残します。
30日のあいだに、パッチは9回出ていました
まず、どれくらいの頻度でパッチが出ているのかを見ておきます。バージョンの公開日時は npm のレジストリが持っているので、手元から問い合わせられます。
npm view expo time --json返ってきた値のうち、57.0.x の直近10件を並べたものが次の表です。実行日は 2026年8月29日です。
| バージョン | 公開日時(UTC) |
|---|---|
| 57.0.9 | 2026-07-29 19:30 |
| 57.0.10 | 2026-08-04 07:40 |
| 57.0.11 | 2026-08-06 11:24 |
| 57.0.12 | 2026-08-10 15:43 |
| 57.0.13 | 2026-08-14 14:25 |
| 57.0.14 | 2026-08-17 10:48 |
| 57.0.15 | 2026-08-20 10:50 |
| 57.0.16 | 2026-08-24 07:56 |
| 57.0.17 | 2026-08-26 20:02 |
| 57.0.18 | 2026-08-28 10:48 |
7月29日から8月28日までのちょうど30日で、9回。平均すると、およそ3日に1回のペースです。
ここで押さえておきたいのは、数日前に調べた「最新のパッチ番号」が、今日はもう最新ではない可能性がそれなりに高い、ということです。手元のメモや、どこかで読んだ記事に書かれた番号をそのまま信じると、判断の前提がずれます。番号は毎回レジストリに聞くほうが確実です。
package.json のレンジは「入る版」を決めていません
次に、レンジの書き方ごとに実際どの版が選ばれるのかを確かめます。空のプロジェクトを作り、dependencies に expo だけを書いて、lockfile だけを生成させます。
npm install --package-lock-only書き方を5通り試した結果です。
| package.json の指定 | 解決された版 |
|---|---|
57.0.9 | 57.0.9 |
~57.0.9 | 57.0.18 |
^57.0.9 | 57.0.18 |
57.0.x | 57.0.18 |
"*"(制約なし) | 57.0.18 |
チルダもキャレットも、この場面では同じ 57.0.18 に落ちました。57.0.x でも、何も制約しない場合でも同じです。マイナーをまたぐ更新がまだ出ていない時期には、チルダとキャレットの差は表に出てきません。
ただし、両者が同じものというわけではありません。チルダは指定したマイナーの内側でパッチだけを許すので、~57.0.9 は 57.0.18 まで受け入れて 57.1.0 の手前で止まります。キャレットはマイナーの更新も許すので、^57.0.9 は 57.1.0 や 57.4.2 が出ればそちらを取ります。いまは 57.1.x がまだ公開されていないため、結果として同じ行になっているだけです。最初の新しいマイナーが出た日から、キャレットで書かれたプロジェクトだけが、誰もファイルを触っていないのに新しい変更を取り込み始めます。公開しているアプリでどちらを使うかは、そのうえで決めたいところです。
そして、これがつまずきの元でした。この表が示しているのは「ゼロから解決させたときに何が選ばれるか」です。すでに動いているプロジェクトで npm install を打ったときに何が入るか、ではありません。
決めているのは package-lock.json のほうです
同じ検証を、lockfile がある状態から始めてみます。
"expo": "57.0.9"と固定して lockfile を作る- package.json のほうだけ
"expo": "~57.0.9"に書き換える - lockfile を残したまま
npm installを打つ
実行した結果です。
step1 lock固定: 57.0.9
step2 lockあり install: 57.0.9
step3 lockなし install: 57.0.18step2 が答えでした。package.json に ~57.0.9 と書いてあっても、lockfile が 57.0.9 を指していて、それがレンジの内側に収まっている限り、npm はその版を維持します。上げにいってはくれません。
step3 は lockfile を消してから打った場合です。同じ package.json から 57.0.18 が入りました。つまり lockfile を Git に入れていないと、手元とビルドサーバーで別の版が入りうるということでもあります。ファイル名の大文字小文字が手元とサーバーで違って見える問題と、原因の形はよく似ています。関連する話はSunset.png と sunset.png は、手元の Mac では同じファイル、ビルドサーバーでは別のファイルですに書きました。
Rork からコードをエクスポートして手元で開発を始めた場合は、最初のコミットに package-lock.json が含まれているかを確認しておくと安心です。無視する側に入れてしまうと、ビルドのたびに版が動きます。Rork のコードをエクスポートしたら、git init より先に .gitignore を用意しますも併せてどうぞ。
ずれているかどうかは npm ci が教えてくれます
package.json と lockfile の食い違いは、npm install では静かに解決されてしまいます。声を上げてほしいときは npm ci を使います。
lockfile が 57.0.9 を、package.json が ~57.0.15 を指す状態で走らせた実際の出力です。
npm error code EUSAGE
npm error `npm ci` can only install packages when your package.json and
npm error package-lock.json or npm-shrinkwrap.json are in sync.
npm error Invalid: lock file's expo@57.0.9 does not satisfy expo@57.0.18止まってくれます。CI やビルド用のスクリプトで npm install ではなく npm ci を使う理由のひとつがここにあります。食い違ったまま気づかずに進むのではなく、その場で落ちてくれるほうが、後から原因を探すより安く済みます。
いま入っている版を1行で見たいだけなら、lockfile を直接読むのが速いです。
node -p "require('./package-lock.json').packages['node_modules/expo'].version"クラウドのビルドサービスも同じ規則で動きます。EAS Build はリポジトリの lockfile を見てインストールするので、3月から動いていない lockfile があれば、今日回したビルドも3月の中身になります。package.json にどんなレンジが書かれていても関係ありません。npm から見れば何も間違っていないので、警告も出ません。
パッケージマネージャが違っても、考え方は変わりません。Yarn なら yarn.lock、pnpm なら pnpm-lock.yaml が同じ役割を担い、どちらもレンジより先に lockfile を見ます。手元にあるのがどのファイルであれ、実際の版を記録しているのはそちらで、package.json のほうは意思表明に近いものだと捉えると混乱が減ります。
上げると決めたときに打つもの
上げる意思があるときは、版を明示して install します。レンジの外を指定すれば、lockfile も一緒に書き換わります。
npm install expo@57.0.18
npx expo install --fix2行目は、expo に紐づく他のパッケージの版を、その SDK が想定する組み合わせへ揃えるためのものです。expo だけを上げて周辺を放置すると、噛み合わない組み合わせのまま動かすことになります。
もうひとつ。ネイティブのディレクトリを手元に持っている構成では、上げたあとの prebuild で手を入れた変更が消えることがあります。上げる前に何が消えるのかを見ておく手順はExpo SDK 57 に上げる前に、prebuild で消えるネイティブ変更を洗い出すにまとめています。
メモリの膨らみを疑って自分のコードを読み込む前に、まず版を確かめるという順番も同じ話です。そちらはメモリが膨らむ原因を自分のコードに探す前に、worklet の数を数えてくださいで触れました。
今日、最初にやること
エディタで package.json を眺めるのをやめて、package-lock.json のほうを開いてください。node_modules/expo の version を1行見る。それだけで、自分のアプリが9つ前にいるのか、先頭にいるのかが分かります。
私自身、個人開発で回しているアプリのひとつで、これを確かめずに、直ったはずの退行を1か月ぶんそのままにしていました。同じ回り道をされないよう、この記録が少しでも役に立てば幸いです。