[Node.js] Why installing packages locally is much better than globally in npm

You can install npm packages globally with -g option and lots of articles about Node.js guide to install globally to beginners.

But, I guess most packages should be installed locally even if you want to use it in CLI. In this article, I’ll write the reason and how to run them.

Install globally, for CLI.

Why they said you to install packages globally? The biggest reason is to allow you to run as a command with forming a path. For exapmle:

> npm install -g @babel/core @babel/cli

With installing Babel globally, the path to Babel’s bin file was formed automatically.

> babel --version
7.0.0-beta.38 (@babel/core 7.0.0-beta.38)

Then you can execute Babel in any directories by a command.

Disadvantages of global packages

The global packages are very useful to run packages by a command, but there are some disadvantages to know.

Global dependencies won’t be listed in package.json

When some packages are installed globally, they won’t be listed in the project’s package.json. If you are developing with another people or when transferring the environment, you have to tell them there are some dependencies globally.

There is high possibility of trouble such as “It didn’t work? Oh I forgot some global packages are necessarry…”.

Dependencies with different versions of packages

With same package, but different versions are sometimes required while developing. Then it will be hard to handle via global installation.

When the version changes slightly, it’s very typical to stop suddenly even it works fine until then. To match package versions strictly is the most basic of basics.

Though it is possible to specify the version compatible for the project and re-install the package globally, so troublesome to switch between multiple projects.

It’s not able to run the command with local installation

By the way, how is it to same operation with installing locally?

> npm uninstall -g @babel/core @babel/cli

(If these Babel packages are still remaining globally) Uninstall Babels globally at first.

> npm install --save-dev @babel/core @babel/cli

To install locally, I’ll try specifying --save-dev option instead of -g, and running this command.

> babel --version
'babel' is not recognized as an internal or external command, operable program or batch file.

Oh, my system got angry. Let’s retry running with path of bin file, because executable file has been retrieved as same as global installation.

> node_modules\.bin\babel --version
7.0.0-beta.38 (@babel/core 7.0.0-beta.38)

Although it was necessary to give a path to run command, I could run the command via local installation. But, it’s too burdensome to run the package with the pass each time.

When installing packages with--save-dev option in npm, package’s name and version will be listed on devDependencies field of package.json.
In the case of only npm install, they will be listed on dependencies field.
There are not different between them so much if you just use packages. If you publish some packages in npm and someone uses it, you should know they how to work while installation.
Basically, it is just enough to use “`–save-dev“` option while developing to list packages into “`devDependencies“`.

npm scripts make it possible as global packages

npm has the feature “npm scripts” to call some commands defined in package.json previously.

In npm scripts, you can write a command same as global packages even the package is installed locally.

npm scripts can be written in scripts field of package.json.

{
  ...
  "scripts": [
    "babel": "babel --version"
  ],
  ...
}

If you create package.json with npm init command, you can find this field written initially. The left hand operand of : is the script name, and the right hand is the command to run.

In this case, I defined a npm script with the name babel and the command babel --version. FYI, it’s not necessary to name the script with the command name strictly.

To run npm scripts, the command npm run script-name
can be used. In some commands like test
or start, you can run a script with the command by omitting run like npm test. Otherwise, you need to type run.

Then, I’ll try to run the script as below:

> npm run babel

> test@1.0.0 babel C:\\test
> babel --version

7.0.0-beta.38 (@babel/core 7.0.0-beta.38)

You can see the local package is executed without a path to the binary file. With npm scripts, it’s practical because you don’t have to write any paths.

strengths and weaknesses of npm scripts

Once you define some commands as npm scripts in package.json, you can run a same command repeatedly with short command in the terminal. Especially, it’s so effective for a long command with many options. I guess this is an advantage of using npm scripts.

On the other hand, you have to open and edit package.json to make a little differences of the script. Open, write, save, and execute, stacking these series of tasks will be a heavy burden.

In addition, you should be careful if you have to use some quotations in your commands. In JSON syntax, using single quotations to define keys or string literals is not valid. Then it’s necessary to use double quotations.

{
  ...
  "scripts": [
    "mycommand": 'some-bin "test test"'
  ],
  ...
}

This above is an invalid JSON.

{
  ...
  "scripts": [
    "mycommand": "some-bin 'test test'"
  ],
  ...
}

It’s okay to use double quotes for outside and single quotes for inside, but this script will fail in Windows cmd because you have to use double quotes for your command parameters in Windows environment.

{
  ...
  "scripts": [
    "mycommand": "some-bin "test test""
  ],
  ...
}

In Windows, you have to use double quotes both outside and inside, and put a backslash before the inside quotes to escape.

So, some npm scripts may be unavailable even they are same command because it depends on OS environments to run commands.

New command npx can execute local packages

Since npm 5.2.0, native npx command has been implemented. my npm is 5.6.0, npx is already available in my machine.

The advantage of npx is that it’s able to run commands as same as global packages even these packages are local packages.

> npx babel --version
C:testnode_modules@babelclibinbabel.js
7.0.0-beta.38 (@babel/core 7.0.0-beta.38)

Though it’s necessary to prepend npx, you can execute commands without npm scripts and paths.

Especially, if you defined some one-phrase scripts like "gulp": "gulp" in your package.json, you can remove them and reduce the amount of typing by switching to npx.

npx package is available for under npm 5.2.0

Even if you are using npm under 5.2.0, you can install and use npx as npm published package.

> npm install -g npx

As expected, it is necessary to use a global installation for this package.

Conclusion

Even if you installed packages locally, there are some smart ways to use them with commands. You don’t have to depend on global installations already.

I recommend you to check your global packages. If so many, I guess it’s time to switch to local installations. I wish to see the friends with the same idea!