少し前に突然 tnpm に出会い、npm install の最適化の原理を学びました。ビジネスには役に立ちませんが、最適化のアイデアを学ぶことは非常に有益です。学習の過程で、最近のnpm、yarn、pnpmの長所と短所を簡単にまとめたいと思います。
元
- 最初は URL を使用してコードを共有します。たとえば、JQuery を使用したい場合は、JQuery 公式 Web サイトにアクセスしてそのダウンロード リンクを使用します。欠点は、他の人のオープン ソース コードを使用するたびに、対応するダウンロード リンクを 1 つずつ見つけてファイルにインポートする必要があることです。
- Npm は、管理のためにこれらのオープン ソース コードを収集するツールを使用し、必要に応じてそれらをプロジェクトにインポートするコマンドを実行するために誕生しました。
- node.jsが誕生してからパッケージ管理ツールが急務となり、npmと意気投合してnpmが普及しました~。
npm
npm インストールの原則
- プロジェクト レベルの
.npmrc
ファイル > ユーザー レベルの.npmrc
ファイル > グローバル レベル.npmrc
>npm
組み込み.npmrc
ファイルから構成を取得します。 - ファイルを確認します
package-lock.json
。- その場合は、依存関係が宣言と一致している
package-lock.json
ことを確認してください。package.json
- 一貫して、依存関係を処理するために直接使用します
package.json
。 - 不一致は、バージョンごとに異なる処理が行われます。
- 一貫して、依存関係を処理するために直接使用します
- 存在しない場合は、
- 依存関係ツリー関係の再帰的構築によると
package.json
、構築プロセスは次のとおりです。- 直接の依存関係であるか副依存関係であるかに関係なく、node_modules のルート ディレクトリに置くことをお勧めします。
- 同じモジュールが見つかった場合は、依存関係ツリーに配置されているモジュールのバージョンが新しいモジュールのバージョン範囲を満たしているかどうかを判断し、満たしている場合はスキップし、そうでない場合は、現在のモジュールの node_modules の下にモジュールを配置します。
- 依存関係ツリー内の各パッケージをキャッシュから順番に検索して、キャッシュがあるかどうかを判断します。
- キャッシュが存在しません:
- リモートリポジトリから
npm
ダウンロード - パッケージの整合性を確認します。
- 失敗した場合は、再ダウンロードしてください。
- 検証に合格しました:
- ダウンロードした圧縮パッケージをキャッシュ ディレクトリにコピーします。
- ダウンロードしたパッケージを次のコマンドを使用して
pacote
解凍します。node_modules
- リモートリポジトリから
- キャッシュがあり、キャッシュされたパッケージは
pacote
次のように解凍されます。node_modules
- キャッシュが存在しません:
- キャッシュされたパッケージを次の助けを借り
pacote
て解凍します。node_modules
- ファイルを生成します
package-lock.json
。
- 依存関係ツリー関係の再帰的構築によると
- その場合は、依存関係が宣言と一致している
フラット構造
資源の無駄遣い
前述したように、npm3
将来的にはフラット構造が採用される予定ですが、この構造にもデメリットがあります。
node_modules
└──A
└──node_modules
└──B V1.0
└──C
└──node_modules
└──B V2.0
└──D
└──node_modules
└──B V2.0
└──E
A
前述のインストールのメカニズムから、依存パッケージは内部的に依存しているためB V1.0
、A
依存パッケージはB V1.0
ルート ディレクトリにインストールされることは推測できますが、依存パッケージをインストールする場合C
、ルート ディレクトリは既に存在するためB V2.0
、同じ理由でルートディレクトリB V2.0
にインストールされます。つまり、構造はおおよそ次のとおりです。C
node_modules
D
node_modules
node_modules
├──A
├──B V1.0
└──C
└──node_modules
└──B V2.0
└──D
└──node_modules
└──B V2.0
└──E
ここで、バージョンが繰り返され、リソーススペースが無駄にされていることがわかります。
不確実性非決定論
依存関係があると、同じpackage.json
ファイルがinstall
同じnode_modules
ディレクトリ構造にならない場合があります。
前の例でも、A は [email protected] に依存し、C は [email protected] に依存し、インストール後に B の 1.0 と 2.0 のどちらをアップグレードする必要があるかが異なります。
node_modules
├── [email protected]
├── [email protected]
└── [email protected]
└── node_modules
└── [email protected]
└── [email protected]
└── node_modules
└── [email protected]
node_modules
├── [email protected]
│ └── node_modules
│ └── [email protected]
├── [email protected]
└── [email protected]
└── [email protected]
ユーザーのインストール順序によって異なります。
重複モジュール:同じ名前とsemver (セマンティック バージョン) 互換性を持つモジュールを指します。各モジュールはバージョン許容範囲に対応しており、2 つのモジュールのバージョン許容範囲が重複する場合、互換性のあるバージョンを取得できます。
ファントム依存関係
依存関係が宣言されていないパッケージは、不正にアクセスされる可能性があります。dependencies
モジュールを直接記述することはありませんがB
、直接行うことはできrequire('B');
ますphantom dependency
。ライブラリがリリースされると、B
ユーザーがライブラリをインストールするときにモジュールがインストールされないため、エラーが報告されます。
時間がかかる
平坦化アルゴリズム自体は非常に複雑で、時間がかかります。
糸
Yarn
最適化されたいくつかの問題npm3
: 依存関係のインストールの遅さと不確実性。
インストール速度の向上
にnpm
依存関係をインストールする場合、インストール タスクはシリアル化され、インストールはパッケージの順序で 1 つずつ実行されます。つまり、パッケージが完全にインストールされるのを待ってから次のパッケージに進みます。
パッケージのインストールを高速化するために、yarn
並列操作が採用され、パフォーマンスが大幅に向上しました。また、キャッシュ機構によりyarn
各パッケージはディスク上にキャッシュされ、次回パッケージをインストールする際には、ネットワークを介さずにオフラインでディスクからインストールすることができます。
ロックファイルは不確実性を解決します
依存関係のインストール中に、package.josn に従ってyarn.lock ファイルが生成されます。このファイルには、依存関係、依存するサブ依存関係、依存するバージョン、アドレスを取得してモジュールの整合性を検証するためのハッシュが記録されます。インストール順序が異なる場合でも、同じ依存関係はどの環境やコンテナーでも安定した node_modules ディレクトリ構造を取得できるため、依存関係のインストールの決定性が保証されます。
デメリット
ゴースト依存とクローン依存の問題はいまだ解決されていない。
Npm はその後、不確実性の問題を解決するために package-lock.json も使用しました。キャッシュ戦略も V5 バージョン後に追加されたため、npm のアップグレードでは、yarn の多くの利点が明白ではなくなりました。
CNPM
関連するソース ファイルのダウンロードを高速化します。原則として、cnpm
私たちが行うことは、すべての人のためにそれを変更することですregistry
。
淘宝源は今年6月にアドレスを変更したため、cnpmを使用する場合は最新のアドレスに更新または置き換える必要があります
registry
アドレス:元の Taobao npm ドメイン名はまもなく解析を停止します
ヤーンベリー
溝のノードモジュール
npm
または にかかわらずyarn
、キャッシュの機能があり、依存関係をインストールするとき、実際にはキャッシュ内の関連パッケージをプロジェクト ディレクトリnode_modules
に。IO 操作に関しては、非常に時間がかかります。
このステップはコピーされませんがyarn PnP
、プロジェクト内に静的なマッピング テーブルが維持されますpnp.cjs
。
pnp.cjs
キャッシュ内の依存関係の特定の場所が記録され、すべての依存関係がグローバル キャッシュに保存されます。同時に、ノードが参照に依存する場合に検索するのではなく、グローバル キャッシュ ディレクトリから依存関係を見つけられるようにするために、独自に構築されたパーサーが構築されていますnode_modules
。
これにより、大量の I/O 操作が回避され、プロジェクト ディレクトリが生成されなくなり、node_modules
同じバージョンの依存関係のコピーがグローバルに 1 つだけになり、インストール速度と依存関係の解析速度が大幅に向上します。
ノードエコロジーからの脱却
- PnP を使用するため、 はなく
node_modules
なりますが、Webpack,Babel
などのさまざまなフロントエンド ツールが存在しますnode_modules
。などの多くのツールはpnp-webpack-plugin
解決されていますが、互換性のリスクは避けられません。 - PnP は独自の依存関係パーサーを構築しており、すべての依存関係参照はパーサーによって実行される必要があるため、ノード スクリプトは Yarn コマンドを通じてのみ実行できます。
PNPM
アドバンテージ
前述したように、npm3
フラット構造を採用しているため、次のような問題があります。
- ディスクリソースの使用量が多い。
- ファントム依存関係。依存関係が宣言されていないパッケージに不正にアクセスする可能性があります。
- 平坦化アルゴリズムは複雑で時間がかかります。
そして、pnpm
上記の問題はすべて改善されました。
pnpm i express
node_modules
フォルダーを確認します。
.pnpm
.modules.yaml
express
これはexpress
ソフト リンクです。内部にはディレクトリはなくnode_modules
、実際のファイルの場所は.pnpm-store
フォルダー内にあります。
▾ node_modules
▾ .pnpm
▸ [email protected]
▸ [email protected]
...
▾ [email protected]
▾ node_modules
▸ accepts -> ../[email protected]/node_modules/accepts
▸ array-flatten -> ../[email protected]/node_modules/array-flatten
...
▾ express
▸ lib
History.md
index.js
LICENSE
package.json
Readme.md
この結果はpackage.json
基本的に宣言された依存関係と一致し、リソースの使用量も削減されます。この依存関係メソッドの管理により、依存関係の昇格のセキュリティ問題も解決されます。
デメリット
プロジェクトの分離
すべてがハード リンクを通じて同じソース コード ファイルにリンクされているため .pnpm
、特定のプロジェクトでこのパッケージのファイルを変更すると、そのパッケージはすべてのプロジェクトで変更されるため、変更されたプロジェクトを分離することができなくなります。
依存関係を修正する
フロントエンド開発の過程では、サードパーティのオープンソースライブラリのバグに遭遇することがよくありますが、通常は次のような方法で対処します。
- ソース コードの変更のコピーを自分でフォークし、修復後、ローカルにパッケージ化して直接使用できます。研究結果を他の人と共有したい場合は、npm リポジトリにアップロードするか、ソース リポジトリに PR を送信します。この方法には欠点があります。それは、ノートを公式ライブラリと同期させるのが難しいということです。
- ライブラリ作成者が修正するのを待っています。オープンソース作成者は一般に多忙であり、ユーザーのニーズが最前線にない可能性があるため、この方法は信頼できません。
pacth-package
ローカルパッケージにパッチを適用するnpm
。
ただし、pacth-package
pnpm はサポートされていません。
npmの最適化の方向性
Ant Group の npm エンジニアである Lingyi は、 SEE Conf 2022 Alipay Experience Technology Conference : tnpm (Ant Group Luban Award) でnpm をインストールする第 2 レベルの方法を共有し、npm インストールの問題点と最適化ソリューションを提案しました。
HTTPリクエスト
キャッシュに関係なく、実行中にnpm install
、現在の依存関係のパッケージ情報を順次かつ再帰的に取得し、tar
それに応じてダウンロードします。つまり、HTTP リクエストの数が増加し、依存関係ツリーの生成時間が徐々に増加します。
この点において、HTTP リクエストの量は集約によって削減できます。
プロジェクトをpackage.json
サーバーに送信し、サーバー上で実行して@npmcli/arborist
依存関係ツリーを生成します。arborist
アクセスされたregistry
インターフェースHTTP
を当社のサービスに直接ハイジャックしますregistry
。メモリキャッシュ/分散キャッシュ/DBにより依存関係ツリーの生成処理を高速化します。
@npmcli/arborist
これは、npm の低レベルの検査および管理ツリーのパッケージですnode_modules
。
I/O操作
この記事では、例としてインストールしたパッケージを使用しています。tar パッケージをローカルにプルした後、解凍後に必要な IO 操作には、フォルダーの作成、ファイルの書き込み、bin ファイルへのソフト リンク、bin の読み取りおよび書き込み権限の設定が含まれます。つまり、合計 13 の IO 操作が関係します。
依存パッケージに対応するパッケージnpm install
はその時点でウェアハウスからプルされtar
、tar パッケージはキャッシュにも使用されるため、最適化方法は最初から始まりますtar
。tar
パッケージを解凍する必要がない場合は、上記の IO 操作は必要ありません。
tar
最後にファイルを追加するのは非常に簡単です。したがって、この 2 つをマージできますtar
。
両方のパッケージを作成するフローは次のようになります。
fs.createFile
: 公開ファイルを作成するfs.appendFile
: 最初のパッケージを書き込むfs.appendFile
: 2 番目のパッケージを書き込む
26 IO から 3 IO 操作に減少しました。
ダウンロードとインストールは高速になりましたが、インストールしたものは使用できません。tar
これはファイルではありませんJS
。直接JS
使用しrequire
、shell
ファイル内で操作したり、IDE で編集したりすることはできません。すべての習慣が壊れます。tar
次に、読み取りと書き込みの問題を解決する必要があります。
require
JS を通過するプロセスを学習します。
- この APIを使用して
fs.readFile
、ファイル読み取りリクエストを開始します。 - JS メソッドはデータ結果を
libuv
次のように構築します。uv_req_t
libuv
libc
のメソッドが呼び出されますread
。read
このメソッドは、カーネル内のファイル システムにアクセスしてファイルを読み取るシステム コールを開始します。
PnP はzip
パッケージの形式で保存されます。パッケージの読み込みはハイジャックnode
というrequire
方法で実現しておりzip
、読み込みIDE
はプラグインの開発によってサポートされていますIDE
。ただし、コミュニティにはディレクトリをfsAPI
横断するための実装が多数あり、開発者はこれを使用して一部の依存関係操作を実行することもあります。既存の使用習慣の場合、より大きなダメージを与えます。node_modules
shell
- tar ファイルの読み取りを解決します。FUSE (FUSE の正式名は FileSystem In UserSpace で、SSH などの特定のネットワーク スペースをローカル ファイル システムにマウントするために Linux で使用されるモジュールです) を使用して、ユーザー モード プログラムでファイル システムを実装します。
- tar ファイルの変更を解決する: オーバーレイ ファイル システム (オーバーレイ ファイル システムは Docker コンテナーで広く使用されているファイル システムであり、書き込み時にコピーするという考え方は、ファイルを下位と上位の 2 つの層に分割することです) を使用して、下位ディレクトリは読み取り専用で、上位ディレクトリは読み取りおよび書き込み可能です。オーバーレイで上位ディレクトリと下位ディレクトリを結合して、読み取りおよび書き込み可能なディレクトリを構築できます。ファイルへの変更は、下位ディレクトリには影響せずに、上位ディレクトリに反映されます。プロジェクトの分離が解決されました。
キャッシュ
インストール速度を解決したら、最後のディスク容量の問題を解決しなければなりませんが、npm はインストール後に多くの容量を占有し、ブラック ホールの名にふさわしいものになっています。
NPM はグローバルtar
キャッシュを使用してダウンロード プロセスを高速化し、繰り返しのダウンロードを減らします。しかし、毎回解凍するのに時間がかかりすぎます。
pnpm
ファイルのハードリンクという形式は書き込み量を減らすために使用されますが、ハードリンクは全体が同じファイルを指すことを意味し、例えば、2つのプロジェクトが同じパッケージに依存している場合、一方のプロジェクトが何らかの変更をdebug
加える
オーバーレイのもう 1 つの機能は、基礎となるファイルを変更するときに、基礎となるファイルを上位ディレクトリにコピーする COW (Copy On Write) です。したがって、同じキャッシュを使用してすべてのプロジェクトをグローバルにサポートできます。
他の
Corepack「パッケージマネージャーを管理するマネージャー」
Corepack は、Node.js v16.13 リリースで導入された実験的なツールです。
- Yarn pnpm などのツールをグローバルにインストールする必要はなくなりました。
- 更新が必要になるたびに手動で同期する必要がなく、チーム プロジェクトに特定のパッケージ マネージャー バージョンの使用を強制することができ、構成と一致しない場合はコンソールでエラーが表示されます。
package.json で設定できます。
"packageManager": "[email protected]"
// 声明的包管理器,会自动下载对应的 yarn,然后执行
yarn install
// 用非声明的包管理器,会自动拦截报错
pnpm install
Usage Error: This project is configured to use yarn
テスト段階での問題:
- 現在、pnpm と Yarn のみがサポートされており、cnpm はサポートされていません。
- 互換性に関してはまだいくつかの問題があり、npm はそれをインターセプトできません。つまり、packageManager が糸を使用するように構成されている場合でも、グローバル npm インストールを呼び出すことができます。
@antfu/ni
実行する前に、yarn.lock
// を検出して現在のパッケージ マネージャーを確認し、適切なコマンドを実行します。pnpm-lock.yaml
package-lock.json
使用 `ni` 在项目中安装依赖时:
假设你的项目中有锁文件 `yarn.lock`,那么它最终会执行 `yarn install` 命令。
假设你的项目中有锁文件 `pnpm-lock.yaml`,那么它最终会执行 `pnpm i` 命令。
假设你的项目中有锁文件 `package-lock.json`,那么它最终会执行 `npm i` 命令。
npm i -g @antfu/ni
ni
参考
- 「数秒でnpmをインストールする方法 - 凌儀」スピーチ動画+テキスト版
- npm install の原理分析
- npm、yarn、pnpmパッケージ管理メカニズムの詳細な説明
- グループ内での npm&pnpm 共有を記録します
- Node.js コアパック
- Yuxi さんはアーティファクト ni を推奨していますが、npm/yarn/pnpm を置き換えることはできますか? シンプルで使いやすい!ソースコード公開!
間違いがあればご指摘ください、読んでいただきありがとうございます~