Modules
Organize Lua code into reusable modules. Essential for structured Neovim configurations.
require
Basic Usage
-- Load module
local utils = require("utils")
local json = require("json")
-- Module returns a table, function, or value
local result = require("mymodule")
-- Modules are cached
local a = require("utils") -- Loads and caches
local b = require("utils") -- Returns cached version
print(a == b) -- true
Module Search Path
-- Lua searches package.path for modules
print(package.path)
-- ./?.lua;./?/init.lua;/usr/share/lua/5.4/?.lua;...
-- ? is replaced with module name
-- require("mymodule") searches:
-- ./mymodule.lua
-- ./mymodule/init.lua
-- /usr/share/lua/5.4/mymodule.lua
-- ...
-- Add custom path
package.path = package.path .. ";/my/custom/?.lua"
-- C modules use package.cpath
print(package.cpath)
Neovim Module Path
-- Neovim adds these to package.path:
-- ~/.config/nvim/lua/?.lua
-- ~/.config/nvim/lua/?/init.lua
-- So require("mymodule") finds:
-- ~/.config/nvim/lua/mymodule.lua
-- ~/.config/nvim/lua/mymodule/init.lua
-- Nested modules use dots
require("plugins.telescope") -- lua/plugins/telescope.lua
require("config.keymaps") -- lua/config/keymaps.lua
Creating Modules
Basic Module Pattern
-- lua/utils.lua
local M = {}
function M.validate_ip(ip)
local pattern = "^%d+%.%d+%.%d+%.%d+$"
return string.match(ip, pattern) ~= nil
end
function M.validate_mac(mac)
local pattern = "^%x%x:%x%x:%x%x:%x%x:%x%x:%x%x$"
return string.match(mac, pattern) ~= nil
end
-- Private function (local, not in M)
local function internal_helper()
return "internal"
end
return M
-- Usage:
-- local utils = require("utils")
-- utils.validate_ip("10.50.1.20")
Module with State
-- lua/logger.lua
local M = {}
-- Module state (private)
local level = "INFO"
local log_file = nil
-- Public API
function M.set_level(new_level)
level = new_level
end
function M.info(msg)
if level ~= "DEBUG" then
print("[INFO] " .. msg)
end
end
function M.debug(msg)
if level == "DEBUG" then
print("[DEBUG] " .. msg)
end
end
function M.error(msg)
print("[ERROR] " .. msg)
end
return M
Module with Setup
-- lua/myplugin.lua (Neovim plugin pattern)
local M = {}
-- Default configuration
M.config = {
enabled = true,
key = "<leader>x",
style = "minimal"
}
function M.setup(opts)
-- Merge user options with defaults
M.config = vim.tbl_deep_extend("force", M.config, opts or {})
if not M.config.enabled then
return
end
-- Setup keymaps, autocommands, etc.
vim.keymap.set("n", M.config.key, M.action, {
desc = "My plugin action"
})
end
function M.action()
print("Plugin action with style: " .. M.config.style)
end
return M
-- Usage in init.lua:
-- require("myplugin").setup({
-- key = "<leader>m",
-- style = "fancy"
-- })
Lazy Loading
-- Only load module when first used
local M = {}
local _utils = nil
local function get_utils()
if not _utils then
_utils = require("utils")
end
return _utils
end
function M.do_something()
local utils = get_utils()
return utils.validate_ip("10.50.1.20")
end
return M
-- Or use Neovim's lazy loading
local utils = setmetatable({}, {
__index = function(_, key)
return require("utils")[key]
end
})
Neovim Module Organization
Typical Structure
~/.config/nvim/ ├── init.lua # Entry point ├── lua/ │ ├── config/ │ │ ├── options.lua # vim.opt settings │ │ ├── keymaps.lua # Key mappings │ │ └── autocmds.lua # Autocommands │ ├── plugins/ │ │ ├── init.lua # Plugin manager setup │ │ ├── telescope.lua # Telescope config │ │ └── lsp.lua # LSP config │ └── utils/ │ ├── init.lua # Utility functions │ └── keymaps.lua # Keymap helpers
init.lua Entry Point
-- ~/.config/nvim/init.lua
-- Load configuration modules
require("config.options")
require("config.keymaps")
require("config.autocmds")
-- Load plugins
require("plugins")
Config Module Example
-- lua/config/options.lua
local opt = vim.opt
-- Line numbers
opt.number = true
opt.relativenumber = true
-- Tabs/indentation
opt.tabstop = 4
opt.shiftwidth = 4
opt.expandtab = true
-- Search
opt.ignorecase = true
opt.smartcase = true
-- UI
opt.termguicolors = true
opt.signcolumn = "yes"
Keymap Module
-- lua/config/keymaps.lua
local map = vim.keymap.set
-- Leader
vim.g.mapleader = " "
vim.g.maplocalleader = " "
-- Better navigation
map("n", "<C-h>", "<C-w>h", { desc = "Move to left window" })
map("n", "<C-j>", "<C-w>j", { desc = "Move to lower window" })
map("n", "<C-k>", "<C-w>k", { desc = "Move to upper window" })
map("n", "<C-l>", "<C-w>l", { desc = "Move to right window" })
-- Clear search
map("n", "<Esc>", "<cmd>nohlsearch<CR>", { desc = "Clear search" })
-- Save
map("n", "<leader>w", "<cmd>w<CR>", { desc = "Save file" })
Plugin Spec Module
-- lua/plugins/telescope.lua (for lazy.nvim)
return {
"nvim-telescope/telescope.nvim",
dependencies = { "nvim-lua/plenary.nvim" },
cmd = "Telescope",
keys = {
{ "<leader>ff", "<cmd>Telescope find_files<cr>", desc = "Find Files" },
{ "<leader>fg", "<cmd>Telescope live_grep<cr>", desc = "Live Grep" },
{ "<leader>fb", "<cmd>Telescope buffers<cr>", desc = "Buffers" },
},
opts = {
defaults = {
prompt_prefix = " ",
selection_caret = " ",
},
},
}
Module Reloading
-- Force reload module (for development)
package.loaded["mymodule"] = nil
local mymodule = require("mymodule")
-- Reload all modules matching pattern
local function reload_module(pattern)
for name in pairs(package.loaded) do
if name:match(pattern) then
package.loaded[name] = nil
end
end
end
reload_module("^config") -- Reload all config.* modules
-- In Neovim, use plenary for hot reload
-- :lua require("plenary.reload").reload_module("mymodule")
Package Patterns
Namespace Module
-- lua/myns/init.lua
local M = {}
-- Re-export submodules
M.utils = require("myns.utils")
M.config = require("myns.config")
M.api = require("myns.api")
return M
-- Usage:
-- local myns = require("myns")
-- myns.utils.helper()
-- myns.api.call()
Singleton
-- lua/database.lua
local M = {}
local instance = nil
function M.get_instance()
if not instance then
instance = {
connected = false,
data = {}
}
end
return instance
end
function M.connect()
local db = M.get_instance()
db.connected = true
end
return M
-- All requires get same instance
local db1 = require("database")
local db2 = require("database")
db1.connect()
print(db2.get_instance().connected) -- true
Next Module
Coroutines - Cooperative multitasking and async patterns.