CLOVER🍀

That was when it all began.

npxコマンドを使って、未インストールのコマンドを使った時の挙動を確認する

これは、なにをしたくて書いたもの?

今まで、npxコマンドはローカルインストールされたnode_modules内の実行ファイルにパスを通して実行してくれるものだと思って
いたのですが、どうやら異なるようなのでちょっと確認しておきたいなと思いまして。

npxコマンド

そもそも、npxコマンドのドキュメントをちゃんと読んだことがなかったので、見てみます。

npx | npm Docs

ローカルにインストールされたnpmパッケージ、またはリモートからフェッチしたnpmパッケージから任意のコマンドをnpm runのように
実行できるコマンドというのが概要になっています。

This command allows you to run an arbitrary command from an npm package (either one installed locally, or fetched remotely), in a similar context as running it via npm run.

要求されたパッケージがローカルプロジェクトの依存関係にない場合は、npmキャッシュ内のフォルダにインストールされ、環境変数PATH
追加するようです。

If any requested packages are not present in the local project dependencies, then they are installed to a folder in the npm cache, which is added to the PATH environment variable in the executed process.

実行したいコマンドとパッケージ名が一致しない場合は、--packageまたは-pで対象のnpmパッケージを指定する必要があります。

npm execというコマンドとも関連があるようです。

では、ちょっと試してみましょう。

環境

今回の環境は、こちら。

$ node --version
v16.16.0


$ npm --version
8.11.0


$ npx --version
8.11.0

ヘルプを見てみる

npxコマンドのヘルプを見てみます。

$ npx --help
Run a command from a local or remote npm package

Usage:
npm exec -- <pkg>[@<version>] [args...]
npm exec --package=<pkg>[@<version>] -- <cmd> [args...]
npm exec -c '<cmd> [args...]'
npm exec --package=foo -c '<cmd> [args...]'

Options:
[--package <pkg>[@<version>] [--package <pkg>[@<version>] ...]]
[-c|--call <call>]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces] [--include-workspace-root]

alias: x

Run "npm help exec" for more info

npm execのヘルプに、より多くの情報があるようですね。

Run "npm help exec" for more info

どうやら、関連のあるコマンドのようです。

指示どおりに以下のコマンドを実行すると、もっと多くの情報が得られます。

$ npm help exec

npxのドキュメントに記載されているものの、npx --helpでは表示されないオプションもありましたし、

npx | npm Docs

より深く知ろうとすると、npm execのドキュメントを見るのが良いのでしょう。

npm-exec | npm Docs

パッケージ名とコマンド名が一致する場合

たとえば、create-react-appを使ってみます。

Getting Started | Create React App

npx create-react-appコマンドを実行。

$ npx create-react-app my-react-app

すると、create-react-appパッケージをインストールするかどうかを聞かれます。

Need to install the following packages:
  create-react-app
Ok to proceed? (y) 

yを入力すると、create-react-appコマンドが実行されます。

作成されたプロジェクト内に移動して

$ cd my-react-app

package.jsonを見てみます。

  "dependencies": {
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^13.3.0",
    "@testing-library/user-event": "^13.5.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },

create-react-appパッケージは依存関係にはいなかったりします。

どこにあるかというと、$HOME/.npm/_npxディレクトリ内にあるようです。

$ ll $HOME/.npm/_npx/c67e74de0542c87c
合計 64
drwxrwxr-x  3 xxxxx xxxxx  4096  8月  3 01:17 ./
drwxrwxr-x  3 xxxxx xxxxx  4096  8月  3 01:16 ../
drwxrwxr-x 65 xxxxx xxxxx  4096  8月  3 01:17 node_modules/
-rw-rw-r--  1 xxxxx xxxxx 45128  8月  3 01:17 package-lock.json
-rw-rw-r--  1 xxxxx xxxxx    61  8月  3 01:17 package.json

package.jsonを見ると、こんな感じになっていました。

$HOME/.npm/_npx/c67e74de0542c87c/package.json

{
  "dependencies": {
    "create-react-app": "^5.0.1"
  }
}

なるほど、と。

ちなみに、パッケージのインストールを対話形式で聞かれないようにするのは、以下のように--yesまたは-yオプションを使います。

$ npx --yes create-react-app my-react-app


### または
$ npx -y create-react-app my-react-app

パッケージ名とコマンド名が一致しない場合

もうひとつ、NestJSで試してみましょう。

First steps | NestJS - A progressive Node.js framework

npx nestで実行してみようとするものの、エラーになりました。

$ npx nest new my-nestjs-app
npm ERR! could not determine executable to run

npm ERR! A complete log of this run can be found in:
npm ERR!     $HOME/.npm/_logs/2022-08-02T16_27_07_177Z-debug-0.log

パッケージ名と実行コマンド名が異なるので、解決できないからですね。

この場合、--packageまたは-pオプションを使用します。

$ npx --package @nestjs/cli nest new my-nestjs-app

### または
$ npx -p @nestjs/cli nest new my-nestjs-app

パッケージのインストールにyと答えれば、npxがパッケージをインストールして進んでいきます。

Need to install the following packages:
  @nestjs/cli
Ok to proceed? (y) 

--yesオプションを使用してもOKです。

$ npx --package @nestjs/cli --yes nest new my-nestjs-app

### または
$ npx -p @nestjs/cli -y nest new my-nestjs-app

完了したら、プロジェクト内に移動して作業をしていきます。

$ cd my-nestjs-app

なお、今回のnpxで自動的にインストールしたNestJSのCLIは、先ほどのcreate-react-appとはまた別のディレクトリにインストールされて
いました。

$ ll $HOME/.npm/_npx/ccf722f030a36e55
合計 204
drwxrwxr-x   3 xxxxx xxxxx   4096  8月  3 01:28 ./
drwxrwxr-x   4 xxxxx xxxxx   4096  8月  3 01:28 ../
drwxrwxr-x 199 xxxxx xxxxx  12288  8月  3 01:28 node_modules/
-rw-rw-r--   1 xxxxx xxxxx 182699  8月  3 01:28 package-lock.json
-rw-rw-r--   1 xxxxx xxxxx     56  8月  3 01:28 package.json

package.jsonの内容は、こちら。

$HOME/.npm/_npx/ccf722f030a36e55/package.json

{
  "dependencies": {
    "@nestjs/cli": "^9.0.0"
  }
}

だいたい、動作はわかりましたね。

ところで

ローカルまたはグローバルにインストールされていないパッケージは、npxコマンドでnpmキャッシュ内にダウンロードしてから実行する
ことができることを確認しました。

ところで、npmキャッシュにインストールしたパッケージは、結局npx経由だとグローバルインストールに近い状態にも見えるのですが、
どうなんでしょうね?

$ npx create-react-app --version
5.0.1


$ npx -p @nestjs/cli nest --version
9.0.0

定期的に削除したり、アップデートしたりした方がいいんでしょうか?

この話は、npm execコマンドのドキュメントで、キャッシュに関する注意事項として記載があります。

指定方法は、こんな感じでしょうか。

$ npx --package @nestjs/cli --yes --prefer-online nest new my-nestjs-app

個人的には、時々キャッシュを削除する、でいいかなと思わなくもないですが…。