Skip to content

Instantly share code, notes, and snippets.

@fl0w
Last active February 27, 2024 03:36
Show Gist options
  • Save fl0w/07ce79bd44788f647deab307c94d6922 to your computer and use it in GitHub Desktop.
Save fl0w/07ce79bd44788f647deab307c94d6922 to your computer and use it in GitHub Desktop.
# lazyload nvm
# all props goes to http://broken-by.me/lazy-load-nvm/
# grabbed from reddit @ https://www.reddit.com/r/node/comments/4tg5jg/lazy_load_nvm_for_faster_shell_start/
lazynvm() {
unset -f nvm node npm npx
export NVM_DIR=~/.nvm
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # This loads nvm
if [ -f "$NVM_DIR/bash_completion" ]; then
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
fi
}
nvm() {
lazynvm
nvm $@
}
node() {
lazynvm
node $@
}
npm() {
lazynvm
npm $@
}
npx() {
lazynvm
npx $@
}
@christophemarois
Copy link

In zsh, I have a different setup in my ~/.zshrc that not only lazyloads nvm and node, but also any globally-installed package

# Add every binary that requires nvm, npm or node to run to an array of node globals
NODE_GLOBALS=(`find ~/.nvm/versions/node -maxdepth 3 -type l -wholename '*/bin/*' | xargs -n1 basename | sort | uniq`)
NODE_GLOBALS+=("node")
NODE_GLOBALS+=("nvm")

# Lazy-loading nvm + npm on node globals call
load_nvm () {
  export NVM_DIR=~/.nvm
  [ -s "$(brew --prefix nvm)/nvm.sh" ] && . "$(brew --prefix nvm)/nvm.sh"
}

# Making node global trigger the lazy loading
for cmd in "${NODE_GLOBALS[@]}"; do
  eval "${cmd}(){ unset -f ${NODE_GLOBALS}; load_nvm; ${cmd} \$@ }"
done

@fl0w
Copy link
Author

fl0w commented Oct 23, 2018

updated for CLI npx and bash completion

@nicholasmakhija
Copy link

Thanks @christophemarois, I've been hunting for a solution like this one!

@plopezlpz
Copy link

@christophemarois this is gold, thanks

@VincentN
Copy link

VincentN commented Apr 29, 2020

Updated @christophemarois's script with npx, removed brew paths for nvm, added bash completion and updated the unset -f function call:

# Add every binary that requires nvm, npm or node to run to an array of node globals
NODE_GLOBALS=(`find ~/.nvm/versions/node -maxdepth 3 -type l -wholename '*/bin/*' | xargs -n1 basename | sort | uniq`)
NODE_GLOBALS+=("node")
NODE_GLOBALS+=("nvm")
NODE_GLOBALS+=("npx")

