nvm: NVM starting too slow -- can I just add the bin folder to PATH?

Hey. I use a minimal zsh config to make sure my shell starts fast. However after adding

. ~/.nvm/nvm.sh

The shell starts much slower.

So I changed it to

PATH=~/.nvm/versions/io.js/v2.3.3/bin:$PATH

I know I can’t utilize nvm full this way, but I’m OK with just using one version. When I want to use another version I’ll load ~/.nvm/nvm.sh to use nvm and then change the node bin PATH. But will this setting break anything? Any ENV var I should set up manually?

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Reactions: 12
  • Comments: 80 (24 by maintainers)

Commits related to this issue

Most upvoted comments

I made my zsh load 0.8 seconds faster by loading nvm when “nvm”, “npm” or “node” is used for the first time. Check out my Gist https://gist.github.com/QinMing/364774610afc0e06cc223b467abe83c0#file-zshrc-L15-L37

Thanks for working on this @lxe! this has been slowing me down for at least the last 9 months and I didn’t even realize what was causing the 1.5s delay in spinning up another terminal.

I came to this workaround after reading what @krokofant put:

# This loads nvm
export NVM_DIR=~/.nvm
function nvm {
  if [ -s "$NVM_DIR/nvm.sh" ]; then 
    . "$NVM_DIR/nvm.sh"
    nvm use system
    nvm $@
  fi
}

The brilliant part is that you can get by even if you totally forget about the workaround, node will just start working after the first time you type nvm and you think “huh, that’s odd” and move on with life 😃

I had wrapped the nvm’s startup into a function which I would just invoke manually.

function loadNVM() {
    export NVM_DIR="$HOME/.nvm"
    [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
    [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion
}

Solution from @netvisao 's and @giggio 's modification, does the same but better, no manual load required. But it still didnt work when I just say nvm list for example, without first running node/npm. So here is their solution combined, with one line added:

export PATH=$PATH:~/.npm/node_modules/.bin # Global modules
export NVM_DIR=$HOME/.nvm
nvm_load () { . $NVM_DIR/nvm.sh && . $NVM_DIR/bash_completion; }
alias node='unalias nvm; unalias node; unalias npm; nvm_load; node $@'
alias npm='unalias nvm; unalias node; unalias npm; nvm_load; npm $@'
alias nvm='unalias nvm; unalias node; unalias npm; nvm_load; nvm $@'

Bash user here, its slow enough to be annoying when starting a new window in tmux:

time /usr/local/opt/nvm/nvm.sh

real    0m0.617s
user    0m0.442s
sys 0m0.204s

@msafi if you add --no-use after . "$NVM_DIR/nvm.sh"', is it still slow?

For my case, it try to minimal the components init when starting terminal. I have add alias nvminit='. "$(brew --prefix nvm)/nvm.sh"' as my workaround.

Great solution from @netvisao. I just changed it to unalias both npm and node:

nvm_load () { . $NVM_DIR/nvm.sh && . $NVM_DIR/bash_completion; }
alias node='unalias node; unalias npm; nvm_load; node $@'
alias npm='unalias node; unalias npm; nvm_load; npm $@'

I have the issue and I’m using bash.

W/o going too much into introspecting the nvm script, I looked into lazy loading Here is how my ~/.bash_profile looks like

# NPM
 export PATH=$PATH:~/.npm/node_modules/.bin # Global modules
 export NVM_DIR=$HOME/.nvm
 nvm_load () { . $NVM_DIR/nvm.sh && . $NVM_DIR/bash_completion; }
 alias node='unalias node; nvm_load; node $@'
 alias npm=' unalias npm;  nvm_load; npm  $@'

The slowdown will only be noticed when node or npm is executed the first time during the current terminal session

A good solution from @pauldraper:

Replace nvm stuff in ~/.bashrc with:

export NVM_DIR="$HOME/.nvm"
nvm_load () {
  . $NVM_DIR/nvm.sh
  . $NVM_DIR/bash_completion
}
alias node='unalias nvm; unalias node; unalias npm; nvm_load; node $@'
alias npm='unalias nvm; unalias node; unalias npm; nvm_load; npm $@'
alias nvm='unalias nvm; unalias node; unalias npm; nvm_load; nvm $@'

The two things that are slowing down the nvm.sh invocation (which automatically runs nvm use):

  • running npm config get prefix which invokes, node/npm delaying things up to 2 seconds
  • resolving the default alias, which takes about 1 second
  • the rest of ‘nvm use’ is slow, which invokes awk to do a version comparison, installation verification, iojs/0.12 tricks, etc…

I’m going to take a crack at these perf issues one by one

@Hubro the only reason it’s slow is because nvm use invokes npm config get prefix, which is slow.

The only way this can be sped up is to either a) make npm faster, or b) replicate npm’s logic inside nvm, which is both complex, and a brittle approach.

If you nvm unalias default, or source nvm with --no-use, then there will be no performance impact (but you’ll have to nvm use when you need node/npm).

I just put together a solution to that defers initialization of nvm until it’s needed that works pretty well:

# 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/nvm.sh" ] && [ ! "$(type -t __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"/nvm.sh
    unset __node_commands
    unset -f __init_nvm
  }
  for i in "${__node_commands[@]}"; do alias $i='__init_nvm && '$i; done
