Better NVM Lazy Loading
tl;dr:
export NVM_DIR="$HOME/.nvm"
# This lazy loads nvm
nvm() {
unset -f nvm
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" --no-use # This loads nvm
nvm $@
}
# This loads nvm bash_completion
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
# This resolves the default node version
DEFAULT_NODE_VER="$((cat "$NVM_DIR/alias/default" || cat ~/.nvmrc) 2> /dev/null)"
while [ -s "$NVM_DIR/alias/$DEFAULT_NODE_VER" ] && [ ! -z "$DEFAULT_NODE_VER" ]; do
DEFAULT_NODE_VER="$(cat "$NVM_DIR/alias/$DEFAULT_NODE_VER")"
done
# This resolves the path to the default node version
DEFAULT_NODE_VER_PATH="$(find $NVM_DIR/versions/node -maxdepth 1 -name "v${DEFAULT_NODE_VER#v}*" | sort -rV | head -n 1)"
# This adds the default node version path to PATH
if [ ! -z "$DEFAULT_NODE_VER_PATH" ]; then
export PATH="$DEFAULT_NODE_VER_PATH/bin:$PATH"
fi
Lazy loaded NVM that behaves as expected without unnecessary complexity or hacks.
- All binaries - including globally installed npm packages - are available before loading NVM.
- NVM is not loaded until the
nvm
command is used. - Supports the default alias and
~/.nvmrc
. - Supports alias chains such as
default -> lts/erbium -> v12.14.1
and partial versions such asv12 -> 12.14.1
. - Simple.
Caveats:
- Does not support NVM internal aliases such as
stable
.
NVM can add a long delay to your shell startup. Especially in environments like WSL. The solution is to lazy load it. However, the node
, npm
, npx
, and any globally installed npm binaries such as gulp
, eslint
, etc. are not available until NVM is loaded.
The existing popular solutions fall short for a couple reasons. The top results from an "npm lazy load" Google search suffer some or all of these issues:
- Whitelists binaries that can be used before loading NVM by creating an alias for each one.
- This is cumbersome to maintain as it requires you to create a new alias for every global npm package you install.
- Requires loading NVM before any of the binaries can be executed.
- This just kicks the slow startup problem to the first execution of
node
,npm
, etc. It is also unnecessary as NVM is not required to execute the binaries.
- This just kicks the slow startup problem to the first execution of
- Does not support aliases such as
lts/erbium
as the default.- Requires you to use a specific version as the default.
- Hardcodes a specific version of node.
- Again, cumbersome to maintain as it is disconnected from the default alias and
~/.nvmrc
. This requires updating whenever you change your default version or behavior will be inconstant with NVM.
- Again, cumbersome to maintain as it is disconnected from the default alias and
- Uses hacky methods of discovering binaries and other unnecessary complexity.
Lazy load NVM
For reference, here is the boilerplate script NVM installs:
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
Let's lazy load instead:
export NVM_DIR="$HOME/.nvm"
# This lazy loads nvm
nvm() {
unset -f nvm
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" --no-use # This loads nvm
nvm $@
}
# This loads nvm bash_completion
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
As you can see, the lines for setting NVM_DIR
and sourcing bash_completion
remain unchanged. We move the code for loading NVM into a function and add the --no-use
flag. The function unsets itself, loads NVM, and executes nvm
. This way NVM is not loaded until the nvm
command is actually used.
- The
--no-use
flag prevents NVM from automatically executingnvm use
after loading which is unnecessary and can be slow.
Make binaries accessible
All the binaries are already on your drive ready to go. There is no reason you must load NVM before executing them. All you need to do is include them in PATH
(which is exactly what NVM does).
export PATH="$NVM_DIR/versions/node/v12.14.1/bin:$PATH"
Now we can access all the v12.14.1
binaries without having to load NVM. This includes globally installed npm package binaries.
If you don't mind the version being hardcoded, you can stop here.
Use the default version
We don't want to hardcode a version. Instead, we want to mirror NVM's behavior and use either the default alias or ~/.nvmrc
:
DEFAULT_NODE_VER="$((cat "$NVM_DIR/alias/default" || cat ~/.nvmrc) 2> /dev/null)"
First we try using the default alias. If this fails we try using ~/.nvmrc
. NVM stores aliases as files and we get the version from its contents just like .nvmrc
files.
Support aliases
It is possible to set the default to an alias (which could, in turn, reference another alias). For example default -> myalias -> lts/erbium -> v12.14.1
. Let's follow the chain of possible aliases to get the root version:
while [ -s "$NVM_DIR/alias/$DEFAULT_NODE_VER" ] && [ ! -z "$DEFAULT_NODE_VER" ]; do
DEFAULT_NODE_VER="$(cat "$NVM_DIR/alias/$DEFAULT_NODE_VER")"
done
If alias/$DEFAULT_NODE_VER
exists, we know we have an alias. Set DEFAULT_NODE_VER
to the contents of the alias and repeat. When it does not exist, assume we have reached the root version and stop.
[ -s ... ]
checks if the alias exists.[ ! -z ... ]
checks if we hit a dead-end (and prevents an infinite loop).
Support partial versions
It is also possible to set a partial version. For example v12 -> v12.14.1
. Let's resolve by matching the newest version with the given version as the prefix:
# This resolves the path to the default node version
DEFAULT_NODE_VER_PATH="$(find $NVM_DIR/versions/node -maxdepth 1 -name "v${DEFAULT_NODE_VER#v}*" | sort -rV | head -n 1)"
v${...#v}
ensures the version is always prefixed with exactly 1v
as both12.14.1
andv12.14.1
are possible.sort -rV
order matches versions newest to oldest.head -n 1
keep the newest matched version.
Caveats
This is a poor-man's version of NVM's alias resolution and as such does not support NVM internal aliases such as stable
. NVM does not store these aliases in files and instead calculates these on-the-fly.
Installation
Put it all together and you get the script at the top of this page. Replace the default script NVM added to your .bashrc
/.zshrc
/etc. Be sure to compare the boilerplate lines for setting NVM_DIR
, bash_completion
, and nvm.sh
as these may change in future versions.