nvm: sourcing nvm.sh is slow even with `--no-use`

I start up a lot of terminal windows on my Mac and for some reason the commands

  [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" --no-use
  [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  

that I had in my .bashrc would sometimes take a noticeable and annoying amount of time to run. (Maybe as long as 2 seconds.) My solution was to setup environment variables like NVM_DIR as usual but to fully defer all other nvm setup until first use by adding these definitions to my .bash_profile script:

function _install_nvm() {
  unset -f nvm
  [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This sets up nvm
  [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # load nvm bash_completion
  nvm "$@"
}

function nvm() {
    _install_nvm "$@"
}

I am not familiar enough with other shells to say how portable that is (I am using GNU bash), but I suggest you at least add it to the documentation (if not the install script) so that people can have an easy way to install deferred setup of nvm that is truly deferred.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 14
  • Comments: 21 (8 by maintainers)

Commits related to this issue

Most upvoted comments

I use this for zsh:

if [ -s "$HOME/.nvm/nvm.sh" ]; then
  export NVM_DIR="$HOME/.nvm"
  nvm_cmds=(nvm node npm yarn)
  for cmd in $nvm_cmds ; do
    alias $cmd="unalias $nvm_cmds && unset nvm_cmds && . $NVM_DIR/nvm.sh && $cmd"
  done
fi

@ljharb Thanks for re-opening this.

It appears --no-use is sometimes slow because nvm_supports_source_options is not reliable, at least not under GNU bash version 3.2.57(1) on my Mac, and when nvm_supports_source_options is false, the --no-use option is ignored.

$ (nvm_supports_source_options && echo true) || echo false
true
$ (nvm_supports_source_options && echo true) || echo false
false
$ i=0; while nvm_supports_source_options; do echo -n .; ((i++)); done; printf "\n%s\n" $i
..
2
$ i=0; while nvm_supports_source_options; do echo -n .; ((i++)); done; printf "\n%s\n" $i

0
$ i=0; while nvm_supports_source_options; do echo -n .; ((i++)); done; printf "\n%s\n" $i
.......
7

The current implementation of nvm_supports_source_options writes to a pipe that then tries to source that pipe as a shell script via /dev/stdin. I guess there is some race condition resulting in the output of the pipe being discarded rather than processed.

FWIW, I updated my .bash_profile script to this:

function _install_nvm() {
  unset -f nvm npm node
  # Set up "nvm" could use "--no-use" to defer setup, but we are here to use it
  [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This sets up nvm
  [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # nvm bash_completion
  "$@"
}

function nvm() {
    _install_nvm nvm "$@"
}

function npm() {
    _install_nvm npm "$@"
}

function node() {
    _install_nvm node "$@"
}

That suits me better, because there is no loading of anything until I need it, but when I need node, it is (almost) immediately available.

With this solution:

function _install_nvm() {
  unset -f nvm npm node
  # Set up "nvm" could use "--no-use" to defer setup, but we are here to use it
  [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This sets up nvm
  [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # nvm bash_completion
  "$@"
}

function nvm() {
    _install_nvm nvm "$@"
}

function npm() {
    _install_nvm npm "$@"
}

function node() {
    _install_nvm node "$@"
}

I still losing 0.01 - 0.03s of shell speed. but thanks anyway.

Looks like this is a bug in Gnu bash 3.2.x, which is what Apple ships, apparently because of licensing issues. (bash 3.2.57(1) is what shipped with El Capitan in 2015 and it is still what is shipping with Catalina in 2020, although starting with Catalina the default shell was switched to zsh.) It appears the receiving side of the pipe does not wait for the sending side of the pipe to start producing output before deciding that is has reached EOF. The best workaround I can give you so far is to use a here-is document instead of echo to produce the script. I’m not a portability expert and do not know what it might break, so I do not want to offer this as a PR, but feel free to take it.

nvm_supports_source_options() {
  [ "_$( . /dev/stdin yes 2> /dev/null <<'EOF'
[ $# -gt 0 ] && echo $1
EOF
       )" = "_yes" ]
}

Spent a hour of debugging my .zshrc file, thanks god I found why

Thanks @ljharb for your quick replies. I will do that