Package Managersについて学ぶ

Package Managersって何してるの
なんとなくわかってるつもりだけど改めて勉強したいのでやる
https://roadmap.sh/frontend に記載されてる記事を読む

Modern JavaScript Explained For Dinosaursを読む
エコシステムの歴史を学べる記事
2017年までの内容らしい

node.js以前
- htmlにscriptタグを書く
- すべてのjsファイルはグローバルで扱われる
- バージョンの追従が大変
JavaScriptパッケージマネージャー(npm)の使用
- 中央リポジトリからライブラリをダウンロードしてアップグレードするプロセスを自動化する仕組み
- npmはnode.js(サーバ)専用に作成されたマネージャー
- package.jsonだけ共有すればよい、node_moduelesを共有しなくていい
- これだけだと、HTMLにパスを指定しないといけないのが大変

モジュールバンドラーの登場(webpackなど)
経緯
- JavaScriptは別のファイルからコードをインポートする方法がなかった
- セキュリティ上の理由からファイルシステムにはアクセスできず、ブラウザのみで動作するため
- CommonJSはサーバでJSエコシステムを定義することを目標としたプロジェクト
- ファイル間でインポート、エクスポートできるようになった
- node.jsはその実装
- サーバーは↑でよかったが、ブラウザでは
require
が動かないから同じことができない
登場
- そのため、
require
ステートメントを見つけてそれを置き換え、単一のバンドルされたJSファイルを作るモジュールバンドラーが必要になった - 単一のJSなのでパフォーマンス的によかった
- ビルドステップで色々なことができるようになって便利だった

トランスパイラの登場(babel)
- トランスパイルとは、ある言語を別の類似言語のコードに変換すること
- ブラウザは新しい機能を追加するのが遅いのでトランスパイル可能な新しい言語が作成された
- CSS
- Sass, Less, Stylus
- JS
- TypeScript
- CoffeScript
- babelは次世代のJSをES5に変換するトランスパイラ
babel
-
@babel/core
:主要部分 -
@babel/preset-env
:どの新しいJS機能をトランスパイルするか定義する -
babel-loader
:webpackで動作できるようにするパッケージ

タスクランナーの使用(npmスクリプト)
- ビルドプロセスを自動化するもの
- タスク
- コードの縮小
- イメージの最適化
- テストの実行

webpackのローダーという概念がよくわからない

An Absolute Beginner's Guide to Using npm
npmのビギナーズガイド

流石に知ってる内容なので、目だけ通した

how-to-npm
- 作業環境構築からモジュールの公開まで学べるらしい
- 面白そう

Meet PNPM: The Faster, More Performant NPM
- pnpmについて簡潔に紹介する記事
- 「Performant NPM」の略らしい

PNPMとは何ですか?
PNPM の主な目的は、すべてのパッケージをグローバル (集中型) ストアに保持し、必要に応じてハード リンクを作成して他のプロジェクトでも使用することです。
NPM よりも PNPM を使用する利点
膨大なディスク容量を節約します。
パッケージのインストールにかかる時間が短縮されます。
mono リポジトリのサポートが組み込まれています。

Getting Started | Yarn
yarnの基本を学ぶ

- v2.0以前のドキュメントらしい
- 基本的なコマンドの使い方が書かれていた

Getting Stared | Yarn (v4~)
新しいのはこっち

Intro
知らない機能がたくさんあるんだなぁ
- 重点分野
- 速度
- 正確性
- セキュリティ
- 開発者体験
- 機能
- ワークスペース
- オフラインキャッシュ
- 並列インストール
- 強化モード
- 対話型コマンド

インストールと使用方法
特に真新しいものはないけど、Corepackが必要らしい

To go further: Yarn PnP
この章読んでも全くわからない...
良さそうな記事を代わりに読む

この記事の人の調査力凄すぎる...笑
時系列でとてもわかりやすかった
ざっくり要約
- 重複削除(deduplication, 略してdedupe)
- 単純に依存関係を全てインストールする問題に対処
- 重複するものはhoistingしてnode_modulesの直下に置かれるようになった
- 木構造が不変のアルゴリズムとlockファイル
- node_modulesをインストール順やパッケージマネージャの順番に依存することなく再現可能になった
- シンボリックリンク方式
- 依存関係をシンボリックリンクで表現することでパフォーマンスを向上した
- もともとnpmも検討してたけどnodeのバグを避けるために採用が見送られたっぽい
- pnpmで採用
- npmにも取り込まれる
- 依存関係をシンボリックリンクで表現することでパフォーマンスを向上した
- yarn PnP
- node_modulesを作らない方式
- Module.loadをオーバーライドする(?)
- npm v7でインストール順に依存しなくなった

