一日一処: npm installとかでピロピロ動いてるあれがなにか突き止める(後編)
前回までのあらすじ
前回は、コマンドが実行されている過程の部分をなんとなく紐解いてきた。前編の記事には記載していないかったが、最終的な到達地点として、@npmcli/promise-spawnにぶつかった。渡された情報をもとに、ここで処理を実行しているというところまでに至った。
設定と表示に目を向ける
前編では、installコマンドがどこで実行されているのか、その処理の流れに目を向けていた。実に様々な処理とパッケージを経由しているというところはなんとなくわかったが、表示を行う機構が、そんな末端の処理で行われているとは考えにくい。基本的には、処理の最終到達点での表示処理ではなく、それを使用している大元で行っている場合が多い。
初手から見ていく
まずは、前回も確認したinstall.jsファイルだ。これの処理の中身を前回下っていたが、今回は、このクラスが継承しているクラスに目を向ける。
継承しているのは、ArboristCmdクラスだ。さらに、これはBaseCommandクラスを継承している。そして、このBaseCommandクラスには気になる記述がある。
return this.npm.output(this.usage)
見ての通りだが、明らかに何かを出力している処理だ。このthis.npm
のプロパティを参照したいが、BaseCommandでは、コンストラクタの引数から取得し、これを継承したArboristCmdクラスでも同様にコンストラクタの引数から取得しsuper(BaseCommand)へ渡している。
ただ、残念なことに、install.jsのクラスは、コンストラクタの設定はされていないため、install.jsのクラスを呼び出しているところでインスタンス化し、このthis.npm
になる値を渡していると言える。
フラグ回収(コマンドの実行)
前編を思い出してほしいが、前回の冒頭ではnpm.jsに到達していた。そして、その中で、setCmdメソッドを確認し、必要なファイル(今回のinstall.js)を呼び出し、インスタンス化していたのを見ていた。前回確認していたことが、ここで生きてくる。
const Command = Npm.cmd(cmd)
const command = new Command(this)
Command変数には、読み込んだクラス(Install)を、command変数には、それをインスタンス化したオブジェクトをお渡している。そして、そこには、なんと、this
が入っている。紛れもなくこれが、前述のthis.npm.output
の源泉だ。ということは、このNpmクラスの中に、outputメソッドがあるということだ。
// output to stdout in a progress bar compatible way
output (...msg) {
log.clearProgress()
// eslint-disable-next-line no-console
console.log(...msg.map(Display.clean))
log.showProgress()
}
別ファイルから拡張されてなくて本当に良かったが、やはり、プログラムは語っている。「output to stdout in a progress bar compatible way」とのことだ。明確にprogress barと記述している。この下りが、インストール時のピコピコ表示に一役買っていることは間違いないだろう。このlogは、更に別のファイル(log-shim.js)を参照している。名称通り、logの仕組みを拡張しているものだろうか。
核心に触れる
このlog-shim.jsファイルの中では、いくつかの設定が行われている。Npm.outputのメソッドでも目撃した内容が含まれているようだ。そして、それぞれの設定(アクセサーを経由して使用されているもの)のほとんどは、NPMLOGを参照している。
const NPMLOG = require('npmlog')
さらにこれは、npmlogというパッケージを読み込んでいる様だ。
libフォルダにはファイルが一つしか無いため、おそらく、これが、エントリーファイルだろう。すると中には、このような記述があった。
log.gauge = new Gauge(stream, {
enabled: false, // no progress bars unless asked
theme: { hasColor: log.useColor() },
template: [
{ type: 'progressbar', length: 20 },
{ type: 'activityIndicator', kerning: 1, length: 1 },
{ type: 'section', default: '' },
':',
{ type: 'logline', kerning: 1, default: '' },
],
})
「no progress bars unless asked」とのことだ。明らかにここでprogress barの有効化を行っているように見える。ということは、用いているGaugeが今回の目的の代物ではないだろう。
パッケージを見ておこう。
ついに、探し当てることに成功した。README.mdでは、複数のプログレスバーの表示があり、npm isntallのときにも目にするスタイルがあることがわかる。
使われている場所
少し原点に戻りつつ、ようやく、目的の正体を見つけ出すことに成功したが、問題はまだある。実際使われている部分を目撃していないということだ。実現している処理がわかったので、ここからは簡単だ。
まずは、Npm.outputの中身を確認すると、以下のような記述がある。
log.showProgress()
log-shim.jsファイルを見ても、このメソッドがGaugeを用いて表示処理を行っているのが明白だった。つまりこのNpm.outputがinstallを行う際にどこで実行されているのか。ということだ。
npm.jsでのNpmクラスが、引数で受け取ったコマンドのファイルを読み込み、インスタンス化する過程まで確認した。そして、Installクラスのexecメソッドまで見たが、前編では触れていない部分がある。
await reifyFinish(this.npm, arb)
この関数は、さらにreifyOutputを内部で呼び出している。これらの名称は、抽象化や、樹医などで、パッケージの依存関係を検査する仕組みとなる。
そして、ついに、reifyOutput関数の定義では、Npm.outputの使用が確認できる。ソースコードの流れを追っただけなので、正確にここでの表示が影響しているか把握できていないが、少なくとも関係性としては、npm installの過程の中で使用されていることがわかった。(別途、番外編として確実に確認する記事を投稿する)
おわりに
実は過去に、別のパッケージを知っていたので、もしかしたらこれが使用されているのか?と考えていたが、これを期に、実際にどこでどの様に使用されているのかを確認しようと至った。理解はしていたものの、やはり大規模なパッケージは様々なほかのパッケージを用いて実現しており、私達もその恩恵を受けていることをひしひしと実感できた。これからは、感謝しながら、毎日一万回の正拳突きとともにnpm installを行おうと思った。
Discussion