## 経緯 今年も[[📚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%`と非常に高かったので興味を持った。 ![frame](https://assets.devographics.com/captures/js2022/ja-JP/tier_list.png) *[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]`はシステムディレクトリへの[[シンボリックリンク]]になると思っていたが、どうやらそうではなさそう。 ![frame](https://pnpm.io/ja/assets/images/node-modules-structure-8ab301ddaed3b7530858b233f5b3be57.jpg) *[モチベーション \| 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]]を使っていこうと思う。