Start typing to search packages!
goodloader
By @encodedlux
Roblox
MirroredGoodLoader
A lightweight and useful module loader for Roblox.
GoodLoader is a no-nonsense module loader for Roblox. Load, sort, dispatch, and bind — pick what fits your architecture and leave the rest.
Features
- 📦 Module Loading — Load modules from multiple paths with optional filtering, priority sorting, and dependency ordering — all in a single call.
- 📣 Method Dispatch — Call methods across all loaded modules sequentially or concurrently.
- 🔗 Event Binding — Bind module methods to signals or custom event sources.
- 🗂️ Module Registry — Register and retrieve module lists by ID for cross-system access.
Installation
GoodLoader is easy to install! You can copy the source code or install via Wally:
[dependencies]
GoodLoader = "encodedlux/goodloader@1.1.0"
Getting Started
First, create a loader script that will be the entry point for GoodLoader.
-- replace with path to GoodLoader
local GoodLoader = require(...)
-- Optional: set the name field for memory profiling.
GoodLoader.getModuleNameField = function(module)
return module.name
end
local modules = GoodLoader:loadModules({
paths = { script.Parent.Services:GetChildren() },
filter = GoodLoader:matchesName("Service$"),
getPriorityField = function(module) return module.priority end,
getDependenciesField = function(module) return module.dependencies end,
})
GoodLoader:registerModules(modules, "Game")
GoodLoader:callMethod(modules, ":Init")
GoodLoader:spawnMethod(modules, ":Start")
Now, create modules in the Services folder.
local MyService = {
name = "MyService",
dependencies = { OtherService } -- OtherService will be loaded before MyService.
}
function MyService.Init(self: Self)
-- Initialize your properties and everything that other modules may depend on here.
-- For example, initialize variables, set up event connections, etc.
end
function MyService.Start(self: Self)
-- Run your module logic here.
-- For example, start a loop.
end
type Self = typeof(MyService)
return MyService
Module Loading
GoodLoader:loadModules(params)
Loads and requires all ModuleScripts from the given paths, returning the resulting module tables in a sorted order. Duplicate ModuleScripts across paths are automatically deduplicated.
At least one of paths or modules must be provided, otherwise an error is thrown.
local modules = GoodLoader:loadModules({
paths = { script.Parent.Services:GetChildren() },
modules = { otherModule },
filter = GoodLoader:matchesName("Service$"),
getPriorityField = function(module) return module.priority end,
getDependenciesField = function(module) return module.dependencies end,
})
Parameters (LoadParams)
- optional
paths: A list of instance lists to scan (e.g.{ folder:GetChildren() }). OnlyModuleScriptinstances are loaded. - optional
modules: A list of already-loaded module tables to include alongside the ones discovered frompaths. Useful for injecting modules that don't live under the scanned paths. - optional
filter: A function(moduleScript: ModuleScript) -> booleanevaluated before requiring. Modules that don't pass the filter are never loaded. - optional
getPriorityField: A function(module) -> number?that returns the priority of a module. Lower values are loaded first; modules without a priority go last. - optional
getDependenciesField: A function(module) -> { module }?that returns the dependencies of a module. When provided, modules are sorted in topological (dependency) order.
Note: When both
getPriorityFieldandgetDependenciesFieldare provided, the priority sort runs first and the topological sort preserves that order as a tie-breaker for modules at the same dependency depth.
Returns
A table of required modules, sorted according to the provided sorting options.
GoodLoader:matchesName(pattern)
A utility that creates a name-matching filter to use with loadModules.
local modules = GoodLoader:loadModules({
paths = { script.Parent.Services:GetChildren() },
filter = GoodLoader:matchesName("Service$"),
})
Parameters
pattern: A Lua pattern string matched against eachModuleScript's name.
Returns
A filter function (moduleScript: ModuleScript) -> boolean.
Method Dispatch
GoodLoader:callMethod(modules, methodName, ...)
Calls methodName sequentially on all modules. Use for initialization steps where order matters.
GoodLoader:callMethod(modules, ":init") -- passes self as first argument
GoodLoader:callMethod(modules, ".init") -- does not pass self
GoodLoader:callMethod(modules, "init") -- same as ":init"
Parameters
modules: The table of modules to dispatch to.methodName: The method to call. Prefix with:to passself,.to not passself. Defaults to:behavior.- optional
...: Additional arguments forwarded to the method.
GoodLoader:spawnMethod(modules, methodName, ...)
Calls methodName concurrently on all modules. Use when modules can run in parallel.
GoodLoader:spawnMethod(modules, ":start")
GoodLoader:spawnMethod(modules, ".start")
GoodLoader:spawnMethod(modules, "start")
Parameters
modules: The table of modules to dispatch to.methodName: The method to call. Same prefix syntax ascallMethod.- optional
...: Additional arguments forwarded to the method.
Event Binding
GoodLoader:bindToSignal(modules, method, signal)
Fires method on all modules whenever signal fires, passing along its arguments.
local disconnect = GoodLoader:bindToSignal(modules, "onHeartbeat", RunService.Heartbeat)
-- later
disconnect()
Parameters
modules: The table of modules to dispatch to.method: The method to call on each module. Supports the same prefix syntax ascallMethod.signal: TheRBXScriptSignalto listen to.
Returns
A cleanup function that disconnects the signal when called.
GoodLoader:bindToCallback(modules, method, callback)
Binds method to a custom event source. Useful for backfilling existing state (e.g. players already in the game).
local disconnect = GoodLoader:bindToCallback(modules, "onPlayerAdded", function(fire)
local conn = Players.PlayerAdded:Connect(fire)
for _, player in Players:GetPlayers() do
fire(player) -- backfill existing players
end
return function()
conn:Disconnect()
end
end)
-- later
disconnect()
Parameters
modules: The table of modules to dispatch to.method: The method to call on each module. Supports the same prefix syntax ascallMethod.callback: A function that receives afirefunction and returns an optional cleanup function. Callfire(...)to dispatch the method across all modules.
Returns
A cleanup function that runs the callback's cleanup when called.
Module Registry
GoodLoader:registerModules(modules, id)
Associates a list of modules with a unique string ID so it can be retrieved anywhere with getModules.
local unregister = GoodLoader:registerModules(modules, "Game")
-- later
unregister()
Parameters
modules: The table of modules to register.id: A unique string identifier.
Returns
A function that unregisters the modules when called.
GoodLoader:getModules(id)
Retrieves a previously registered module list by ID.
local modules = GoodLoader:getModules("Game")
GoodLoader:spawnMethod(modules, ":doSomething")
Parameters
id: The string identifier used when registering.
Returns
The registered table of modules.
GoodLoader:unregisterModules(id)
Removes a module list from the registry.
GoodLoader:unregisterModules("Game")
Parameters
id: The string identifier to remove.
Memory Profiling
GoodLoader.getModuleNameField
An optional callback that GoodLoader uses to read a module's name for debug.setmemorycategory. Set this to enable per-module memory profiling in the Roblox Developer Console.
GoodLoader.getModuleNameField = function(module)
return module.name
end
If not set (defaults to nil), memory categories are not modified during dispatch.
Migrating from Knit
Still on Knit and looking for something more current? GoodLoader is a drop-in replacement for the loading layer — and the best part is you don't need to rewrite your services or controllers at all.
Knit services already have KnitInit, KnitStart, and a Name field. Just point callMethod and spawnMethod at the methods you already have.
Before (Knit):
Knit.Start():andThen(function()
print("Knit started!")
end)
After (GoodLoader):
GoodLoader.getModuleNameField = function(module)
return module.Name
end
local modules = GoodLoader:loadModules({
paths = { script.Parent.Services:GetChildren() },
filter = GoodLoader:matchesName("Service$")
})
GoodLoader:callMethod(modules, ":KnitInit")
GoodLoader:spawnMethod(modules, ":KnitStart")
print("Services started!")
Your services stay exactly as they are:
-- No changes needed to existing services
local MyService = { Name = "MyService" }
function MyService:KnitInit()
print(self.Name, "initialized!")
end
function MyService:KnitStart()
print(self.Name, "started!")
end
return MyService
From there you can adopt GoodLoader features gradually — add a priority field to control load order, declare dependencies for topological sorting, or bind signals with bindToSignal. None of it is required up front.
Made by EncodedLux
Package Details
Install command (Click to copy)
Version
1.1.0
License
MIT
Safe for commercial use
Automated license review — not legal advice.
