#What’s lazy loading?
Lazy loading is postponing the loading/initialization of modules until they are required. If we have a plugin for a specific programming language, we don’t need to use it until we edit a file in said language, so we don’t run it immediately at startup, but wait until we encounter a specific filetype.
If you are someone who only recently started using Neovim and uses some prebuilt configuration like kickstart.nvim↗ or LazyVim↗1 you are already doing this. Both of them use lazy.nvim↗, which does its best to make sure you don’t load anything unless you need it.
But you don’t actually have to use a complex plugin manager to do this and it can be implemented in like 30 lines of code using builtin Neovim functionality.2
#How does it work?
Neovim and modern Vim have a builtin package system (see: :h packages
).
Plugin managers may place installed plugins somewhere in your 'runtimepath'
,
in a directory that looks like “pack/*/start” or “pack/*/opt” where the star
is any arbitrary name that the plugin manager chooses3.
The “start” directory contains all plugins which are loaded automatically and
“opt” contains plugins which only run on demand. To load such a plugin we can
use the :packadd
command. The only issue is that we don’t want to do this
manually.
#Registering plugins
Let’s name the script “ll”, because short names are cute. The script will need some way to register a plugin and then load it at an appropriate time. Its usage will look something like this:
We require it, call register
on a plugin and specify that we want to load it
once we invoke :Telescope
, and it should somehow figure out what to do that
with that information.
Inside ll.lua:
We create an autocommand that triggers whenever undefined command matching one
of the commands we specified executes and then we :packadd
.
Similar autocommands can be easily made for any event (FileType
for
example).
But what about setup functions and requiring lua modules?
#Setup functions
Most Neovim plugins follow the convention of doing require('plugin').setup()
to initialize them or override default settings. We cannot call these from our
init.lua if the plugin isn’t loaded yet. Let’s just take a setup function as
part of the options:
And back in the module’s file:
|
|
#Requiring modules
To require a module of an unloaded plugin I also keep track of modules the
plugin contains, this information is also passed as part of the register
call.
|
|
The only caveat is that now instead of using require
I have to use
ll.require
for requiring lazy loaded modules. I prefer this, because of its
explicitness, but it is possible to overwrite the global require
call to add
custom loading logic to resolve and load necessary plugins.
But I’ll leave that as an exercise for you (the reader) if you wish to use that.
#Conclusion
If you use a plugin manager that uses (Neo)vim’s native package system, and it doesn’t have lazy loading it is trivial to implement it.
You don’t need a plugin manager in fact! If you use git, (Neo)vim’s builtin mechanisms and techniques described here you can achieve most of the things a plugin manager does.
To learn how to manage plugins with plain old git you can read this great blogpost↗ by HiPhish↗.
The code in this blogpost is incomplete for the sake of brevity, if you are interested you can check out the version I use↗ which also handles filetypes, help tags and has type validation and annotations.
Chosen because of its popularity, I actually don’t recommend using a Neovim “distro” like LazyVim (either use kickstart or start from scratch). ↩︎
Obviously lazy.nvim does a lot more things than my simple module. ↩︎
Only the ones that follow this convention, lazy.nvim doesn’t actually use this and takes over the entire initialization of Neovim. ↩︎