For convenience, many npm based development tools instruct users to install globally. It makes sense if the tool is used to initiate/create a project, but many such modules are also used with an existing projects. These should installed as local (project) dependencies, which have several advantages:
A small barrier to this practice is that local dependencies are not directly executable as they binaries live within the project folder. One fix is to add the projects node_modules/.bin
folder to PATH, but don’t do this. For one, it would need to be done by every developer needing to use the command.
Another option is to add a script to package.json to act as a runner. For example, to make exp
available as a command in your project, install as a project dependency (using --dev
)and add script entry to act as a runner for your module:
# in package.json:"scripts": {"exp": "exp"...}
The exp
module will now be installed along with other modules during the installation step, and, thanks to argument forwarding developers can run any subcommand using npm:
$ npm run exp -- build:ios$ npm run exp -- build:status$ npm run exp -- publish
If using yarn, there is no need to add the script entry. Yarn automatically searches for binaries in node_modules/.bin
and uses them if present. It also does not need --
for argument forwarding:
$ yarn exp build:ios$ yarn exp build:status$ yarn exp publish
It may still be useful to add script entries as shortcuts to common commands:
# in package.json:"scripts": {"publish: "exp publish"...}
# usage$ npm run publish$ yarn publish
As pointed out by Kevin Kipp, another option is now available, npx, which is installed alongside npm as of npm 5.2.0. Invoking commands with $ npx
instead of $ npm
will automatically check for and, if present, run the local dependency:
$ npm install --save-dev exp$ npx exp publish
Just like yarn, local dependencies are preferred over a global versions of the same modules. Npx, however, goes a step further by automatically installing packages invoked which are not installed either locally or globally. In my opinion this feature will inevitably cause some confusion, surprises, and errors and as such is regrettable.
I’m glad to see this practice of installing everything as a local dependency is finally being embraced and supported by the community. Given the advantages I look forward to this being standard practice. It will take time for it to catch on so if you are a project maintainer with a project which instructs users to install globally, please consider changing the instructions to use a local dependency invoked via npx or yarn.