Lightweight Node.js version switching
Recently, I’ve been paying attention to the time it takes my shell’s
init script to complete. Bash is notoriously slow, but since it’s
popular in scripting use, I keep using it. This leaves me to optimize my
~/.bashrc
.
I program with Node.js frequently, so a Node.js version manager is an
essential tool. Upon investigating the execution time of my .bashrc
, I
found that loading nvm takes a lot of time:
$ ls ~/.nvm/versions/node
v10.11.0 v8.12.0 v9.11.2
$ export NVM_DIR="$HOME/.nvm"
$ time source "$HOME/brew/opt/nvm/nvm.sh"
real 0m0.397s
user 0m0.270s
sys 0m0.134s
$ nvm --version
0.33.11
400 ms for sourcing nvm.sh
is a way too big share of the time budget
I’d like to allocate for starting Bash in interactive mode. It’s a pity,
because nvm
is a quite nice tool.
An alternative for nvm
is nodenv:
$ ls ~/.nodenv/versions
10.11.0 8.12.0 9.11.2
$ time eval "$(~/brew/bin/nodenv init -)"
real 0m0.070s
user 0m0.034s
sys 0m0.034s
$ nodenv --version
nodenv 1.1.2
I can manage with 70 ms. This is the tool I chose to use as my Node.js
version manager for a while. But because nodenv
utilizes shims to wrap
the executables of the selected Node.js version, a couple of problems
arise. The first is that after installing a new executable from global
npm package, you must remember to run nodenv rehash
to rebuild the
shims. Otherwise you can’t run the executable. The second is that you
lose access to the manual pages of the wrapped executables: a shim is an
indirection for the actual executable, causing man
’s manual page
search to miss the page. A demonstration of the problems:
$ npm ls -g --depth=0
/Users/tkareine/.nodenv/versions/10.11.0/lib
`-- npm@6.4.1
$ npm install -g marked
/Users/tkareine/.nodenv/versions/10.11.0/bin/marked -> /Users/tkareine/.nodenv/versions/10.11.0/lib/node_modules/marked/bin/marked
+ marked@0.5.1
added 1 package from 1 contributor in 0.586s
$ command -v marked
$ nodenv rehash
$ command -v marked
/Users/tkareine/.nodenv/shims/marked
$ man -w node
No manual entry for node
$ man -w marked
No manual entry for marked
I keep forgetting to run nodenv rehash
and I do would like to access
the manual pages of the executables of the selected Node.js version.
nvm
and nodenv
have a lot of features. While they are useful in some
scenarios, such as continuous integration setups, I’d be satisfied with
less in my development environment. The ability to install specific
Node.js versions and to switch between them easily, independently per
shell session, would be enough.
In the Ruby community, ruby-install and chruby tools provide just
these features, and nothing more. The former is for installing Rubies
and the latter for switching between them. What’s great about this
arrangement of separate tools is that the switcher, chruby
, is very
lightweight.
node-build, part of nodenv
project, is a dedicated Node.js
installer. It checks the digest of the downloaded Node.js package and
allows you to unpack it to any directory. This is good and I’ll keep
using it.
For the version switcher, I didn’t find anything I liked. sh-chnode is
written in the same spirit as chruby
, but includes some design
decisions I didn’t like personally.
I ended up writing my own version switcher, even though there’s already
so many of them. But this one is fast to load, does one thing well, and
is suitable for me. :) Naming is hard, so I just call it chnode
. Let’s
see it in action:
$ ls ~/.nodes
node-10.11.0 node-8.12.0 node-9.11.2
$ time source ~/brew/opt/chnode/share/chnode/chnode.sh
real 0m0.007s
user 0m0.004s
sys 0m0.003s
$ chnode node-10
$ chnode
* node-10.11.0
node-8.12.0
node-9.11.2
$ npm ls -g --depth=0
/Users/tkareine/.nodes/node-10.11.0/lib
└── npm@6.4.1
$ command -v marked
/Users/tkareine/.nodes/node-10.11.0/bin/marked
$ man -w node
/Users/tkareine/.nodes/node-10.11.0/share/man/man1/node.1
$ man -w marked
/Users/tkareine/.nodes/node-10.11.0/share/man/man1/marked.1
$ chnode --version
chnode: 0.2.0
For me, chnode is the tool comparable to chruby
for Node.js
versions. Like chruby
, the primary mechanism of chnode
is to modify
the PATH
environment variable to include the path to the bin
subdirectory of the selected Node.js version. But unlike chruby
,
chnode
does not modify any Node.js specific environment variable
(there’s no need).
I didn’t implement auto-switching to chnode
. The feature would switch
Node.js version to the version specified in the .node-version
file if
the current working directory, or its parent, would have the file. You
might put such a file at a project’s root directory. chruby
has the
feature, but because I don’t use it, I dropped it.
chnode
supports GNU Bash and Zsh, has good test coverage, and
allows you to display the selected Node.js version in the shell prompt
with ease. It’s MIT licensed. See the README for more.
Finally, the total execution time of initializing my Bash
setup in interactive mode, including selecting a Node.js
version with chnode
:
$ time bash -i -c true
real 0m0.337s
user 0m0.240s
sys 0m0.083s