One nice npm feature is that you can install packages with executables locally. This blog post explains how to run locally installed executables.
node_modules
(An aside, on the topic of packages versus modules: npm packages may or may not contain Node.js modules.)
If you require a module, Node.js looks for it by going through all node_modules/
directories in ancestor directories (./node_modules/
, ../node_modules/
, ../../node_modules/
, etc.). The first appropriate module that is found is used.
Whenever you are somewhere in the file system, npm root
tells you where it would install packages if you used npm install
. That directory node_modules/
may or may not exist, already; in the following example, directory /tmp/
is empty.
$ cd /tmp/
$ npm root
/tmp/node_modules
When executables are installed via npm packages, npm links to them:
node_modules/.bin/
directory.bin/
directory (e.g. /usr/local/bin
).The command npm bin
lets you find out where the closest executables are:
$ npm bin
/tmp/node_modules/.bin
If your shell is bash then you can define the following command for running executables from that directory:
function npm-do { (PATH=$(npm bin):$PATH; eval $@;) }
Let’s try out that shell command: We install package figlet-cli
that comes with an executable. npm puts multiple packages into the closest node_modules/
and links to the executable figlet
from node_modules/.bin/
:
$ npm install figlet-cli
$ ls -1 /tmp/node_modules/
figlet
figlet-cli
minimist
optimist
wordwrap
$ ls -1 /tmp/node_modules/.bin
figlet
If we run figlet
as a normal shell command, it fails, because we haven’t installed the package (and thus the executable) globally. However, npm-do
allows us to run figlet
.
$ figlet hi
-bash: figlet: command not found
$ npm-do figlet hi
_ _
| |__ (_)
| '_ \| |
| | | | |
|_| |_|_|
I’m using the repo npm-bin-demo
to demonstrate running executables from inside an npm package. This repo is installed as follows (feel free to read on without doing that):
git clone https://github.com/rauschma/npm-bin-demo.git
cd npm-bin-demo/
npm install
That package has the following package.json
:
{
"bin": {
"hello": "./hello.js"
},
"scripts": {
"fig": "figlet",
"hello": "./hello.js"
},
"dependencies": {
"figlet-cli": "^0.1.0"
}
}
bin
: lists the executables provided by this package. It only matters if this package is installed via npm and then affects the node_modules/
of an ancestor directory.
scripts
: defines commands that you can execute via npm run
if the current package.json
is the one that is closest to your current working directory. Note that we can use figlet
as if it were a globally installed shell command. That’s because npm adds local .bin/
directories to the shell path before it executes scripts.
dependencies
: lists packages that are installed by npm install
, into npm-bin-demo/node_modules/
. As you can see, we have installed figlet-cli
.
Let’s examine our surroundings (remember that we are still inside the directory npm-bin-demo/
):
$ npm root
/tmp/npm-bin-demo/node_modules
$ ls -1 node_modules/
figlet
figlet-cli
minimist
optimist
wordwrap
$ npm bin
/tmp/npm-bin-demo/node_modules/.bin
$ ls -1 node_modules/.bin/
figlet
As expected, there is no shell command figlet
, but we can run figlet
via npm-do
:
$ figlet hi
-bash: figlet: command not found
$ npm-do figlet hi
_ _
| |__ (_)
| '_ \| |
| | | | |
|_| |_|_|
We can also execute figlet
via npm run
:
$ npm run fig hi
> @ fig /Users/rauschma/tmp/npm-bin-demo
> figlet "hi"
_ _
| |__ (_)
| '_ \| |
| | | | |
|_| |_|_|
As explained previously, the entries in bin
have no effect inside a package, which is why we can’t run hello
via npm-do
:
$ npm-do hello
-bash: hello: command not found
We can, however, run the script whose name is hello
:
$ npm run hello
> @ hello /tmp/npm-bin-demo
> ./hello.js
Hello everyone!
For more information on the topic of local npm installs, consult Sect. “npm and local installs” in “Setting up ES6”.