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 the bin 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 at node_modules/.bin/command-name. Do a ls node_modules/.bin to see all the existing executable symlinks. You are then able to execute this via npm exec or npx commands. You can even add these to your package.json scripts object that can then be run via npm run-script or just npm 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

Leave a Reply

Your email address will not be published. Required fields are marked *