📑

一日一処: npm installとかでピロピロ動いてるあれがなにか突き止める(後編)

2024/02/13に公開

前回までのあらすじ

前回は、コマンドが実行されている過程の部分をなんとなく紐解いてきた。前編の記事には記載していないかったが、最終的な到達地点として、@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が今回の目的の代物ではないだろう。
パッケージを見ておこう。
https://github.com/npm/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