How to correctly use “dependencies” with lazy.nvim

Yasushi Jinnouchi - Sep 15 - - Dev Community

🔔 Note: The following is an addition.

The original title was Don't use “dependencies” in lazy.nvim. But I get pointed out that it is a mistake and makes users misunderstanding about lazy.nvim. So changed to more accurate one.

🔔 End of addition.

lazy.nvim is the most popular plugin manager of Neovim. Typical Neovim distributions, such as LazyVim or NvChad, use lazy.nvim in base, so many users use lazy.nvim without knowing it. Here I wrote one point advice to use lazy.nvim.

Load plugins lazily with lazy.nvim

Usually Neovim plugins are loaded in starting Neovim itself. The time for starting Neovim should become larger and larger when you add more and more plugins. So there is “lazy loading” to solve this. “lazy”.nvim has many features about lazy loading.

For example, telescope.nvim, that is also one of the most popular plugins, has a note in README that describes the way to use with lazy.nvim.

{
  "nvim-telescope/telescope.nvim",
  dependencies = { "nvim-lua/plenary.nvim" },
}
Enter fullscreen mode Exit fullscreen mode

This code means that it should install telescope.nvim from GitHub and add a dependency to plenary.nvim. With this, Neovim loads plugins at the startup time without lazy loading.

If you want to load plugins lazily, you need to specify chances to load them. There are some ways to do this, so I illustrate one way below.

{
  "nvim-telescope/telescope.nvim",
  cmd = { "Telescope" },
  dependencies = { "nvim-lua/plenary.nvim" },
}
Enter fullscreen mode Exit fullscreen mode

Neovim doesn't load telescope.nvim at its startup but loads as soon as you call :Telescope command at the first time, such as :Telescope find_files.

When Neovim loads dependencies in lazy loading?

The example above shows that telescope.nvim is loaded after you call :Telescope command. Then, when plenary.nvim, that telescope.nvim depends on, have been loaded?

The answer is “the time Neovim started at”. It is ideal that minimum plugins are loaded in startup, and others are loaded when they are needed. But dependencies option interferes with it.

🔔 Note: The following is an addition.

Sorry. This sentence above have misunderstanding about dependencies. Plugins in dependencies are not loaded in Neovim startup. lazy.nvim makes them (such as plenary.nvim) be loaded just before the dependent plugin (telescope.nvim).

🔔 End of addition.

You can write settings below to load plenary.nvim relevantly to telescope.nvim.

{ "nvim-telescope/telescope.nvim", cmd = { "Telescope" } },
{ "nvim-lua/plenary.nvim", lazy = true },
Enter fullscreen mode Exit fullscreen mode

lazy = true means that you set no chance to load the plugin explicitly, but it should be loaded as soon as it become needed. lazy.nvim detects the plugin to be needed when it does require "plenary".

  • This works with modules placed deeper, such as require "plenary.log".
  • This logic is achieved by hacking package.loaders in Lua core. I cannot write here the detail because it's lengthy.

That is, lazy.nvim works as below when :Telescope find_files.

  1. You type :Telescope find_files.
  2. lazy.nvim loads telescope.nvim.
  3. It reaches require "plenary.***".
  4. lazy.nvim loads plenary.nvim.
  5. It continues the process and shows UI for :Telescope find_files.

We need “dependencies”?

I think no. It is meaningless that defines dependencies because lazy.nvim loads plugins automatically when they become needed.

{
  "nvim-telescope/telescope.nvim",
  cmd = { "Telescope" }
  dependencies = {
    { "nvim-lua/plenary.nvim", lazy = true },
  },
}
Enter fullscreen mode Exit fullscreen mode

You even think this code above works good, but this is wrong. You may think it loads when the plugin become needed (when it sees require "plenary.***"). In fact, lazy.nvim loads plenary.nvim before telescope.nvim.

  1. You type :Telescope find_files.
  2. lazy.nvim loads plenary.nvim because it is in dependencies. It is not loaded in Neovim startup because of lazy = true.
  3. lazy.nvim loads telescope.nvim.
  4. It continues the process and show windows for :Telecope find_files.

Yes, this may be difficult to understand. Surely it loads plenary.nvim not in startup. But I want to load it more lazily.

🔔 Note: The following is an addition.

This section below is based on this page. But this is not for general users but for plugin developers writing package spec, so I've commented out it.

🔔 End of addition.

Official doc also says “Don't use dependencies”

This is not a special technique. The document of lazy.nvim also says that.

🔥 Developers | lazy.nvim

Only use dependencies if a plugin needs the dep to be installed AND loaded. Lua plugins/libraries are automatically loaded when they are require()d, so they don't need to be in dependencies.

It is not easy to find out that “if a plugin needs the dep to be installed AND loaded”. An example for this is the case when there is an initialization script in plugin directory of the plugin, and the initialization process is needed by the dependent plugin to work.