# Lazy-loading nvm + npm on node globals call
load_nvm () {
  export NVM_DIR=~/.nvm
  [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # This loads nvm
  if [ -f "$NVM_DIR/bash_completion" ]; then
    [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
  fi
}

# Making node global trigger the lazy loading
for cmd in "${NODE_GLOBALS[@]}"; do
  eval "${cmd}(){ unset -f ${cmd} >/dev/null 2>&1; load_nvm; ${cmd} \$@; }"
done

Thanks for sharing!

@adriaandotcom
Copy link

I got this error with @christophemarois and @VincentN script:

bash: eval: line xx: syntax error: unexpected end of file

I changed

eval "${cmd}(){ unset -f ${NODE_GLOBALS} >/dev/null 2>&1; load_nvm; ${cmd} \$@ }"

to

eval "${cmd}(){ unset -f ${cmd} >/dev/null 2>&1; load_nvm; ${cmd} \$@; }"

Note the change of NODE_GLOBALS and the ; at the end.

@VincentN
Copy link

@adriaanvanrossum, oops. Updated the script!

@laur89
Copy link

laur89 commented Jun 26, 2020

Unsetting NODE_GLOBALS still makes sense. My take (tested only in bash):

export NVM_DIR="$HOME/.nvm"
mapfile -t __NODE_GLOBALS < <(find "$NVM_DIR/versions/node/"*/bin/ -maxdepth 1 -mindepth 1 -type l -print0 | xargs --null -n1 basename | sort --unique)
__NODE_GLOBALS+=(node nvm)

# instead of using --no-use flag, load nvm lazily:
_load_nvm() {
    [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
    [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion
}

for cmd in "${__NODE_GLOBALS[@]}"; do
    eval "function ${cmd}(){ unset -f ${__NODE_GLOBALS[*]}; _load_nvm; unset -f _load_nvm; ${cmd} \"\$@\"; }"
done
unset cmd __NODE_GLOBALS

@alexbraga
Copy link

@VincentN is this the approach I should take if I want it to be applied to nodemon, for example?
Because with the original lazynvm.sh when I try to run nodemon inside a project I get a "command not found" error

@king-11
Copy link

king-11 commented Sep 16, 2020

@laur89 I ran into this error while using your code snippet
command not found: mapfile

also I am not able to use yarn globals using any of the above snippets @VincentN @christophemarois please any suggestion

@laur89
Copy link

laur89 commented Sep 16, 2020

command not found: mapfile

This suggests you're not using bash, as mapfile is bash builtin. As I stated above, the snippet was only ever tested in bash.

@king-11
Copy link

king-11 commented Sep 16, 2020

@laur89 thanks for your snippet and reply. So I just used a combined script took some parts from yours and some from @christophemarois now this works with yarn globals and node version is same for projects using yarn as well.

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

NODE_GLOBALS=(`find ~/.nvm/versions/node -maxdepth 3 -type l -wholename '*/bin/*' | xargs -n1 basename | sort | uniq`)
NODE_GLOBALS+=(node nvm yarn)

_load_nvm() {
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion
}

for cmd in "${NODE_GLOBALS[@]}"; do
eval "function ${cmd}(){ unset -f ${NODE_GLOBALS[*]}; _load_nvm; unset -f _load_nvm; ${cmd} \$@; }"
done
unset cmd NODE_GLOBALS

export PATH="$PATH:$HOME/.yarn/bin"```

@creio
Copy link

creio commented Nov 13, 2020

~/.zshrc

export NVM_DIR="$HOME/.config/nvm"

# Lazy load
if [[ -s "$NVM_DIR/nvm.sh" ]]; then
  NODE_GLOBALS=(`find $NVM_DIR/versions/node -maxdepth 3 -type l -wholename '*/bin/*' | xargs -n1 basename | sort | uniq`)
  NODE_GLOBALS+=("node")
  NODE_GLOBALS+=("nvm")
  # Lazy-loading nvm + npm on node globals
  load_nvm () {
    [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
  }
  # Making node global trigger the lazy loading
  for cmd in "${NODE_GLOBALS[@]}"; do
    eval "${cmd}(){ unset -f ${NODE_GLOBALS}; load_nvm; ${cmd} \$@ }"
  done
fi

@throrin19
Copy link

throrin19 commented Feb 26, 2021

It works fine but vscode tasks does not works with lazy loading 👎

@laur89
Copy link

laur89 commented Jul 30, 2021

Any idea if this solution still has merit after nvm-sh/nvm#2317?
Edit, in my case with bash, it still beats the original/vanilla by ~0.4s

@tusharsnx
Copy link

This does not work incase a script is run directly ./script.js.
script.js looks like:

#!/usr/bin/env node
# everything else

because env tries to find node within current paths which is not updated by the nvm due to lazy load.

solution that I found works is to create individual shell script for each of the command in the path which would trigger lazynvm(). nvm will then initialize node path at the beginning of the $PATH so that the next node call will run the node from the right path.

# lazynvm.sh
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
# node.sh
. $(dirname "$0")/lazynvm
node $@

and then add both to $PATH in your .zshrc:

export PATH="~/lazy-load:$PATH"  # assuming both files are inside ~/lazy-load dir

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment