◆ デフォルトで pnp が有効だった
◆ .yarn\cache フォルダにパッケージごとの zip が配置される
◆ 実行時は yarn node index.js 形式で yarn を通す
◆ ES Modules は対応してないみたい

気づけば yarn に 3 が出ていました
2 でけっこう大きな違いがあったので 1 を使い続けて そのまま 2 を使わず 3 になってしまいました
この機会なので 3 で pnp を使ってみます

ついでに yarn を入れるのに corepack も使おうと思ったのですが corepack は Node.js 15 からデフォルトみたいなことを Readme に書いているのに 16 でも同梱されてないようでした
なので普通に yarn を入れます

yarn3

yarn は 1 と 2 で大きな違いがありますが 3 は 2 系のままで 1 と 2 ほどの違いはないようです
yarn 2, 3 はプロジェクトフォルダの中にインストールするタイプで グローバルなコマンドは yarn 1 を使います
yarn 1 でコマンドを実行したときに yarn 2, 3 がインストールされているプロジェクトフォルダ内だと yarn 2, 3 を使ってくれます

npm -g install yarn

適当にフォルダを作って移動してから yarn 3 をインストールします

yarn set version berry

インストール後にバージョンを表示すると 3 になっています

>yarn --version
3.0.0

フォルダを見ると以下のファイルが追加されていました

  • .yarn\releases\yarn-berry.cjs
  • .yarnrc.yml

yarn-berry.cjs の方が yarn 3 の本体のようで 1 ファイルにまとめた JavaScript ファイルです
.yarnrc.yml は yarn-berry.cjs のパスが記述されてました

yarnPath: ".yarn/releases/yarn-berry.cjs"

pnp

pnp はオプトインと見た気がするのですが yarn add でインストールするとデフォルトで pnp としてインストールされました
node_modules を使うなら .yarnrc.yml の nodeLinker に node_modules を指定しないといけないみたいです
指定しなければデフォルトで pnp として扱われるようです

pnp としてインストールが行われると .yarn フォルダが作られたのと同じフォルダに .pnp.cjs というファイルが作られます
これが require を置き換えて pnp 形式のプロジェクトのパッケージをロードするためのモジュールのようです

node_modules フォルダはつくられず .yarn\cache 以下にパッケージごとの zip ファイルが作られます
fastify をインストールした例です

Mode                 LastWriteTime         Length Name
---- ------------- ------ ----
-a---- 8/8/2021 1:55 PM 20 .gitignore
-a---- 8/8/2021 1:55 PM 7864 @fastify-ajv-compiler-npm-1.1.0-8f156239a8-b8a2522ead.zip
-a---- 8/8/2021 1:55 PM 2059 abstract-logging-npm-2.0.1-b805b8edfa-6967d15e5a.zip
-a---- 8/8/2021 1:55 PM 245015 ajv-npm-6.12.6-4b5105e2b2-874972efe5.zip
-a---- 8/8/2021 1:55 PM 5493 archy-npm-1.0.0-7db8bfdc3b-504ae7af65.zip
-a---- 8/8/2021 1:55 PM 4031 atomic-sleep-npm-1.0.0-17d8a762a3-b95275afb2.zip
-a---- 8/8/2021 1:55 PM 37812 avvio-npm-7.2.2-59206ab649-ece793dd14.zip
-a---- 8/8/2021 1:55 PM 7627 cookie-npm-0.4.1-cc5e2ebb42-bd7c47f5d9.zip
-a---- 8/8/2021 1:55 PM 15326 debug-npm-4.3.2-f0148b6afe-820ea160e2.zip
-a---- 8/8/2021 1:55 PM 11921 deepmerge-npm-4.2.2-112165ced2-a8c43a1ed8.zip
-a---- 8/8/2021 1:55 PM 5312 fast-decode-uri-component-npm-1.0.1-578ba9fecf-427a48fe09.zip
-a---- 8/8/2021 1:55 PM 7393 fast-deep-equal-npm-3.1.3-790edcfcf5-e21a9d8d84.zip
-a---- 8/8/2021 1:55 PM 11434 fast-json-stable-stringify-npm-2.1.0-02e8905fda-b191531e36.zip
-a---- 8/8/2021 1:55 PM 68432 fast-json-stringify-npm-2.7.8-d29e90f9f0-78699673bf.zip
-a---- 8/8/2021 1:55 PM 20891 fast-redact-npm-3.0.1-bd84a09cb8-89de97ea5c.zip
-a---- 8/8/2021 1:55 PM 10877 fast-safe-stringify-npm-2.0.8-33b49729ad-be8a07f342.zip
-a---- 8/8/2021 1:55 PM 5676 fastify-error-npm-0.3.1-9c1ef70a86-fd6a0f6f87.zip
-a---- 8/8/2021 1:55 PM 362172 fastify-npm-3.20.1-2d1c005b89-e6742daf0f.zip
-a---- 8/8/2021 1:55 PM 6139 fastify-warning-npm-0.2.0-f9c53563fc-c19ebccf54.zip
-a---- 8/8/2021 1:55 PM 11752 fastq-npm-1.11.1-ed420613b5-3877a63bee.zip
-a---- 8/8/2021 1:55 PM 60680 find-my-way-npm-4.3.3-71a92171a0-c5f212d2d2.zip
...
(略)