……is this difficult? Then I say again in other words.

Definitely, there is no such plugin written with Lua. So you DON'T need to use dependencies.

Why plugin authors/users still use dependencies?

Even so, some expert users or many popular plugins such as telescope.nvim uses dependencies in README or their codes. Why?

To write “dependencies” (literally)

The dependencies option is the only one that users can describe plugins' dependency in lazy.nvim. So some expert users (ayamir/nvimdots, for example) uses dependencies even if they know warnings from the official lazy.nvim document.

To reduce inquiries

Lazy loading is so difficult, basically. Many inquiries that say I can't see this plugin to be loaded are caused by lazy loading.

README should have a config that everyone can work with it. The dependencies option in it can make even newbie users load all plugins certainly.

For Vim Script plugins

lazy.nvim's automatic loading feature (lazy = true) can work only with plugins written with Lua. You should use dependencies option for ones written with Vim Script.

Really needed by the plugin

This is the case “if a plugin needs the dep to be installed AND loaded” mentioned above. This is an extremely rare pattern.

Even if you need an initialization for the dependency, you can write configs below.

{ "foo/bar.nvim", cmd = { "Bar" } },
{ "hoge/fuga.nvim", lazy = true, opts = {} },
Enter fullscreen mode Exit fullscreen mode

Suppose bar.nvim has a code like require "fuga" in it. Then it will work as below.

  1. You type :Bar.
  2. lazy.nvim loads bar.nvim.
  3. It reaches require "fuga".
  4. lazy.nvim loads fuga.nvim.
  5. It calls require("fuga").setup {}. This is an effect by opts option.
  6. require "fuga" works well, then it continues :Bar.

The initialization for fuga.nvim is properly executed even in such cases. If still you need dependencies, it is the case when you need require("fuga").setup {} to be executed before it reaches require "fuga" in bar.nvim. You usually don't want to create such “dependencies”.

Some complex examples with popular plugins

As you know, lazy loading is too complex to understand. Here I illustrate some examples.

telescope.nvim extensions

Many telescope.nvim users are sure to use extensions. For example, you can write configs with telescope-project.nvim.

{ "nvim-telescope/telescope-project.nvim", lazy = true },
{ "nvim-lua/plenary.nvim", lazy = true },

{
  "nvim-telescope/telescope.nvim",
  cmd = { "Telescope" },
  opts = {
    extensions = {
      project = { theme = "dropdown" },
    },
  },
},
Enter fullscreen mode Exit fullscreen mode

This code works as below.

  1. You type :Telescope project.
  2. lazy.nvim loads telescope.nvim.
  3. It does require("telescope').setup { extensions = { …… } }.
  4. It does require("telescope").load_extension "project" (here) because a picker named “project” has not been loaded.
  5. It does require("telescope._extensions.project") (here).
  6. lazy.nvim loads telescope-project.nvim.
  7. lazy.nvim does the initialization for telescope-project.nvim (require("telescope._extensions.project").setup { theme = "dropdown" }) (here).
  8. It continues the process for :Telescope project.

Hmm…… It's so complex.

nvim-cmp and its sources

You should use many source plugins to use nvim-cmp effectively. The orders to load them lazily are difficult a bit to understand.

{ "hrsh7th/cmp-buffer", event = { "InsertEnter" } },
{ "mtoohey31/cmp-fish", ft = { "fish" } },
{
  "hrsh7th/nvim-cmp",
  lazy = true,
  opts = { …… },
},
Enter fullscreen mode Exit fullscreen mode

This code above is a config to load nvim-cmp and its sources lazily. When you start the Insert mode at the first time, lazy.nvim works as below.

  1. You start to input any character.
  2. Neovim invokes InsertEnter event.
  3. lazy.nvim loads cmp-buffer.
  4. It sources after/plugin/cmp_buffer.lua in cmp-buffer.
  5. It reaches require("cmp").register_source(……).
  6. lazy.nvim loads nvim-cmp.
  7. lazy.nvim does the initialization of nvim-cmp: require("cmp").setup { …… }. This is an effect by opts option.
  8. It continues the process for require("cmp").register_source().
  9. Now nvim-cmp does the code completion as its normal way.

This config also adds cmp-fish. This source is needed only in fish scripts, so it is delayed to be loaded until you open fish scripts.

  1. You open a fish script.
  2. Neovim invokes FileType event.
  3. lazy.nvim loads cmp-fish.
  4. (This and latter ones are almost the same as cmp-buffer)

Complex, but easier than the case with telescope.nvim extensions?

Conclusion

This article explained the way lazy.nvim deals with dependencies option and correct places to use it. Lazy loading is a challenging matter in using Neovim plugins. You can learn Neovim's startup mechanism deeper when you are hacking with lazy.nvim.

Bonus Content

.
Terabox Video Player