How to use Volta and why you should consider it over nvm


Node.js is alive and well, they've released version 17 into the ecosystem on October 19th, 2021. Along with Node.js 17 brings npm 8 and many cool new features. Now as you go to try it out, it's also a good time to think about how you're managing your toolchain.

Working on projects, big or small, it is important that all contributors are using the same versions of tools like node, npm, yarn, tsc, etc. This helps us mitigate both technical risks and potential business risks associated with it.

Volta (previous name: Notion, also nemo originally too) is a fantastic tool created to solve your toolchain issues properly.

GitHub - volta-cli/volta: Volta: JS Toolchains as Code. ⚡
Volta: JS Toolchains as Code. ⚡. Contribute to volta-cli/volta development by creating an account on GitHub.

What makes Volta so great?

The best phrase I can think of to explain Volta is magic. Yep, it's magic. It will magically (well, using a shim that proxies you to the right thing) get the right tool for you.

Now, I'm not just talking about node here, I'm talking about things like npm and tsc as well. Volta is so great that it can ensure everyone in your project uses the right typescript binaries from command line, instead of their potentially whack global version.

Although Volta still supports global versions, and requires you to do that first. But Volta will automagically switch to your project's configured version if it's specified.

Let's start with a basic example. Jumping into a test project, let's assume in production it was running on node v15 and npm v7. Let's pin that to the project!

➜  my-project $ volta pin node@15
success: pinned node@15.14.0 in package.json
➜  my-project $ volta pin npm@7  
success: pinned npm@7.24.2 in package.json

So wait, what just happened? You just configured your project with node 15 and npm 7. Nothing feels dramatic here, it's an effortless feeling, all that we see is these few lines that have popped into the package.json file, lets grep for volta here:

➜  my-project $ grep -A3 volta package.json
  "volta": {
    "node": "15.14.0",
    "npm": "7.24.2"

Neat, I guess! So did it work?

➜  my-project $ node --version
➜  my-project $ npm --version

It sure did! So now, whenever your terminal changes to this directory (or any of its subdirectories), Volta will (like magic) know to use that version. But hang on, what happens if we don't have the version installed? Let me change my node version in the volta object to 15.13.0 and show you what happens.

As you can see, Volta detected that we don't have that version installed and did it for us (again, magic!).

Lets move into the next example which is related to typescript. Using Volta we can install global tools as well, this way the binaries in that package become apart of our shell.

➜  my-project $ volta install typescript
success: installed typescript@4.5.2 with executables: tsc, tsserver

Take note here that typescript has been installed globally at version 4.5.2 and notes the executables tsc and tsserver. That really isn't interesting, npm can do that for us already - but we did this so tsc would be checked for in our path. I want it to use the version in my package. So lets add the beta version as a dev dependency for the sake of this demo:

➜  my-project $ npm i --save-dev typescript@beta

changed 1 package, and audited 2 packages in 4s

found 0 vulnerabilities
➜  my-project $ tsc --version
Version 4.5.0-beta

Notice how its not using the global version, but the version installed in my node_modules/.bin directory? Now everyone is on the same toolchain. Awesome!

Now you know how easy it is to set your version of node, tsc, and everything else in your path, plus ensure everyone else working on the project is on the same version. Let's go ahead and upgrade node and npm, it's simple: pin, and then commit your new package.json file!

➜  my-project $ volta pin node@17
success: pinned node@17.2.0 in package.json
   note: this version of Node includes npm@8.1.4, which is higher than your pinned version (7.24.2).
      To use the version included with Node, run `volta pin npm@bundled`
➜  my-project $ volta pin npm@8
success: pinned npm@8.1.4 in package.json
➜  my-project $ node --version
It even warns you - and suggested to use the bundled version! 

Hows this for thought - you could also consider using Volta in your Docker images, so that way even production matches up perfectly.  

Diving deeper: whats really going on?

This is a bit more technical.

Volta does not use anything crazy. It uses a simple but verified method of shims. Whatever tool you install using Volta, it adds a shim to your PATH which acts as a router to the correct version, and don't worry it is fast.

So to elaborate, once you install a global version of say, tsc from the typescript package (you would have usually done this through yarn global add typescript) it will add it to your path.

Volta is keeping record of this in the ~/.volta/tools/user/bins/ directory, but dumps them into your path by create an alias to volta-shim in ~/.volta/bin/  

➜  ~ $ ls -al ~/.volta/bin/node
~/.volta/bin/node -> ~/.volta/bin/volta-shim

When you run a shim, it will check for a package.json file (working back through your file path if it needs to) and find that volta key value set of versions to ensure it loads the correct version, if it can't find one it'll use the global default.

It creates a cache of these tools in the ~/.volta/tools/image/ directory so if it's not downloaded on your system, it grabs it for you.

This lets you trust your globally installed packages. You can read more about "Global installs done right" on their own blog where they go into a good example with Mocha.

Why is this better than nvm?

For starters, nvm is only supported on Unix environments. Although that is my preference, I also believe in supporting the preference of other developers. Sure, there is nvm-windows, but they act slightly different - wouldn't it be nice to have one solution that ensures everyone gets the same result to matter their operating system preference?

Another downfall to using nvm in is that you need to either use command line helpers, or manually type a command. It also doesn't handle things like npm and yarn versioning very well, and it doesn't do other things for your toolchain like seed your node_modules .bin directory (important when working with typescript projects - see above!)

➜  my-project $ which node
➜  my-project $ which npm
Volta controls the version through it's shim - you don't have to update your path

Another nice perk is that Volta is created in rust (check it out on GitHub), which compiles down to a really slim and fast binary, whereas nvm is a node project which is why it is not as fast.  

Let's install Volta!

Are you in love?

For those that have access to a Unix shell, you can get started like this:

curl | bash

For windows users, go ahead and download the installer on their website.

Getting Started | Volta
Getting Started

Thats it. Restart your command line tool, and enjoy! You can thank me later.

Appreciate you taking the time to read my blog. Feel free to subscribe, and I'll try my best to keep this blog updated.