yarn v3 の独自機能を避けつつ yarn v1 から v3 へのアップグレードをする
yarn v3 が出ました。詳しい解説は譲るとして、esbuild integration や パフォーマンス向上が目玉です。
Yarn 3.0 🚀🤖 Performances, ESBuild, Better Patches, ... - DEV Community
流石に v1 はもう古いが、 v2 からの独自路線は受け付けがたい…という立場なのですが(yarn オリジナル作者の sebmck も難色を示しています)、今回は yarn 特有の機能をできるだけ避けて、できるだけ npm や pnpm 等と互換な部分だけで yarn v3 を使います。なので pnp も使いません。eslint や vscode の typescript 等でハマりどころが多すぎます。
ゼロインストールも否定派です。git blob objects のサイズが爆発して仕事にならなくなったことがあります。具体的には git lfs を使わずバイナリをコミットする Unity Project みたいになる予感しかしません。あれは辛かった…。
既にある yarn v1 プロジェクトからの移行
$ rm -rf yarn.lock node_modules **/node_modules
$ yarn set version berry
$ touch yarn.lock
$ yarn set version 3.0.0
# .yarnrc.yml を編集
# yarnPath: .yarn/releases/yarn-3.0.0.cjs
# nodeLinker: node-modules
# .gitignore を編集
# .yarn/
# !.yarn/releases/*
$ yarn install
意味はあとで解説します。yarn.lock が v1 と互換があるかわからないので、一旦消して更地から構成しています。
解説: yarn v3 プロジェクトの新規作成
最初に、新規だとどういう振る舞いをするか知っておく必要があります。これは既にある v1 プロジェクトを移行する際に必要になる知識が身につきます。
$ mkdir yarn-v3-test
$ cd yarn-v3-test
$ yarn init -y # この時点では yarn v1.22
$ yarn set version berry # berry
$ touch yarn.lock
$ yarn set version 3.0.0 # 3.0.0
既にややこしい問題が複数あって、その回避策が混ざったセットアップです。
まず、現在の yarn v1.22.x から直接 v3 へアップグレードする機能がなく、一旦 berry というコードネームの実質 v2 を経由します。もうややこしいですね。
ここからは環境依存なのですが、 v2 以降では、親ディレクトリのいずれかに package.json があると、そこを workspace の親として認識しようとして、親に管理されていない子 package.json では yarn install が失敗します。自分は巨大なリポジトリの一部でこれをやろうとしてハマりました。
package.json # ここに husky と lint-staged の設定があった
app/
package.json
yarn.lock # これが必要
これを解決するために、自分が独立した package.json のロックの管理単位であることを宣言するために空の yarn.lock
を作成しておきます。
yarn.lock
があるディレクトリで、 yarn set version 3.0.0
をすると、.yarn/releases/yarn-3.0.0.cjs
に yarn v3 の実体が生成されます。これがないと yarn
コマンドの実行に失敗するので、 git にコミットしておく必要があります。
また、 .yarnrc.yml
もコミットします。
.yarnrc.yml の設定
まだ yarn install しません。最初に pnp を無効化します。
.yarnrc.yml
yarnPath: .yarn/releases/yarn-3.0.0.cjs
nodeLinker: node-modules
今の yarn コマンドの振る舞いは、.yarnrc.yml
があればその yarnPath
を使います。何もなければ安定版である v1 を使います。
これで yarn install ができるはずです。
$ yarn install
.gitignore
yarn install すると .yarn/cache
に node_modules の実体の zip が保存されます。これをコミットするのがゼロインストールモードで、 yarn install が不要になります。ただ、これをやると前述のように git blob objects が爆発するので避けます。
.yarn/
!.yarn/release/*
たしかにゼロインストールで便利なところもあって、 docker に転送する際に docker 内で yarn install をする必要がない、という点です。このためだけに 一時的に .gitignore を書き換えるスクリプトを用意するのもありかもしれないと考えてますが、まだ検証してません。
Workspace の設定
機能自体は v1 と同一ですが、v3 のほうが安定しています。
packages/
foo/
package.json
bar/
package.json
package.json
yarn.lock
こういう構成の時, package.json に次の設定をすることで、可能な限り親の node_modules
に巻き上げられます。また、foo や bar がそれぞれの name で相互に require/import できるようになります。
"workspaces": ["packages/*"]
巻き上げのポリシーがいくつかあり、 nmHoistingLimits
で設定できます。デフォルトの全部巻き上げる none
, workspace 単位で独立する workspaces
, それぞれが独立する dependencies
です。
# ...
nmHoistingLimits: workspaces
ここで今自分が困ってるのが、 今まで package.json の workspaces.nohoist
で出来た、特定パッケージで hoist を無効化する機能がなくなっています。 react-native などでは詰むかもしれません。また、試した感じ dependencies
は nodeLinker: node-modules
だと機能してない気がしてます。 pnp だと yarn install 自体は動きましたが正しい状態かは未確認です。
GitHub Package Registry を使う設定
yarn v3 は .npmrc
を認識しません。なので認識するための設定を .yarnrc.yml
に書く必要があります。
例えば @mizchi/myutil
という private な npm パッケージがあった場合、それを解決するためには次の設定が必要です。
# ...
npmScopes:
mizchi:
npmAlwaysAuth: true
npmRegistryServer: 'https://npm.pkg.github.com'
npmRegistries:
//npm.pkg.github.com:
npmAlwaysAuth: true
npmAuthToken: <your-token>
共有パッケージの link 宣言
shared/
hoge/
package.json
app-a/
package.json
yarn.lock
app-b/
package.json
yarn.lock
app-a や app-b から shared を引きたいとき、dependencies として link:
を使います。
"dependencies": {
"hoge": "link:../shared/hoge"
}
自分はここでハマったのですが、yarn v3 は link:
を省いたデフォルトだと portal:
というプロコトルが使われています。これは hoisting 単位が app-a や app-b と同一になり、同名のパッケージでバージョンのコンフリクトがあると hoisting がおきない…ではなく、Cannot Link のエラーがおきて yarn install が停止します。つまり、ここで使われているパッケージ内で全部揃える必要が発生して、例えば typescript コンパイラの patch バージョンすら揃える必要が出ます。
link:
だと単に simulink になるので、この問題は発生しません。ただし、自分で対象の (yarn/npm) install を済ませておく必要があります。これは npm
や pnpm
と同一の機能のはず…です。
感想
動きだせば安定したように見えてるんですが、またハマったりしそうなので、しばらく運用したら追記します。
Discussion