How I can get away with never installing npm packages globally
Tags: npm, scripts, global, dependencies
Published: 2024-05-29 06:54:57

My belief is that when you clone a git repository all code, settings and tools should be contained in that cloned repo; the directory. It should not contaminate the system, not by tools, not by environment variables, maybe a bit for the platform. 

# Scope
This article will not discuss the use of `.env` file for maintaining environment variables or the `.nvmrc` to enable automatically switching to the proper node version. Also it will not include info about `.npmrc` file to e.g. lock the node version.

# Trigger to write this article

So many README.md files or manuals say things like:

// https://angular.io/guide/setup-local
```sh
npm install -g @angular/cli
```

More candidates like these are, but not limited to:
- `eslint`
- `prettier`
- `create-react-app`
- `webpack`
- `@vue/cli`
- `nx`

Why would you want to do this? You have now local packages to maintain, and you can't use it across different projects that may have different versions of that software package. 

## Enter `npx`

npx is a package runner tool that comes with `npm 5.2.0` and higher. The current version when writing this article is `npm 10.8.0` It is designed to execute binaries from Node packages without globally installing them. However, NPM documentation suggests globally installing certain packages, especially when they are CLI tools that will be used frequently across different projects.

So instead of installing globally you can execute:

```sh
npx @angular/cli
```

Or even if you want to use a specific version of [@angular/cli](https://www.npmjs.com/package/@angular/cli)

```sh
npx @angular/cli@17 
```

You have to type less, you can specify exact versions and you don't contaminate the developer's machine.

## But what about `package.json`?

The `package.json` file serves as the cornerstone of any Node.js project, acting as the project's manifest. It provides critical metadata such as the project’s name, version, and author, and it specifies the dependencies and devDependencies required for the project to run and be developed. Additionally, `package.json` defines custom scripts that automate common tasks like building, testing, and starting the application. This file ensures consistent environment setup, simplifies dependency management, and facilitates project configuration, making it an essential tool for maintaining and sharing Node.js projects.

So if it is a dependency of the project, why not add it to the `dependencies`, `devDependencies` or even `peerDependencies`?

You can specify the exact versions, using carot-minor (^) or tilde-patch (~) imports. It even automatically updates when fixes arrives. 

## But I can't run the tools' commands directly from the shell?

Well, fair point. If you want to run e.g. eslint, you don't want to enter:

```sh
node_modules/eslint/bin/eslint.js .
```

enter `npm` `scripts`. 

You can simply write:

```json
{
  "scripts": {
    "lint": "eslint ."
  }
}
```

So why does this work?

NPM scripts can locate executables in the project's node_modules/.bin directory without needing to specify the full path. I does also look at the project itself and the global installed packages and eventually just performs the command hoping the OS will pick it up. 

So that does mean that you can also install e.g. `nx` and use a script to alias the `nx` command:

```json
{
  "scripts": {
    "nx": "nx"
  }
}
```

## But what about passing arguments? 

You can't simply pass arguments to `nx` as `nx --help`, you need to pass them as positional arguments using an extra pair of dashes `--` like so as described in the [npm documentation](https://docs.npmjs.com/cli/v10/commands/npm-run-script#:~:text=Any%20positional%20arguments%20are%20passed%20to%20the%20specified%20script.%20Use%20%2D%2D%20to%20pass%20%2D%2Dprefixed%20flags%20and%20options%20which%20would%20otherwise%20be%20parsed%20by%20npm.):

```sh
nx -- --help
```

So that is why I don't need to install any global dependency. Ever. Because we have `npx` and `npm` `scripts`.