## 経緯
今年も[[📚The State of JS 2022]]が発表された。
<div class="link-card">
<div class="link-card-header">
<img src="https://devographics.github.io/surveys/state_of_js/js2022/images/js2022-favicon.svg" class="link-card-site-icon"/>
<span class="link-card-site-name">2022.stateofjs.com</span>
</div>
<div class="link-card-body">
<div class="link-card-content">
<p class="link-card-title">State of JavaScript 2022: ライブラリ</p>
<p class="link-card-description">The 2022 edition of the annual survey about the latest trends in the JavaScript ecosystem.</p>
</div>
<img src="https://devographics.github.io/surveys/state_of_js/js2022/images/js2022-og.png" class="link-card-image" />
</div>
<a href="https://2022.stateofjs.com/ja-JP/libraries/#tier_list"></a>
</div>
その中で[[pnpm]]のランクが`92%`と非常に高かったので興味を持った。

*[The State of JS 2022: ライブラリ](https://2022.stateofjs.com/ja-JP/libraries/#tier_list) から引用*
## インストール
[[Scoop]]で[[pnpm]]をインストールする。
```console
scoop install pnpm
```
```console
$ pnpm --version
7.25.0
```
## サンプルプロジェクトを作ってみる
```console
mkdir pnpm-test
cd pnpm-test
pnpm init
pnpm i lodash
```
### `node_modules`の中身
前情報通りフラットな構成。
```
$ tree .\node_modules\
./node_modules
└── lodash -> C:\Users\syoum\tmp\pnpm-test\node_modules\.pnpm\
[email protected]\node_modules/lodash
```
`.pnpm`への[[シンボリックリンク]]が貼ってあるので中身を見る。
```
$ tree .\node_modules\.pnpm -L 1
.\node_modules/.pnpm
├── lock.yaml
└──
[email protected]
```
`
[email protected]`はシステムディレクトリへの[[シンボリックリンク]]になると思っていたが、どうやらそうではなさそう。

*[モチベーション \| pnpm](https://pnpm.io/ja/motivation) より*
これを見ると、`
[email protected]`は`.pnpm store`への[[ハードリンク]]となっているが、`fsutil hardlink list`で確認しても、別々のファイルを指しているように見えた。
### 簡単なコードを書いてみる
`package.json`
```json
{
"name": "pnpm-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "node main.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"lodash": "^4.17.21"
},
"type": "module"
}
```
`main.js`
```json
import _ from "lodash";
const result = _([11, 21, 31, 43, 55])
.groupBy((x) => x % 10)
.value();
console.log(result);
```
実行できる。
```console
$ pnpm run dev
>
[email protected] dev C:\Users\syoum\tmp\pnpm-test
> node main.js
{ '1': [ 11, 21, 31 ], '3': [ 43 ], '5': [ 55 ] }
```
## 別の依存関係をインストール
[[TypeScript]]と[[Prettier]]を[[devDependencies]]としてインストールしてみる。
```console
$ pnpm i -D typescript prettier
Packages: +2
++
Downloading registry.npmjs.org/typescript/4.9.4: 11.6 MB/11.6 MB, done
devDependencies:
+ prettier 2.8.3
+ typescript 4.9.4
Progress: resolved 3, reused 1, downloaded 2, added 2, done
Done in 6s
```
`reused 1`というのがポイントだろう。
## ロックファイル
[[pnpm-lock.yaml]]というロックファイルが生成される。これが非常に見やすい。
```yaml
lockfileVersion: 5.4
specifiers:
lodash: ^4.17.21
prettier: ^2.8.3
typescript: ^4.9.4
dependencies:
lodash: 4.17.21
devDependencies:
prettier: 2.8.3
typescript: 4.9.4
packages:
/lodash/4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: false
/prettier/2.8.3:
resolution: {integrity: sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==}
engines: {node: '>=10.13.0'}
hasBin: true
dev: true
/typescript/4.9.4:
resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true
```
## 依存関係の確認
[[dayjs]]に依存している[[🦉Owlelia]]を追加。
```console
pnpm i owlelia
```
`pnpm ls`でプロジェクトの依存関係は確認できる。
```console
$ pnpm ls
Legend: production dependency, optional only, dev only
[email protected] C:\Users\syoum\tmp\pnpm-test
dependencies:
lodash 4.17.21
owlelia 0.42.1
devDependencies:
prettier 2.8.3
typescript 4.9.4
```
より詳細は情報は[[npm explain]]に対して`pnpm explain`かと思いきや違った。[[pnpm why]]コマンドを使う。
```console
$ pnpm why dayjs
Legend: production dependency, optional only, dev only
[email protected] C:\Users\syoum\tmp\pnpm-test
dependencies:
owlelia 0.42.1
└── dayjs 1.11.7
```
[[npm explain]]の場合に比べて、良くも悪くも最低限の情報のみ出力される。[[npm explain]]の出力は以下のように依存バージョンの定義まで表示される。
```console
$ npm explain dayjs
[email protected]
node_modules/.pnpm/
[email protected]/node_modules/dayjs
dayjs@"^1.9.7" from
[email protected]
node_modules/.pnpm/
[email protected]/node_modules/owlelia
[email protected]
node_modules/owlelia
owlelia@"^0.42.1" from the root project
[email protected]
node_modules/.pnpm/
[email protected]/node_modules/dayjs
[email protected]
node_modules/.pnpm/
[email protected]/node_modules/dayjs
dayjs@"^1.9.7" from
[email protected]
node_modules/.pnpm/
[email protected]/node_modules/owlelia
[email protected]
node_modules/owlelia
owlelia@"^0.42.1" from the root project
```
`--long`オプションがあったのでもしや...と思ったが想像と違った。
```console
$ pnpm why dayjs --long
Legend: production dependency, optional only, dev only
[email protected] C:\Users\syoum\tmp\pnpm-test
dependencies:
owlelia 0.42.1
│ TODO
│ git+https://github.com/tadashi-aikawa/owlelia.git
│ https://github.com/tadashi-aikawa/owlelia#readme
└── dayjs 1.11.7
2KB immutable date time library alternative to Moment.js with the same modern API
git+https://github.com/iamkun/dayjs.git
https://day.js.org
```
[[pnpm]]のリポジトリでも[[npm explain]]は機能するので、詳細が知りたくなった場合は[[npm explain]]を使い、そうでなければ[[pnpm why]]を使えばいいと思う。
## 実際に移行してみる
試しに[[🦉Silhouette]]を使って、[[npm]]から[[pnpm]]に移行してみる。まずは`node_modules`と`package-lock.json`の削除から。
```console
rm -rf node_modules package-lock.json
```
[[.npmrc]]で`tag-version-prefix`オプションが使えるかが不安。[[pnpm]]のドキュメントには記載がなさそう...。
<div class="link-card">
<div class="link-card-header">
<img src="https://pnpm.io/ja/img/favicon.png" class="link-card-site-icon"/>
<span class="link-card-site-name">pnpm.io</span>
</div>
<div class="link-card-body">
<div class="link-card-content">
<div>
<p class="link-card-title">.npmrc | pnpm</p>
</div>
<div class="link-card-description">
pnpm は、コマンド行、環境変数、および .npmrc ファイルから設定を取得します。
</div>
</div>
<img src="https://pnpm.io/ja/img/ogimage.png" class="link-card-image" />
</div>
<a href="https://pnpm.io/ja/npmrc"></a>
</div>
依存関係をインストールしなおす。
```console
pnpm i
```
ほとんどのコマンドは動作したが、なぜか `tsc -noEmit -skipLibCheck`で[[Jest]]の型定義をimportできない問題が発生する。
```
$ pnpm run pre:commit
>
[email protected] pre:commit C:\Users\syoum\tmp\silhouette
> tsc -noEmit -skipLibCheck && npm run test
src/hoge.test.ts:1:40 - error TS2307: Cannot find module '@jest/globals' or its corresponding type declarations.
1 import { describe, expect, test } from "@jest/globals";
~~~~~~~~~~~~~~~
Found 1 error in src/hoge.test.ts:1
```
[[package.json]]
```json
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"test": "jest",
"version": "node version-bump.mjs && git add manifest-beta.json manifest.json versions.json",
"prepare": "husky install",
"pre:commit": "tsc -noEmit -skipLibCheck && npm run test"
},
```
[[npm]]では通常通り動く。設定を追加すれば動くのかもしれないが、==[[npm]]と挙動が変わるという事実は無視できない==。
また、[[📝pnpmで生成されたnode_modulesがWindowsのPowerShellからrmコマンドで消せない]]という問題にも直面した。おそらく、[[ハードリンク]]によるもので、回避策も2つほど発見したが、通常のファイルシステムのように`rm -rf ...`と削除できないのは少し不便だ。
あと、インストール速度だが、[[npm]]と[[pnpm]]で大差ないように思えた。...というかむしろ[[npm]]の方が高速だった印象さえある。出力の見やすさは[[pnpm]]の方が上だが、先ほどの互換性問題などを考慮すると、[[pnpm]]に切り替える価値があるかは少し疑問だ。
## まとめ
今後も[[npm]]を使っていこうと思う。