Yarn PnP
- ざっくりはわかったけどよくわからないのでドキュメントも眺める
- あんまりよくわからなかったかも...
メリット
-
node_modules
が不要になる -
.pnp.cjs
がNode.js loader fileになる(?)らしい- loaderはモジュールの読み込み方法をカスタマイズするための機能(GPT曰く?)
- ディスク上のすべてのプロジェクトで同じパッケージ成果物を利用
- キャッシュパスを介して参照するので複雑さが軽減(?)
- hostingが完全になる(?)
- セマンティックなエラーメッセージ
- これは嬉しそう

Yarn PnP試してみる
- voltaで管理してるので、
corepack
とyarn
を入れた - initする
yarn init -2
// 2って何?
- ディレクトリにファイルが作成される
package.json
yarn.lock
.yarnrc.yml
-
.pnp.cjs
- これはgit対象外
- 中身を覗くと色々処理の書いてあるただのjsファイル

-
-
というダミーのnpmパッケージを入れてみる -
RAW_RUNTIME_STATE
に差分あり
-
RAW_RUNTIME_STATE
は下記で使われている
function $$SETUP_STATE(hydrateRuntimeState, basePath) {
return hydrateRuntimeState(JSON.parse(RAW_RUNTIME_STATE), {basePath: basePath || __dirname});
}
- 最終的には
defaultApi
というexportされてるインスタンスで使われてる - これがModule.loadをpatchしているという話(?)なのかな
- ここら辺はよくわからんので諦める

Corepackとは
- パッケージマネージャのバージョンを定義できるNode.jsのツール
- yarnとpnpmで推奨してる
- 実験段階で、オプトインなので
corepack enable
が必要 - voltaは現在統合されていない
- あれ、普通にvoltaでcorepackインストールしたけど、何かあるのかな

Workspace機能について
monorepoのときに選択肢として聞くけど、何ができるかわかってないので簡単に確認しておく

npm workspace
Getting started with workspaces
試しにやってみた
.
├── lib
│ └── index.js
├── node_modules
│ ├── abbrev
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── lib
│ │ │ └── index.js
│ │ └── package.json
│ ├── test-a -> ../packages/test-a
│ └── test-b -> ../packages/test-b
├── package-lock.json
├── package.json
└── packages
├── test-a
│ ├── index.js
│ └── package.json
└── test-b
└── package.json
This set of features makes up for a much more streamlined workflow handling linked packages from the local file system. Automating the linking process as part of npm install and avoiding manually having to use npm link in order to add references to packages that should be symlinked into the current node_modules folder.
次の一連の機能により、ローカルファイルシステムからリンクされたパッケージを扱う際のワークフローが大幅に簡素化されます。npm installの一部としてリンクプロセスを自動化し、手動でnpm linkを使用して現在のnode_modulesフォルダにシンボリックリンクを追加する必要がなくなります。(GPT翻訳)
-
node_modules
の中はリンクになってる -
abbrev
はtest-a
でインストールしたものだけど、一番上のnode_modulesに実態がある
// ./lib/index.js
const moduleA = require('a')
console.log(moduleA) // -> a
確かに普通にモジュールとして呼べるようになってる

yarn v1 workspace
npm workspace
と同じっぽかった(どっちが先かはわからないけど)
yarn v4 workspace
いつ使うか
- yarnはworkspace機能をドックフーディングして運用してる
- babelやjestも使ってる
宣言方法
v1でもできるって書いてあったな
{
"workspaces": [
"packages/*"
]
}
相互依存
workspace:
プロトコルでいい感じにできるらしい
{
"dependencies": {
"@my-org/utils": "workspace:^"
}
}

pnpm workspace
-
pnpm-workspace.yaml
ファイルで独立して定義する
workspace protocol
link-workspace-packagesがtrueに設定されている場合、利用可能なパッケージが宣言された範囲と一致していれば、pnpmはワークスペースからパッケージをリンクします。例えば、barの依存関係に"foo": "^1.0.0"が含まれており、foo@1.0.0がワークスペース内に存在する場合、foo@1.0.0がbarにリンクされます。しかし、barの依存関係に"foo": "2.0.0"が含まれており、foo@2.0.0がワークスペース内に存在しない場合は、foo@2.0.0がレジストリからインストールされます。この動作により、多少の不確実性が生じます。
- ワークスペースのライブラリのバージョンと、ルートプロジェクトのライブラリのバージョンが違う時、勝手にレジストリからインストールされるらしい
-
workspace:
を使えば、ワークスペースからの依存に指定できる
Usage examples
モダンなUIフレームワークメタフレームワーク、だいたいpnpmやんけ...

Node.js がモジュール解決を処理する方法の仕様
npm workspaceで言及があって気になった

require()
が呼ばれた時の擬似アルゴリズムが書かれていた
require.resolve()
すれば、どのパッケージが呼び出されてるかわかるらしい