Npx is cool... but why not use a relative $PATH?
npx is a nice tool. But you can solve the local packages problem with a relative path as well. And you can apply that to any package manager in any language!
What’s npx?
npx
is a tool that’s bundled with NPM.
Ordinarily, users would install a tool like Webpack or Gulp globally on their system, and then use it in various web projects.
The trouble with this approach, however, is that you cannot support two projects needing two different versions of a globally installed package.
npx
solves this by executing commands from the project’s local node modules, before looking elsewhere for the binary:
$ cd my-project
$ npx gulp
This would execute the gulp
executable that’s in the node modules folder of my-project
.
Other languages, other package managers
Of course this is a solution specific to the Nodejs ecosystem. We work with a variety of languages, however, for instance PHP.
With PHP, the package manager is called Composer, and it works similar to NPM. It also supports global and local packages, which poses the same kinds of problems. Ruby has gems – same difference.
A catch-all solution: a relative $PATH
You can solve all this by adding a relative path to your $PATH
. What?
Relative vs absolute
Consider the following two paths:
/usr/local/bin
./node_modules/.bin
The first path contains a leading slash. This signifies an absolute path. Wherever you are on your system, executing cd /usr/local/bin
will bring you to the exact same location.
The second path is relative. It starts with a dot (.
). The dot signifies “my current location”.
Executing cd ./node_modules/.bin
inside folder my-project
will bring you to the node modules’ binary directory of my-project
.
Executing the same command inside folder my-other-project
will move you to the directory relative to my-other-project
.
And therein lies the solution.
Modifying your $PATH
A quick refresher: your $PATH
is a global variable on your system, containing the paths to folders where executables are stored.
When you execute ls my-project
, ls
is the name of the program you want to execute, and my-project
its argument.
How does your shell know what ls
is or where to find the executable to run? It knows because the folder containing the ls
program is specified in your $PATH
.
Your $PATH
can contain many paths. You can modify it in the standard configuration file supported by your shell, for instance .bashrc
or config.fish
.
The following would be an example of how to modify your $PATH
when using the Bash shell:
export PATH=./node_modules/.bin:./vendor/bin:$PATH
This adds the path ./node_modules/.bin
and ./vendor/bin
to the $PATH
(respectively the binary directories of NodeJS projects and PHP projects).
Things to note:
- The paths are separated by a colon. This is Bash-specific, other shells might use different syntax or commands.
- The existing
$PATH
variable is included at the end. If we would omit that, the$PATH
from then on would only contain our custom paths, and you wouldn’t even be able to executels
, since you removed its executable directory from the$PATH
!
Now, whenever we execute the following, it will find the gulp
executable in the node_modules
directory of the current project, and execute it:
$ gulp
The same works with PHP packages:
$ phpunit
This would look up phpunit
inside ./vendor/bin
.
Great!
npx auto-install feature
In all fairness, there’s another feature of npx
that’s not easily mimicked: it auto-installs any missing package it needs:
$ npx gulp
This would run gulp
even when it’s not installed on your system, as a one-off command invocation, which is a pretty neat feature if you need it.