There are two ways in which npm packages can be installed:
Locally, into a node_modules
directory that npm searches for (or creates) in the current directory and its ancestors:
npm install some-package
Globally, into a global node_modules
directory:
npm install --global some-package
(Instead of the long version --global
of this flag, we can also use the shorter -g
.)
The latter requires root access on macOS and some other Unix platforms – which is a considerable downside. That’s why this blog post explores alternatives to global installs.
In the remainder of this blog post, we need to change the command line PATH for some approaches. This PATH is a command line variable that lists all paths where the command line looks for executables when we enter a command. If we want to install executables via npm, it’s important that the PATH is set up correctly.
There are many good tutorials online, just do a web search for:
On Windows, we can display the current PATH like this:
$env:PATH
On Unix, we can display it like this:
echo $PATH
The npm documentation recommends to change the npm prefix.
We can display the current prefix as follows (I’m showing the results for my Mac):
% npm config get prefix
/usr/local
Under that prefix, there are two important subdirectories.
First, a node_modules
directory:
% npm root --global
/usr/local/lib/node_modules
Second, a bin
directory which contains executable files:
% npm bin --global
/usr/local/bin
This directory is part of the macOS PATH by default. npm adds links from it into the global node_modules
– e.g.:
/usr/local/bin/tsc -> ../lib/node_modules/typescript/bin/tsc
How do we change npm’s prefix?
We create a directory and set npm’s prefix to that directory:
mkdir ~/.npm-global
npm config set prefix '~/.npm-global'
A tilde (~
) on its own refers to the home directory on Unix and Windows. Instead of that symbol, we can also use the shell variable $HOME
(on Unix and Windows), but must take care that shell variables are expanded.
Afterwards, we must add ~/.npm-global
to the PATH.
We can now continue to install packages with the flag --global
, but they won’t be installed globally, they will be installed into our home directory:
npm install --global some-package
npm install --global
works everywhere.package.json
of what’s installed makes reinstalls more work.~/.npm-global
(e.g. if you tell it to update itself).Another alternative to global installs is to install locally into a node_modules
in our home directory and only set up the PATH correctly.
We first turn our home directory into a package:
cd ~
npm init --yes
Then we add "~/node_modules/.bin"
to our PATH.
Once we install our first package, the following new files will exist:
~/node_modules
~/package-lock.json
~/package.json
Instead, of installing a package globally, we do this:
cd ~
npm install some-package
This adds at least the following directory to node_modules
(possibly more, depending on how many dependencies some-package
has):
~/node_modules/some-package
Per executable cmd
that some-package
provides, we also get:
~/node_modules/.bin/cmd -> ../some-package/bin/cmd
That is, the executable is a link into the package.
~/package.json
records all installed packages. That helps with reinstallations.package.json
, package-lock.json
, node_modules
.Acknowledgement: This approach was suggested by Boopathi Rajaa.
This approach is a variation of approach 2. However, instead of turning our home directory into a package, we use a subdirectory of our home directory.
mkdir ~/npm
cd ~/npm
npm init --yes
Then we add ~/npm/node_modules/bin
to our PATH.
Once we install our first package, the following new files will exist:
~/npm/node_modules
~/npm/package-lock.json
~/npm/package.json
cd ~/npm
npm install some-package
~/npm/package.json
records all installed packages. That helps with reinstallations.~/npm
before we can install a package.npx is an option if an executable that we are interested in has the same name as its package. (This is not a strict requirement but we have to type much more otherwise.)
It works as follows. If we install the executable cowsay
globally and run it this way:
cowsay 'Moo'
Then we can also run it this way – without installing anything:
npx cowsay 'Moo'
The first time we use this command, npx downloads cowsay
into a user-local cache and runs it from there. The download may take some time, but is only needed once. Thus, starting with the second time, running cowsay
via npx is virtually as quick as running an installed version.
The npm documentation has more information on npx.
There are tools that let us install multiple Node.js versions and switch between them – for example:
These tools usually set the npm prefix to a directory somewhere inside the current home directory.
Acknowledgement: A discussion on Twitter helped me with writing this blog post. Thanks to everyone who participated!