A dead simple lazy-loading Lua library for Neovim plugins.
It is intended to be used
[!NOTE]
Should I lazy-load plugins?
It should be a plugin author's responsibility to ensure their plugin doesn't unnecessarily impact startup time, not yours!
See our "DO's and DONT's" guide for plugin developers.
Regardless, the current status quo is horrible, and some authors may not have the will or capacity to improve their plugins' startup impact.
If you find a plugin that takes too long to load, or worse, forces you to load it manually at startup with a call to a heavy
setup
function, consider opening an issue on the plugin's issue tracker.
:h autocmd-events
)FileType
events:h packpath
(:h packadd
)lz.n
provides abstractions for lazy-loading Neovim plugins,
with an API that is loosely based on lazy.nvim
,
but reduced down to the very basics required for lazy-loading only.
lz.n
is designed based on the UNIX philosophy: Do one thing well.
lazy.nvim
lz.n
is not a plugin manager, but focuses on lazy-loading only.
It is intended to be used with (or by) a plugin manager.lazy.nvim
features are out of scope:
lazy.vim
completely disables and takes over Neovim's
built-in loading mechanisms, including
adding a plugin's API (lua
, autoload
, ...)
to the runtimepath.
lz.n
doesn't.
Its only concern is plugin initialization, which is
the bulk of the startup overhead.require
.lz.n
provides a colorscheme
handler in the plugin spec.main
module and automatically calling
a setup()
function.require
.
You can use lzn-auto-require
for that.lazy.nvim
's dependencies
)
for influencing the order in which plugins are loaded.
See also: Plugin dependencies.opts
table.
lz.n
provides simple hooks that you can use to specify
when to load configurations.Neovim >= 0.10.0
You can override the function used to load plugins.
lz.n
has the following default:
vim.g.lz_n = {
---@type fun(name: string)
load = vim.cmd.packadd,
}
require("lz.n").load(plugins)
table
or a string
table
:string
: a Lua module name that contains your Plugin Spec.
See Structuring Your Plugins[!TIP]
You can call
load()
as you would calllazy.nvim
'ssetup()
. Or, you can also use it to register individual plugin specs for lazy loading.See also:
:h lz.n
[!IMPORTANT]
Since merging configs is out of scope, calling
load()
with conflicting plugin specs is not supported.
Property | Type | Description | lazy.nvim equivalent |
---|---|---|---|
[1] | string |
The plugin's name (not the module name). This is what is passed to the load(name) function. |
name [^1] |
enabled | boolean? or fun():boolean |
When false , or if the function returns false, then this plugin will not be included in the spec. |
enabled |
beforeAll | fun(lz.n.Plugin)? |
Always executed before any plugins are loaded. | init |
before | fun(lz.n.Plugin)? |
Executed before a plugin is loaded. | None |
after | fun(lz.n.Plugin)? |
Executed after a plugin is loaded. | config |
event | string? or {event?:string\|string[], pattern?:string\|string[]}\ or string[] |
Lazy-load on event. Events can be specified as BufEnter or with a pattern like BufEnter *.lua . |
event |
cmd | string? or string[] |
Lazy-load on command. | cmd |
ft | string? or string[] |
Lazy-load on filetype. | ft |
keys | string? or string[] or lz.n.KeysSpec[] |
Lazy-load on key mapping. | keys |
colorscheme | string? or string[] |
Lazy-load on colorscheme. | None. lazy.nvim lazy-loads colorschemes automatically[^2]. |
lazy | boolean? |
Lazy-load manually, e.g. using trigger_load . |
lazy |
priority | number? |
Only useful for start plugins (not lazy-loaded) to force loading certain plugins first. Default priority is 50 . |
priority |
load | fun(string)? |
Can be used to override the vim.g.lz_n.load() function for an individual plugin. |
None. |
[^1]: In contrast to lazy.nvim
's name
field, a lz.n.PluginSpec
's name
is not optional.
This is because lz.n
is not a plugin manager and needs to be told which
plugins to load.
[^2]: The reason this library doesn't lazy-load colorschemes automatically is that
it would have to know where the plugin is installed in order to determine
which plugin to load.
DeferredUIEnter
: Triggered when load()
is done and after UIEnter
.
Can be used as an event
to lazy-load plugins that are not immediately needed
for the initial UI[^3].[^3]: This is equivalent to lazy.nvim
's VeryLazy
event.
This library does not provide a lz.n.PluginSpec
field like lazy.nvim
's dependencies
.
The rationale behind this is that you shouldn't need it.
Instead, you can utilise the trigger_load
function
in a before
or after
hook.
However, we generally do not recommend this approach.
Most plugins primarily rely on the Lua libraries of other plugins,
which can be added to the :h package.path
without any noticeable
impact on startup time.
Relying on another plugin's plugin
or after/plugin
scripts is considered a bug,
as Neovim's built-in loading mechanism does not guarantee initialisation order.
Requiring users to manually call a setup
function is an anti pattern.
Forcing users to think about the order in which they load plugins that
extend or depend on each other is even worse. We strongly suggest opening
an issue or submitting a PR to fix this upstream.
However, if you're looking for a temporary workaround, you can use
trigger_load
in a before
or after
hook, or bundle the relevant plugin configurations.
[!NOTE]
This does not work with plugins that rely on
after/plugin
, such as many nvim-cmp sources, because Neovim's:h packadd
does not sourceafter/plugin
scripts after startup has completed. We recommend bundling such plugins with their extensions, or sourcing theafter
scripts manually. In the spirit of the UNIX philosophy,lz.n
does not provide any functions for sourcing plugin scripts. For sourcingafter/plugin
directories manually, you can usertp.nvim
. Here is an example.Why not provide a
dependencies
field for plugins that don't adhere to best practices? Because it's unnecessary. By using thebefore
andafter
hooks, you gain full control over when to load another plugin, without cluttering the API.[!TIP]
We recommend care.nvim as a modern alternative to nvim-cmp.
require("lz.n").load {
{
"neo-tree.nvim",
keys = {
-- Create a key mapping and lazy-load when it is used
{ "<leader>ft", "<CMD>Neotree toggle<CR>", desc = "NeoTree toggle" },
},
after = function()
require("neo-tree").setup()
end,
},
{
"crates.nvim",
-- lazy-load when opening a toml file
ft = "toml",
},
{
"sweetie.nvim",
-- lazy-load when setting the `sweetie` colorscheme
colorscheme = "sweetie",
},
{
"vim-startuptime",
cmd = "StartupTime",
before = function()
-- Configuration for plugins that don't force you to call a `setup` function
-- for initialization should typically go in a `before`
--- or `beforeAll` function.
vim.g.startuptime_tries = 10
end,
},
{
"care.nvim",
-- load care.nvim on InsertEnter
event = "InsertEnter",
},
{
"dial.nvim",
-- lazy-load on keys. -- Mode is `n` by default.
keys = { "<C-a>", { "<C-x>", mode = "n" } },
},
}
As is the case with lazy.nvim
, you can also split your plugin specs
into multiple files.
Instead of passing a spec table to load()
, you can use a Lua module.
The function will merge specs from the module and any top-level sub-modules
together in the final spec, so it is not needed to add require
calls
in your main plugin file to the other files.
Example:
~/.config/nvim/init.lua
require("lz.n").load("plugins")
~/.config/nvim/lua/plugins.lua
or ~/.config/nvim/lua/plugins/init.lua
(this file is optional)return {
{ "sweetie.nvim" },
{ "telescope.nvim", cmd = "Telescope" },
}
lz.n
will automatically merge any Lua file in ~/.config/nvim/lua/plugins/*.lua
with the main plugin spec[^4].[^4]: It does not merge multiple specs for the same plugin from different files.
Example structure:
── nvim
├── lua
│ └── plugins # Your plugin specs go here.
│ └── init.lua # Optional top-level module returning a list of specs
│ └── neorg.lua # Single spec
│ └── telescope/init.lua # Single spec
├── init.lua
Or
── nvim
├── lua
│ └── plugins.lua # Optional top-level module returning a list of specs
├── init.lua
You may register your own handlers to lazy-load plugins via other triggers not already covered by the plugin spec.
You should register all handlers before calling require('lz.n').load
,
because they will not be retroactively applied to
the load
calls that occur before they are registered.
The register_handler
function returns a boolean
that indicates success.
---@param handler lz.n.Handler
---@return boolean success
require("lz.n").register_handler(handler)
lz.n.Handler
Property | Type | Description |
---|---|---|
spec_field | string |
The lz.n.PluginSpec field used to configure the handler |
parse | fun(plugin: lz.n.Plugin, spec: unknown)? |
Parse a spec and add it to the passed in plugin |
add | fun(plugin: lz.n.Plugin) |
Adds a plugin to the handler |
del | fun(name: string) |
Removes a plugin from the handler by name |
lookup | fun(name: string): lz.n.Plugin? |
Lookup a plugin managed by this handler by name |
post_load | fun()? |
Ran once after each require('lz.n').load call, for handlers to create custom triggers such as the event handler's DeferredUIEnter event |
To manage handler state safely, ensuring trigger_load
can be invoked from
within a plugin's hooks, it is recommended to use
the :h lz.n.handler.state
module.
[!TIP]
For some examples, look at
The following Lua functions are part of the public API.
[!WARNING]
If you use internal functions or modules that are not listed here, things may break without a major version bump.
trigger_load
You can manually load a plugin and run its associated hooks
using the trigger_load
function:
---@overload fun(plugin: lz.n.Plugin | lz.n.Plugin[])
---@overload fun(plugin_name: string | string[], opts: lz.n.lookup.Opts): string[]
require('lz.n').trigger_load
The function provides two overloads, each suited for different use cases:
trigger_load(plugin: lz.n.Plugin)
lz.n.Handler
lz.n.Handler
instances to maintain referential transparency.
Each handler has full authority over its internal state, ensuring it
remains isolated and unaffected by external influences[^5],
thereby preventing multiple sources of truth.trigger_load(plugin_name: string | string[], opts?: lz.n.lookup.Opts)
before
or after
hooks.lookup
functions
to identify an appropriate plugin, and returns the first match.
You can fine-tune the search process by providing a lz.n.lookup.Opts
table.[^5]: Until the handler is instructed to stop tracking a loaded plugin via its del
function.
lookup
To lookup a plugin that is pending to be loaded by name, use:
---@type fun(name: string, opts: lz.n.lookup.Opts):lz.n.Plugin?
require('lz.n').lookup
The lookup, as well as trigger_load(string|string[])
can be
fine-tuned with a lz.n.lookup.Opts
table:
---@class lz.n.lookup.Opts
---
--- The handlers to include in the search (filtered by `spec_field`)
--- In case of multiple filters, the order of the filter list
--- determines the order in which handlers' `lookup` functions are called.
---@field filter string | string[]
All contributions are welcome! See CONTRIBUTING.md.
This library is licensed according to GPL version 2 or (at your option) any later version.
[luarocks-shield]: https://img.shields.io/luarocks/v/neorocks/lz.n?logo=lua&color=purple&style=for-the-badge