yarn と pnpm でパッケージにパッチをあてる
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ yarn patch で作られた一時フォルダの中でパッケージを修正
◆ 修正後に yarn patch-commit でパッチファイルが作られる
◆ pnpm でもほぼ同じ
◆ パッケージのコードを少し変更して使いたいってときに便利
◆ 修正後に yarn patch-commit でパッチファイルが作られる
◆ pnpm でもほぼ同じ
◆ パッケージのコードを少し変更して使いたいってときに便利
パッケージの中身を変更したい
npm をパッケージを使っているとき パッケージの中の処理を一部修正したいことがありますJavaScript は動的言語で実行時に書き換えられる部分も多く できる限りそれで済ませたいですが 公開されていない部分や深い部分の処理だったりすると直接コードを書き換えるしかないときもあります
機能追加やバグ修正なら PR を出すとか フォークしてそっちのパッケージを使うとかの方法もあります
ですが 修正内容はパッケージとして正当な内容に限らず 使う側都合でこうしたいからと強制的に処理を置き換えてしまうケースもあります
特に最近だと private プロパティ機能が JavaScript で使えるようになったことで ライブラリで使用されていることも増えています
外側から参照したり上書きしたいのに それが禁止されているので ソースコードを変更してパブリック化する必要がでてきてます
方法
単純なのは node_modules の中のパッケージのソースコードを書き換えてしまうことですですがパッケージマネージャーは node_modules が書き換えられることを想定しないので 別パッケージを追加したりアップデートすると パッケージの初期状態に戻り修正が消えてしまいます
都度 yarn install などをせず Git のブランチ切り替えだけで済ませるため node_modules をすべてバージョン管理に含めているケースもあり そういう場合は消される心配は無いですが それでもパッケージの追加のたびに修正前の状態に戻るので 修正済みの状態に再度戻す手間はあります
なので サブパッケージと言う形でプロジェクトフォルダ内で管理していました
ただ これも不満点はあって パッケージマネージャーによって動きが違ったりします
たしか npm だと フォルダの場所によってまた動きが違って サブパッケージの devDependencies までインストールされることもあります
数行書き換える程度で その場で動作確認したら十分なので 書き換えたパッケージのテストを修正したり実行したりするつもりはないのに テストツールまでインストールされて結構重たくなったりします
node_modules の中からサブパッケージに移行するということは そのパッケージを使う側から開発する側に変わるという意味なので devDependencies もインストールされるのは間違いではないのですが やめてほしい動作だったりします
デフォルトが workspace 的な扱いなのでしょうか
サブパッケージの devDependencies もインストールしたいなら サブパッケージのフォルダで npm install するのでまとめてインストールはしないでほしいです
ちなみに yarn の場合はデフォルトでは workspace が有効じゃないからか そういうことは起きませんでした
この他にも サブパッケージは全部バージョン管理対象にする必要が出てきます
node_modules をすべてバージョン管理するのに比べればマシですが 修正するパッケージが増えれば本来のアプリコード以外の部分でリポジトリが重たくなります
その辺は仕方ないものかなと思ってました
Github のリポジトリを見ていても リポジトリ内に別のパッケージを持っているということは結構見ますし
Node.js だって acorn や openssl をリポジトリ内部に持っています
patch
そんなだったのですが patch 機能を見つけましたyarn では berry (v2 以降) のみの機能です
PnP いらないしと v1 ばかり使っていたので気づいてませんでした
機能はパッケージにパッチをあてることができるというものです
差分をパッチファイルで管理するので 修正箇所が少なければリポジトリに保存するデータも少なくすみます
逆に差分じゃなくて変更済みデータだけを保持したほうがいいくらいに大きく書き換える場合は効率悪いかもしれません
パッケージには minify 済みしかないけど 編集したりコードを見たりをしやすいよう minify 前の形式で保存するとか
yarn patch
yarn で試してみますまずは yarn init で package.json を作ります
berry になるよう -2 を指定します
root@0d9e97115e6f:/dir1# yarn init -2
{
name: 'dir1',
packageManager: 'yarn@3.5.1'
}
v3 が出てしまったので -2 の指定はわかりづらくなってますね
-3 は無効です
PnP は使わないので .yarnrc.yml に nodeLinker を設定します
root@0d9e97115e6f:/dir1# cat .yarnrc.yml
yarnPath: .yarn/releases/yarn-3.5.1.cjs
nodeLinker: node-modules
デフォルトで yarnPath があるのでその下に追加です
適当にパッケージをインストールします
今回は node-fetch にしました
root@0d9e97115e6f:/dir1# yarn add node-fetch
➤ YN0000: ┌ Resolution step
➤ YN0000: └ Completed in 1s 904ms
➤ YN0000: ┌ Fetch step
➤ YN0013: │ fetch-blob@npm:3.2.0 can't be found in the cache and will be fetched from the remote registry
➤ YN0013: │ formdata-polyfill@npm:4.0.10 can't be found in the cache and will be fetched from the remote registry
➤ YN0013: │ node-domexception@npm:1.0.0 can't be found in the cache and will be fetched from the remote registry
➤ YN0013: │ node-fetch@npm:3.3.1 can't be found in the cache and will be fetched from the remote registry
➤ YN0013: │ web-streams-polyfill@npm:3.2.1 can't be found in the cache and will be fetched from the remote registry
➤ YN0000: └ Completed in 1s 450ms
➤ YN0000: ┌ Link step
➤ YN0000: └ Completed
➤ YN0000: Done in 3s 540ms
その後 yarn patch コマンドでパッチを当てるパッケージを指定します
root@0d9e97115e6f:/dir1# yarn patch node-fetch
➤ YN0000: Package node-fetch@npm:3.3.1 got extracted with success!
➤ YN0000: You can now edit the following folder: /tmp/xfs-eb51a6a6/user
➤ YN0000: Once you are done run yarn patch-commit -s /tmp/xfs-eb51a6a6/user and Yarn will store a patchfile based on your changes.
➤ YN0000: Done in 0s 54ms
メッセージにある通り /tmp 以下に一時的なフォルダが用意されます
中には指定のパッケージが展開されているので この中のファイルを編集します
root@0d9e97115e6f:/dir1# nano /tmp/xfs-eb51a6a6/user/src/index.js
とりあえず export される fetch 関数の最初で console.log をするようにしてみました
編集が完了したらメッセージに出てるコマンドを実行します
root@0d9e97115e6f:/dir1# yarn patch-commit -s /tmp/xfs-eb51a6a6/user
これで完了です
package.json があるフォルダの中に .yarn フォルダがあり そこに patches フォルダが増えています
この中にパッチファイルが作成されています
root@0d9e97115e6f:/dir1# cat .yarn/patches/node-fetch-npm-3.3.1-576511fc5a.patch
diff --git a/src/index.js b/src/index.js
index 7c4aee87b68f7b3613ce7dad4a570c17ec3630b9..bd9c265340917a13ec82c18150fe9534f765b077 100644
--- a/src/index.js
+++ b/src/index.js
@@ -46,6 +46,7 @@ const supportedSchemas = new Set(['data:', 'http:', 'https:']);
* @return {Promise<import('./response').default>}
*/
export default async function fetch(url, options_) {
+ console.log("FETCH")
return new Promise((resolve, reject) => {
// Build request object
const request = new Request(url, options_);
また package.json も更新されていて resolutions にパッチファイルの情報が追加されています
root@0d9e97115e6f:/dir1# cat package.json
{
"name": "dir1",
"packageManager": "yarn@3.5.1",
"dependencies": {
"node-fetch": "^3.3.1"
},
"resolutions": {
"node-fetch@^3.3.1": "patch:node-fetch@npm%3A3.3.1#./.yarn/patches/node-fetch-npm-3.3.1-576511fc5a.patch"
}
}
package.json は変わりましたが yarn.lock は変わっていなくて node_modules の中身もそのままです
この後で yarn install が必要です
root@0d9e97115e6f:/dir1# yarn install
➤ YN0000: ┌ Resolution step
➤ YN0000: └ Completed
➤ YN0000: ┌ Fetch step
➤ YN0013: │ node-fetch@patch:node-fetch@npm%3A3.3.1#./.yarn/patches/node-fetch-npm-3.3.1-576511fc5a.patch::version=3.3.
➤ YN0000: └ Completed
➤ YN0000: ┌ Link step
➤ YN0000: └ Completed
➤ YN0000: Done in 0s 169ms
これで反映されたので fetch を呼び出してみます
root@0d9e97115e6f:/dir1# node
Welcome to Node.js v18.13.0.
Type ".help" for more information.
> import("node-fetch").then(module => module.default("https://google.com"))
Promise {
<pending>,
[Symbol(async_id_symbol)]: 357,
[Symbol(trigger_async_id_symbol)]: 341
}
> FETCH
FETCH
FETCH という文字が出力されていますね
2 回出てるのは URL に指定した Google の URL だとリダイレクトが発生して 2 回この関数が呼び出されるからみたいです
パッケージの修正がお手軽にできてかなりいい感じですね
ちなみに 直接インストールした node-fetch に限らず node-fetch の依存関係で入ったパッケージでも使えます
依存関係の変更
これのためだけに yarn を v1 から berry に更新しようかとも思ったくらいですが 問題点もありましたpatch という名の通りで 本来のパッケージをインストールしてパッチを当てるだけです
依存関係を変更できません
さっきの node-fetch パッケージ内の package.json を書き換えて dependencies に dayjs を追加します
さっき追加した console.log で dayjs で作った日付も出力するようにします
これを patch としてコミットしてから yarn install します
dayjs がインストールされていてほしいのですが node_modules にはみつかりません
実行してみても dayjs パッケージが見つからないというエラーになります
node-fetch パッケージではなく メインのパッケージの方の依存関係に dayjs を追加すればパッケージを参照できますが node-fetch パッケージの中で dayjs を使うのに node-fetch の中に書けないのは微妙なところです
メインの方で dayjs の利用がなければ間違って消してしまって予想外のところでエラーになったりしそうです
pnpm patch
この patch 機能は pnpm でも使えますyarn の patch 機能を参考にしたらしく 使い方はほぼ同じです
一部 yarn より使いやすくなってます
pnpm init で初期化します
root@0d9e97115e6f:/dir2# pnpm init
Wrote to /dir2/package.json
{
"name": "dir2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
yarn のときと同じで node-fetch を使うことにします
root@0d9e97115e6f:/dir2# pnpm add node-fetch
Packages: +6
++++++
Packages are hard linked from the content-addressable store to the virtual store.
Content-addressable store is at: /root/.local/share/pnpm/store/v3
Virtual store is at: node_modules/.pnpm
dependencies:
+ node-fetch 3.3.1
Progress: resolved 6, reused 0, downloaded 6, added 6, done
Done in 1.1s
pnpm patch で node-fetch を指定します
root@0d9e97115e6f:/dir2# pnpm patch node-fetch
You can now edit the following folder: /tmp/4919902d53c42b14f6ca167a37ca6c49
Once you're done with your changes, run "pnpm patch-commit /tmp/4919902d53c42b14f6ca167a37ca6c49"
yarn と違って user というフォルダの階層はなく /tmp 直下の一時フォルダにパッケージが展開されます
書き換えてから pnpm patch-commit を実行します
root@0d9e97115e6f:/dir2# nano /tmp/4919902d53c42b14f6ca167a37ca6c49/src/index.js
root@0d9e97115e6f:/dir2# pnpm patch-commit /tmp/4919902d53c42b14f6ca167a37ca6c49/
Packages: -1
-
Progress: resolved 6, reused 7, downloaded 0, added 1, done
ここでも -s が不要になっていたり少し入力が楽になってます
package.json を見てみます
root@0d9e97115e6f:/dir2# cat package.json
{
"name": "dir2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"node-fetch": "^3.3.1"
},
"pnpm": {
"patchedDependencies": {
"node-fetch@3.3.1": "patches/node-fetch@3.3.1.patch"
}
}
}
pnpm の中にパッチ情報が追加されました
yarn の resolutions より pnpm と明示されてる方がいいですね
パッチの指定方法も見やすいです
場所は指定しなければ プロジェクトのトップレベルに patches フォルダが作られます
隠しフォルダの .yarn よりは好みです
root@0d9e97115e6f:/dir2# cat patches/node-fetch\@3.3.1.patch
diff --git a/src/index.js b/src/index.js
index 7c4aee87b68f7b3613ce7dad4a570c17ec3630b9..bd9c265340917a13ec82c18150fe9534f765b077 100644
--- a/src/index.js
+++ b/src/index.js
@@ -46,6 +46,7 @@ const supportedSchemas = new Set(['data:', 'http:', 'https:']);
* @return {Promise<import('./response').default>}
*/
export default async function fetch(url, options_) {
+ console.log("FETCH")
return new Promise((resolve, reject) => {
// Build request object
const request = new Request(url, options_);
pnpm だと patch-commit の時点で反映してくれるので pnpm install の実行は不要です
パッケージを使ってみます
root@0d9e97115e6f:/dir2# node
Welcome to Node.js v18.13.0.
Type ".help" for more information.
> import("node-fetch").then(module => module.default("https://google.com"))
Promise {
<pending>,
[Symbol(async_id_symbol)]: 45,
[Symbol(trigger_async_id_symbol)]: 29
}
> FETCH
FETCH
ちゃんと動いてますね
細かいところは後発の pnpm のほうがいい感じになってると思いますが どっちも同じ感じでパッケージにパッチを当てられます
ただ残念ながら pnpm でも依存関係の変更はサポートされてないようでした