それぞれが zip なので 1 つ 1 つの小さいテキストファイルをすべて展開するのに比べて高速になりそうです
ただ node_modules に比べるとライブラリ内のコードを確認したいときに手間が増えそうです
それにデバッグ実行で node_modules 内も含めたステップ実行とかできるのでしょうか……
試してませんが この辺を考えると開発時は node_modules の方がいいのかなという気もします

実行

普通に実行すると require がデフォルトのままなので node_modules を探してエラーになります
pnp を有効にして実行するには yarn コマンドを通します

yarn node index.js

これは結構いろいろなところで面倒になりそうな気がします
直接 node コマンドが呼び出される前提なところが多いでしょうし

yarn コマンドが実行されるプロジェクトで振る舞いを変えるのでプロジェクト外からの実行ができません
C:\ や / にいるときに

yarn node path/to/project/index.js

は yarn がプロジェクト内のバージョンにならず pnp も有効になりません
カレントディレクトリがルートになるツールを通して実行していると カレントディレクトリを移動させる必要もあります

ES Modules

require を置き換えるので cjs なら問題なさそうですが ES Modules でのインポートで問題が出そうです
試しに単純に ES Modules に書き換えてみたところ やっぱりエラーでした
issue を探すと議論はあるものの まだ実装されてなさそうな感じです
webpack とかで cjs 化すれば動くらしいですが なんかそれは違う気がします
webpack するとプロダクション環境はビルドファイルのみで node_modules も pnp も不要ですし

ゼロインストール

cache フォルダ内の zip をリポジトリに含めることでゼロインストールにもできるようです
プロダクション環境で yarn install したくない都合で node_modules フォルダごと git に含めたこともありますが ファイル数がすごく多くて不便も多いのでこれはありかもしれません

グローバルキャッシュ

この仕組みを見てると プロジェクトごとじゃなくグローバルにパッケージの zip を保持して すべてのプロジェクトがそれを見に行けば無駄なコピーやストレージ使用量が減りそうに思います
複数のプロジェクトで毎回同じパッケージをインストールする場合にはかなり効率的になるはずです
そういうことができないのか調べてみるとグローバルキャッシュという仕組みがありました

.yarnrc.yml の enableGlobalCache を true にすれば良いみたいです
プロジェクトごとにキャッシュを持たないので ゼロインストールにはできませんが ローカルで自分しか使わないならこっちのほうが良いかもしれません

固有ファイル比較

yarn1 と yarn3 でプロジェクトフォルダに作られるファイル・フォルダの比較です

yarn 1
node_modules\
yarn.lock

yarn 3
.pnp.cjs
.yarn\
.yarnrc.yml
yarn.lock

yarn 1 はシンプルでしたが yarn 3 では yarn の設定ファイルや pnp のスクリプトも入るので プロジェクトのルートフォルダが少しごちゃごちゃしてくる欠点があります
yarn.lock のフォーマットも変わってるようでした
最初の部分を比べるとこういう感じです

yarn 1
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


"@fastify/ajv-compiler@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@fastify/ajv-compiler/-/ajv-compiler-1.1.0.tgz#5ce80b1fc8bebffc8c5ba428d5e392d0f9ed10a1"
integrity sha512-gvCOUNpXsWrIQ3A4aXCLIdblL0tDq42BG/2Xw7oxbil9h11uow10ztS2GuFazNBfjbrsZ5nl+nPl5jDSjj5TSg==
dependencies:
ajv "^6.12.6"

yarn 3
# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!

__metadata:
version: 4
cacheKey: 8

"@fastify/ajv-compiler@npm:^1.0.0":
version: 1.1.0
resolution: "@fastify/ajv-compiler@npm:1.1.0"
dependencies:
ajv: ^6.12.6
checksum: b8a2522ead00a01ab7ff2921f00aa8e4aeb943949191ce2a617c88e4679db1358a70e4099791828a397a50e5d6f6bd75184ad0ac75a12dffeb9df4c089986a32
languageName: node
linkType: hard

最初に __metadata というブロックができていたり パッケージごとの情報も増えています

まだいいかな

思ってた以上にデメリットもありましたし もっと一般的に使われるようになって ESModules 対応もされたころにまた考えるでいいかなと思いました
pnp を無効にして yarn 3 を使うこともできるようなので yarn 3 だけ使うことはできますが 1 で困ってないので pnp 使わないなら yarn も 1 のままで良い気がしてます