It is a very common practice to install NPM packages globally. It's an easy method to install and run executable programs that helps with your development. However, there are some pitfalls to it, particularly when it comes to distributing your application working in a team.
Reasons to not install NPM packages globally
Requires to install them system-wide
Global install leads to your project dependency installed system-wide into a directory such as
/usr/local/lib/node_modules
. It's good practice to not trust random packages on the
internet with access to system directories. Therefore, it's best to keep the package access and
versioning to specific projects and within ./node_modules
of the project directory.
Extra steps to install required dependencies
For every project, you would run npm install
or yarn
to install all the
required dependencies. It's the convention that is understood by all Javascript and Node.js
developers. Using scripts that require global dependencies install separately requires additional
steps.
$ npm install -g typescript
$ npm install -g nodemon
$ npm install -g mocha
# And so on...
It then required additional documentation of what additional dependencies to install. Possibly
worst, is that you can forget and miss out on writing documentation. Thus, leading to
scenarios such as scripts in the package.json
not working since the additional
dependencies are not installed.
"scripts": {
"dev": "nodemon bin/www",
"test": "mocha",
"transpile": "tsc myfile.ts"
},
Help! Why does it say tsc: command not found
when I try to run the script?
What am I suppose to do?
- Do I need to run
npm install -g tsc
? - Or is it
npm install -g typescript
?
Versioning is not consistent
When installing them globally, the would be presumably install the latest version. Depending on when you or your team members run the command, you can end-up installing different versions of the package causing breaking changes or different behaviors. You may not even be able to keep track of the version that worked if you need to revert.
When to install them globally (Exceptions)
You can install packages globally when you are using a CLI tools that you do not expect to have to
distribute. That means packages fit neither in the dependencies
or
the devDependencies
section in package.json
file.
For example, I use npm-check-updates to keep packages in a project up to date. It is not a dependency for the code to run and it is not a tool that necessarily needs to be kept aligned and consistent for everybody. There are many other and preferred methods of managing package versions. Therefore, it does not need to be installed and kept at a consistent version for everybody.
Steps to using local package installation
Step 1: Install packages locally
Instead of installing packages globall such as the commands above, install the packages locally and set the dependency scope appropriately.
$ npm install typescript --save-dev
$ npm install nodemon --save-dev
$ npm install mocha --save-dev
This method installs the dependencies and save them to the package.json
file as a
devDependencies
as these are not required to be install in production with
npm install --production
, or the now preferred npm install --omit=dev
.
Step 2: Update package.json to use the local package installation
Using npx (simplest)
Just add npx
to trigger the package runner. This command would execute the locally installed
and automatically linked executable.
"scripts": {
"dev": "npx nodemon bin/www",
"test": "npx mocha",
"transpile": "npx tsc myfile.ts"
},
Executable path (alternative)
Alternatively, if you want to be fancy and do it the old fashioned way, you can specify
the executable path directly. This method was from before the handyness npx
came in to newer
NPM versions. The executable path would typically be in the
./node_modules/[package_name]/bin
directory.
"scripts": {
"dev": "./node_modules/nodemon/bin/nodemon.js bin/www",
"test": "./node_modules/mocha/bin/mocha.js",
"transpile": "./node_modules/typescript/bin/tsc myfile.ts"
},
Note: There's hardly any advantage to this method over using npx
Conclusion
The 3 reasons use the locally install NPM package dependencies are:
- Prevent extra steps to install dependencies
- Manage consistent package versions
- Keep them scoped to a project instead of system-wide
Update the scripts in package.json
to use npx
and ensure to have the
packages installed and listed in the dependencies list.