fi

I go into more detail in this blog post: http://www.growingwiththeweb.com/2018/01/slow-nvm-init.html

It’s a bit hacky but this is what I use

export NVM_DIR=~/.nvm
function nvm {
    . "$(brew --prefix nvm)/nvm.sh" --no-use
    nvm use system
    nvm $@
}

So hey, why is this issue closed?

Also, after reading through all the comments here, I don’t really see anybody discussing why nvm is so slow to initialize in the first place. Other version managers (like pyenv and rvm) are vastly faster than nvm to initialize.

Quick comparison with pyenv:

~ 
➜ time zsh -c 'eval "$(pyenv init -)"; exit'
zsh -c 'eval "$(pyenv init -)"'  0.05s user 0.02s system 101% cpu 0.062 total

~ 
➜ time zsh -c "source /usr/share/nvm/init-nvm.sh; exit"           
zsh -c "source /usr/share/nvm/init-nvm.sh; exit"  0.23s user 0.03s system 112% cpu 0.230 total
 
~ 
➜ time zsh -c "source /usr/share/nvm/init-nvm.sh; nvm use 7; exit"
Now using node v7.10.1 (npm v4.2.0)
zsh -c "source /usr/share/nvm/init-nvm.sh; nvm use 7; exit"  0.69s user 0.07s system 112% cpu 0.673 total

The NVM equivalent of doing eval "$(pyenv init -)" takes over 34 times longer to execute.

Is there nothing that can be done to speed up nvm, rather than devising workarounds like lazy loading?

Personally I find the delay when starting my terminal unacceptable, so I’ve switched to manually enabling nvm when I need it. I wish that wasn’t necessary.

@zeroby0 This is what I currently have and it’s working fine for me.

# -------------------------------- #
# Node
# -------------------------------- #

# NVM
# . ~/.nvm/nvm.sh
# PATH=~/.nvm/versions/node/v6.10.0/bin:$PATH
export PATH=~/.nvm/versions/node/v7.7.1/bin:$PATH

I just run source ~/.nvm/nvm.sh when I actually need nvm.

@ljharb tks. now i usezsh-nvm ,this is a great plugin.

Oh, turns out if I defer nvm initialization, my global Node CLI commands don’t work until I nvminit, so I guess --no-use is the best solution for now.

EDIT: looks like with --no-use I don’t have access to npm and node at start-up. I have to do something like nvm use stable before I can begin using them. So, that’s a no-go for me either…back to square one…

Someone summoned me? J/K @ljharb is doing a great job with this project, he’s pretty much in charge of all decisions.

@netvisao right, but say you have eslint installed as a global binary in your nvm-managed node; eslint won’t work until after node is ran, which is not desirable.

The zsh-nvm plugin supports lazy load and solved this issue for me. My load time dropped from 2.2s to 0.8s.

@Tsuki . "$(brew --prefix nvm)/nvm.sh" --no-use - although I highly discourage using brew at all, as it’s completely unsupported.

I just did some other digging and saw the homebrew thing. That’s a total bummer, and while I can understand the need to limit support scopes, the most popular package manager for mac shouldn’t be excluded.

I’ll give it a try to uninstall and install the old fashioned way and let you know if this helps speed things up at all.

https://gist.github.com/audacioustux/4967b2c6d25a004c394455a95f676508#file-nvm-zsh with this, it takes less than one second, and version change is only triggered by .nvmrc, and use the system node version by default

It makes sense to optimize for your demands. I don’t change Node.js versions that often, maybe once every few days. But I open several terminals a day, so the startup time impacts me more. Switching could be faster, but I can easily live with it. And I don’t remember the last time I needed 2 node versions on different shell sessions, but this is clearly the main difference: nvm touches your session, not your system, and that takes time. N, on the other hand, changes the system, and the session loads faster. And yes, N sucks as a name. You can’t search for it.

@audacioustux what if you have two different versions of the same module, globally installed in two nvm-managed node versions?

(also you can . $NVM_DIR/nvm.sh --no-use without incurring any speed penalty, and then you don’t need to copy-paste any of nvm’s functions)

Again, note that this has the caveat of not allowing globally installed modules in the default node to work prior to invoking node/npm/nvm.

@TheNotary however, that means that both node and npm, as well as any globally installed binaries you have in the default node version, will be unavailable until the first time you’ve typed an nvm command.

@gatspy you’d then have to manually run nvm use when you were ready to use node.

nvm is also causing my shell start-up time to be slow. Yes, I’m using oh-my-zsh. Happy to provide any additional information.

For now I’m using @Tsuki’s nvminit solution like this:

alias nvminit='export NVM_DIR="/Users/mk/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"'

Pretty good as a workaround…

Thanks for the tip. I’d still like to know which zsh option is causing the problem, since sourcing nvm.sh is lightning fast in stock zsh as well as every other shell I’ve tested. If the code you posted is all you’re including beyond stock zsh then thanks, that’s awesome, and I’ll take a deeper look once I’m back in my country in a week and a half 😃

As far as I know, that will work fine. You seem to understand that the nvm command won’t be usable without first sourcing it.