Start typing to search packages!
good-loader
By @encodedlux
Roblox
MirroredGoodLoader 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 children, descendants, or multiple paths with optional filtering.
- ๐ข Sorting โ Sort modules by priority or dependency order.
- ๐ฃ Method Dispatch โ Call methods across all loaded modules.
- ๐ Event Binding โ Bind module methods to
RBXScriptSignals 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/good-loader@VERSION"
Getting Started
First, create a loader script that will be the entry point for GoodLoader.
-- replace with path to GoodLoader
local GoodLoader = require(...)
-- The name field will be used for memory profiling.
-- It's optional, but highly recommended to use.
GoodLoader:setFields({ name = "name" })
local modules = GoodLoader:loadDescendants(script.Parent:WaitForChild("Services"), GoodLoader.matchesName("Service$"))
GoodLoader:prioritySort(modules, function(m)
return m.priority
end)
GoodLoader:topoSort(modules, function(m)
return m.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:loadChildren(parent, predicate?)
Loads all ModuleScript children from a given Instance.
-- Load all ModuleScripts:
local modules = GoodLoader:loadChildren(script.Parent)
-- Load only modules whose name ends with "Service":
local modules = GoodLoader:loadChildren(script.Parent, function(moduleScript)
return moduleScript.Name:match("Service$") ~= nil
end)
Parameters
parent: TheInstancewhose children will be scanned forModuleScripts.- optional
predicate: A function(moduleScript: ModuleScript) -> boolean. If provided, a module is only loaded if it returnstrue.
Returns
A table of required modules.
GoodLoader:loadDescendants(parent, predicate?)
Loads all ModuleScript descendants from a given Instance.
-- Load all ModuleScripts:
local modules = GoodLoader:loadDescendants(script.Parent)
-- Load only modules whose name ends with "Service":
local modules = GoodLoader:loadDescendants(script.Parent, function(moduleScript)
return moduleScript.Name:match("Service$") ~= nil
end)
Parameters
parent: TheInstancewhose descendants will be scanned forModuleScripts.- optional
predicate: A function(moduleScript: ModuleScript) -> boolean. If provided, a module is only loaded if it returnstrue.
Returns
A table of required modules.
GoodLoader:loadPaths(paths, predicate?)
Loads modules from multiple lists of instances at once.
local modules = GoodLoader:loadPaths({
script.Parent:GetChildren(),
ReplicatedStorage.Shared.Modules:GetChildren(),
})
Parameters
paths: An array of instance lists (e.g. from:GetChildren()). Each list is iterated and itsModuleScripts are loaded.- optional
predicate: A function(moduleScript: ModuleScript) -> boolean. If provided, a module is only loaded if it returnstrue.
Returns
A table of required modules.
GoodLoader.matchesName(pattern)
A utility that creates a name-matching predicate to use with the loading functions.
local modules = GoodLoader:loadDescendants(script.Parent, GoodLoader.matchesName("Service$"))
Parameters
pattern: A Lua pattern string matched against eachModuleScript's name.
Returns
A predicate function (moduleScript: ModuleScript) -> boolean.
๐ข Sorting
GoodLoader:prioritySort(modules, getPriority)
Sorts modules by priority in ascending order (1,2,3...).
GoodLoader:prioritySort(modules, function(module)
return module.priority
end)
Parameters
modules: The table of modules to sort. Sorted in-place.getPriority: A function(module) -> number?that returns the priority of a module.
GoodLoader:topoSort(modules, getDependencies)
Sorts modules by topological dependency order. A module that another depends on will always load first.
GoodLoader:topoSort(modules, function(module)
return module.dependencies
end)
Parameters
modules: The table of modules to sort. Sorted in-place.getDependencies: A function(module) -> { module }?that returns the dependencies of a module.
๐ฃ 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 via task.spawn. 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.
โ๏ธ Field Setup
GoodLoader:setFields(fields)
Configures the field names GoodLoader reads from modules. Currently only name is supported, which is used for memory profiling. Defaults to "Name".
GoodLoader:setFields({ name = "name" })
Parameters
fields: A table with optional keys:- optional
name: The field GoodLoader reads to identify a module by name.
- optional
๐ 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. GoodLoader reads Name by default, so just point callMethod and spawnMethod at the methods you already have.
Before (Knit):
Knit.Start():andThen(function()
print("Knit started!")
end)
After (GoodLoader):
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
0.1.0
License
MIT
Safe for commercial use
Automated license review โ not legal advice.
