【Yarn】パッケージをオフラインでインストールする


背景

Yarn v2についての動画で出てきた「v1のオフラインインストール」について、よく知らなかったので調べてみた。

概要

Yarnのオフラインインストールとは、パッケージを追加する際に実態のtarballをローカルにダウンロードしておき、後からそれを用いてパッケージをインストール出来るようにしたものである。パッケージをインストールする際に yarn.lock ファイルにダウンロード元のURLが記録されているため、URLが有効な限りは何度実行しても同一のファイルがダウンロードされることが保証されるが、ダウンロード元のダウンや閉鎖等によってURLが無効になった場合はダウンロードに失敗する。このような場合に備えて予めtarballをダウンロードしておき、オフライン時に利用するのがオフラインインストールである。

詳解

事前準備

まずはじめに、適当なディレクトリを作成して以下のファイルを配置する。

package.json
{
  "name": "yarn-offline",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "is-array": "^1.0.1",
    "left-pad": "^1.3.0",
    "mime-types": "^2.1.27"
  }
}

パッケージを追加し、ディレクトリの構成が以下の通りになっていることを確認する。

$ yarn
(略)
$ ls
node_modules    package.json    yarn.lock

オフラインミラーの設定

オフラインミラー(ダウンロードされるtarballが配置されるパス)を設定する。ここで指定するパスはホームディレクトリからの相対パスであることに留意する。

$ yarn config set yarn-offline-mirror ./npm-packages-offline-cache
yarn config v1.22.0
success Set "yarn-offline-mirror" to "./npm-packages-offline-cache".
✨  Done in 0.04s.
$ yarn config set yarn-offline-mirror-pruning true
yarn config v0.23.2
success Set "yarn-offline-mirror-pruning" to "true".
✨  Done in 0.06s.

yarn config set で追加した設定は ~/.yarnrc に記録されている。これを特定のプロジェクトでのみ利用可能にするため、ファイルをプロジェクトディレクトリ内に移動する。

$ mv ~/.yarnrc ./

パッケージのダウンロード

オフラインインストールを用いる場合でも、yarn.lock は変更されないためパッケージ管理に影響を及ぼさない。これを検証するためにファイルをコピーしておく。

$ cp yarn.lock yarn.lock.before

node_modules/yarn.lock を削除して再度パッケージの追加を行う。その後、~/npm-packages-offline-cache にtarballがダウンロードされていることを確認する。

$ rm -rf node_modules/ yarn.lock
$ yarn
yarn install v1.22.4
info No lockfile found.
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
✨  Done in 0.52s.
$ ls ~/npm-packages-offline-cache
is-array-1.0.1.tgz      left-pad-1.3.0.tgz      mime-db-1.44.0.tgz      mime-types-2.1.27.tgz

yarn.lock が変更されていないことを確認する。

$ diff yarn.lock yarn.lock.before
$ rm yarn.lock.before

オフラインインストールの実行

オフラインインストールの検証を行うため、node_modules/ とキャッシュを削除する。

$ rm -rf node_modules/
$ yarn cache clean
yarn cache v1.22.4
success Cleared cache.
✨  Done in 0.10s.

ネットワークを切断してオフラインにする。続けて yarn --offline を実行し、オフライン時でもパッケージが追加できることを確認する。

thara@ht-mbp-2:yarn-offline $ ls
package.json            yarn.lock
thara@ht-mbp-2:yarn-offline $ yarn --offline
yarn install v1.22.4
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
✨  Done in 0.55s.
thara@ht-mbp-2:yarn-offline $ ls
node_modules            package.json            yarn.lock

検証

上記に続けて、ダウンロード済みtarball、キャッシュ、yarn.lock の関係について検証する。

オフラインインストールはキャッシュが無い場合に行われる

オフラインインストール時のパッケージとしてダウンロード済みtarballが用いられたように思えるがこれは正確ではない。キャッシュにもtarballが存在しており、こちらが優先して用いられるため「キャッシュが無ければダウンロード済みtarballが用いられる」という説明が正しい。以下を実行し、~/npm-packages-offline-cache が存在しなくともオフラインインストールに成功することを確認する。

$ rm -rf node_modules/
$ mv ~/npm-packages-offline-cache ~/npm-packages-offline-cache-renamed
$ yarn --offline
yarn install v1.22.4
info No lockfile found.
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
✨  Done in 0.28s.

キャッシュとダウンロード済みtarballの両方が存在しなければ、当然オフラインインストールは失敗する。

$ rm -rf node_modules/
$ yarn cache clean
yarn cache v1.22.4
success Cleared cache.
✨  Done in 0.05s.
$ yarn --offline
yarn install v1.22.4
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
error Can't make a request in offline mode ("https://registry.yarnpkg.com/is-array/-/is-array-1.0.1.tgz")
info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.

ダウンロード済みパッケージの検証には yarn.lock が用いられる

~/npm-packages-offline-cache を元に戻し、yarn.lock を削除してオフラインインストールに失敗することを確認する。yarn.lock が存在しない場合は、インストールするべきパッケージの検証が出来ないためである。

$ mv ~/npm-packages-offline-cache-renamed/ ~/npm-packages-offline-cache
$ rm yarn.lock
$ yarn --offline 
yarn install v1.22.4
info No lockfile found.
[1/4] 🔍  Resolving packages...
error An unexpected error occurred: "There should only be one folder in a package cache (got  in /Users/thara/.cache/yarn/v6/npm-is-array-1.0.1-e9850cc2cc860c3bc0977e84ccf0dd464584279a-integrity/node_modules)".
info If you think this is a bug, please open a bug report with the information provided in "/Users/thara/Desktop/yarn-offline/yarn-error.log".
info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.

キャッシュが存在する場合は yarn.lock は不要である

端末をオンライン状態にし、パッケージを追加してキャッシュを作成する。

$ yarn 
yarn install v1.22.4
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
✨  Done in 0.57s.
$ yarn cache list
yarn cache v1.22.4
Name       Version Registry Resolved                                                                                                
is-array   1.0.1   npm      https://registry.yarnpkg.com/is-array/-/is-array-1.0.1.tgz#e9850cc2cc860c3bc0977e84ccf0dd464584279a     
left-pad   1.3.0   npm      https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e     
mime-db    1.44.0  npm      https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92      
mime-types 2.1.27  npm      https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f
✨  Done in 0.04s.

端末をオフライン状態にして node_modules/yarn.lock を削除し、オフラインインストールに成功することを確認する。

$ rm -rf node_modules/ yarn.lock
$ yarn --offline
yarn install v1.22.4
info No lockfile found.
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
✨  Done in 0.40s.