Understanding NPM package.json bin Field
NPM packages can contain one or more executable script files. These scripts are then executed by the package user to make their lives easier, i.e., have better workflows.
Let’s look at a very simple example – the react-scripts package which is widely used for React app development (part of create-react-app). The react-scripts
package has an executable file which is executed via the command react-scripts
(same command name as the package name) to setup a development environment, start a server, enable hot module reloading, etc. Even if you’re not into frontend development you should get the basic idea.
There are a bunch of arguments we can pass like:
$ react-scripts start|build|test|eject
So how does the command magically become available and work? The bin
option or object in package.json
does that. The bin
field maps one or more command name(s) to one or more script file(s) inside the package that can be executed in multiple ways (we’ll cover the different ways shortly).
{
... other package.json options ...
"bin": {
"command-name": "path/to/script/file.js"
}
}
If a package has a map like this, we’ll be able to execute command-name
and pass arguments to it from the command line (just like we passed to react-scripts
above). The arguments will be passed on to the script file that the command name maps to. But where or how exactly can we execute the command ? Will it be available to us in our command line terminals right after package installation ? Lets see:
- When the package is installed globally (
npm i -g pkg-name
), node will install a symlink to the script file (from thebin
option) at/usr/local/bin/command-name
. This will make the command or script accessible in your$PATH
. - When the package is installed locally (in your
node_modules
folder) a symlink to the script will be created atnode_modules/.bin/command-name
. Do als node_modules/.bin
to see all the existing executable symlinks. You are then able to execute this vianpm exec
ornpx
commands. You can even add these to yourpackage.json
scripts
object that can then be run vianpm run-script
or justnpm run
.
Note: If you’re the package author then make sure all your scripts begin with a she-bang followed by the interpreter invocation like #!/usr/bin/env node
.
As a package author again, if you only have a single executable file in your package and would want its command name to be the same as that of the package name (like in case of react-scripts
), then instead of specifying a map, you can just do this:
{
"name": “package-name",
"version": "1.0.0",
"bin": "./path/to/script"
}
After the package installation, the usage will be:
$ package-name ... # global installation
// or
$ npx package-name ...
Since we took the react-scripts
package example earlier, here’s how it’s package.json
bin
option looks like (just for reference):
# bin option in package.json
$ cat node_modules/react-scripts/package.json
{
...
"bin": {
"react-scripts": "./bin/react-scripts.js"
},
...
}
# symlink inside node_modules/.bin
$ ls -l node_modules/.bin/react-scripts
... node_modules/.bin/react-scripts -> ../react-scripts/bin/react-scripts.js
# Actual contents of the package's bin directory
# that contains all the executable script files
$ ls node_modules/react-scripts/bin
react-scripts.js