应该说很早之前我就注意到终端启动时加载时间过长(有时候达到 5s 以上)了,且我忘记是去年底还是今年初时,我也分析过自己的 .bash_profile 发现 nvm 是耗时增加的最大凶手有时候能达到 3s 左右的耗时,极大的拖慢了终端的启动速度。刚开始我也是参考一些社区解决方案,在 nvm 初始化的时候添加了 -no-use 参数,并手动指定了默认 nodejs 版本,这个解决方案确实有减少大概 1s 左右的初始化耗时。但其实大约 2s 的执行时间还是超过我配置文件中其他全部脚本的执行时间一倍以上。

于是我最近就在想一个问题,我真的有必要在每个 terminal 启动时就去初始化 nvm 吗?答案是否定的,因为我并不是每次都需要切换 node 版本,第一个也许我当前写的项目根本就不是一个前端项目,第二个就是就算写前端项目也不是每次都必须切换到指定 node 版本才能工作(我会将一个常用的 node 版本作为默认版本),第三点就是 nvm 会在启动时自动扫描当前项目的 .nvm 配置指定 node 版本我是不是需要?而我给出的答案是,为了这个功能增加我每次的终端启动速度我选择更快的启动速度。

得出来的结论就是,我其实好像不需要每次终端启动就去初始化 nvm 版本管理,但是我需要能够在我想用的时候至少 nvm 的版本切换功能是正常的,于是我开始尝试 hook 一下 nvm 版本管理,在执行前判断一下 nvm 有没有加载如果没有初始化的话我先通过 init 初始化它,然后执行 nvm 版本管理。如此一来,我就能够将耗时操作从高频的每次终端启动,迁移至低频操作第一次执行 nvm 版本管理工具时(虽然会导致每个新的 terminal 在第一次使用 nvm 命令时会先初始化导致增加 3s 左右的耗时,但是我想这是值得的),下面就是我的 lazy_nvm 函数定义。

# 懒加载 nvm
NVM_DIR="$HOME/.nvm"
function lazy_nvm() {
    if [ ! -n "$(command -v nvm_auto)" ]; then
        unalias nvm # 必须先取消 alias 设置,否则会导致递归调用
        [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
        nvm $@
    else
        nvm $@
    fi
}

alias nvm=lazy_nvm

# NVM 管理工具
# [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[[ -r $NVM_DIR/bash_completion ]] && \. $NVM_DIR/bash_completion

# 手动指定终端默认 NodeJS 版本优化 nvm 脚本加载速度,使用 nvm which default 查看默认版本路径
NVM_VERSION_DEFAULT=$(cat $NVM_DIR/alias/default)
export PATH="$NVM_DIR/versions/node/$NVM_VERSION_DEFAULT/bin/:$PATH"

让我来稍微解释一下上面的代码,首先我定义了一个函数 lazy_nvm 并且使用别名的方式将 nvm 指令重定向到 lazy_nvm 函数中以此达到 hook nvm 指令的效果,在 lazy_nvm 函数中首先判断有没有 nvm_auto 命令以此作为条件判断 nvm 是否已经初始化过了,因为在 nvm.sh 中有不少 nvm 相关的函数定义而 nvm_auto 就是其中之一,如果我们没有执行 nvm.sh 初始化的话正常情况我们是不会有 nvm_auto 指令的,相反如果初始化后 nvm_auto 指令也就会被定义出来。

接着,首先需要通过 unalias 来取消 nvm 的别名定义,因为我们之前是用别名的方式已经将 nvm 指向 lazy_nvm 函数了现在如果不取消它的话会导致递归调用,随后正常执行 nvm.sh 初始化它,并且执行 nvm 将全部参数传过去。当然,如果在当前 terminal 上下文中已经初始化过了的话直接调用 nvm 传参数就好了。

当然,这个时候我们是没有在 PATH 中加入任何 nodejs 版本的,因为原本 nvm.sh 中读取默认版本并加载到 PATH 中的操作,由于这里定义成函数后并不会执行,所以我们需要手动指定一个 node 版本,我这里是使用 $NVM_DIR/alias/default 指定的默认版本并将其加到 PATH 中(需要注意的是 nvm 支持定义 v20 这种模糊版本号,但是 $NVM_DIR/versions/node 文件夹中下载的 node 都是语意明确的版本,所以在执行 nvm alias default v18.20.2 时切忌使用类似 v18 这种模糊版本),当然你这里也可以改成你明确本地有的版本路径。

NVM 懒加载延迟初始化减少 bash 启动耗时-天真的小窝

最后,我其实在想我们的代码和产品中或许充斥类似的为了 “避免麻烦” 或者所谓的 “为了用户” 而在一个高频操作中加入一些本是一个低频操作的东西以至于它都影响到原本的系统正常工作?意识到它的存在避免它的存在,“这条小鱼在乎,这条也是,还有这条,这条…”

(可不要像隔壁群的怪叔叔那这样,人家国庆都是想着去哪玩,他整天就想着他这终端能不能再快一秒.jpg

好了,今天的划水文就先写到这里,祝大家国庆快乐。