ZSH autoloads to lazy load slow init scripts

 · 3 min · torgeir

Some scripts that need to be initialized when your terminal starts take a long time - too long to run them on every shell you spawn.

Terminal Zsh Autoload

pyenv and nvm comes to mind. You can use zsh autoloads to fix this, to “lazy load” shell scripts that need to be initialized when your shell is loaded.

This will prevent you from incurring the load on every spawned shell, postponing it to when you actually call the command you wish to load.

The following shows how you can do this for pyenv.

In ~/.zshrc add the following, that adds a folder $HOME/.zsh/autoloads/ to the zsh path, so it will find the autoload functions. Then register an autoload for each of the files in the folder by calling the autoload built-in with the name of the function.

# register autoloads
fpath=($HOME/.zsh/autoloads $fpath)
for f in $(ls $HOME/.zsh/autoloads); do
    autoload $f
done

I usually call the autoload functions the same as the scripts they intend to load. This causes trouble, however, because by default, it will load your script every time the autoloaded function is run. To prevent this, you can make use of the aptly named zsh function unfunction - to remove the autoloaded function that will initialize your script once the autoloaded function has run the first time.

Over in ~/.zsh/autoloads/, create a file to autoload pyenv

if [[ -z "$PYENV_SHELL" ]]; then
    unfunction pyenv
    echo "initializing pyenv" # remove this in the actual autoload
    eval "$(pyenv init -)"
    command pyenv "$@"
fi

It checks for some condition, that running eval $(pyenv init -) will make come true. You should adjust this depending on a condition that will determine if your script is already initialized. In this case, if $PYENV_SHELL is not set, it means pyenv was not yet initialized, so we need to load it. First, call unfunction pyenv, to undefine the very function (the autoload) that is being run. Then run the pyenv-initializing code. This in effect replaces the pyenv autoload command with the actual pyenv, such that the next time pyenv is run, the current fcuntion will not run, but the actual pyenv command will.

To also make the actual pyenv command work the first time around, when the autoloaded function runs, make sure to run it after the script has been initialized. This can be done with something like command pyenv "$@", which will forward all args given to the actual pyenv command.

❯ pyenv local 3.8.14
initializing pyenv

❯ python --version
Python 3.8.14

The next time you call pyenv, the actual command will be available instantly.

❯ pyenv local 3.8.14

❯ python --version
Python 3.8.14

I do this for a lot of commands. Actually, I stuck all my custom shell functions in there as well. Don’t know if that’s a good idea, but it seems to work fine.

Near instant shell load times! 🚀