Slow shell startup after installing nvm

Fixing slow shell startup after installing nvm

Published on Nov 20, 2019

Reading time: 2 minutes.

Fixing slow shell startup after installing nvm (zsh)

The issue

If you’ve ever written software using node.js or one of the languages dependant on its ecosystem, chances are you’ve encountered nvm, the node version manager. It is an essential tool for managing different node.js and npm versions on a single machine.

Unfortunately, it is also (1) written entirely in bash and (2) makes a bunch of superfluous calls to npm, which makes it quite slow.

The official nvm documentation suggests adding the following script to your shell config file to enable nvm:

export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/" ] && \. "$NVM_DIR/" # This loads nvm

This increased the time until a new shell became available after starting it by about 0.5 seconds on my desktop and 1.5 seconds on my notebook.

Available fixes

1. Switching to the nvm-ng fork

There have been PRs to nvm to fix the slow startup type by removing the calls to npm. The maintainer’s response has been very questionable:

However, I’m not going to remove it lightly - it’s been there, with the slowdown, for 2 years now. If you can’t use nvm with that check, then for the time being, you can’t use nvm - but I hope we can come up with a healthier long term plan than that.

As a reaction to this response, the author of the PR has hard forked nvm. The fork is available here: nvm-ng.

I personally didn’t want to switch to a fork of nvm. The fork is already about 100 github commits behind the original.

2. Deferring the nvm setup

Most shells won’t ever interact with nvm, so deferring the nvm setup until the first time nvm, node or npm are called saves a lot of time.

Growing with the Web has written a nice bash script to achieve just that.

The script does use bash-specific built-in syntax though, namely a type -t call. The -t flag does not exist for other shells such as the zsh, so you’re likely to encounter an error when running it:

/home/$USER/.zshrc:type:42: bad option: -t

All we need to do is changing the syntax to a corresponding zsh implementation.

# Defer initialization of nvm until nvm, node or a node-dependent command is
# run. Ensure this block is only run once if .bashrc gets sourced multiple times
# by checking whether __init_nvm is a function.
if [ -s "$HOME/.nvm/" ] && [ ! "$(whence -w __init_nvm)" = "__init_nvm: function" ]; then
  export NVM_DIR="$HOME/.nvm"
  [ -s "$NVM_DIR/bash_completion" ] && . "$NVM_DIR/bash_completion"
  declare -a __node_commands=('nvm' 'node' 'npm' 'yarn' 'gulp' 'grunt' 'webpack')
  function __init_nvm() {
    for i in "${__node_commands[@]}"; do unalias $i; done
    . "$NVM_DIR"/
    unset __node_commands
    unset -f __init_nvm
  for i in "${__node_commands[@]}"; do alias $i='__init_nvm && '$i; done

After adding this script to my .zshrc and removing the original nvm startup lines, starting up shells has become nearly instantaneous again.