Forest Logo
search
package_2

good-loader

By @encodedlux

Roblox

Mirrored

GoodLoader

A lightweight and useful module loader for Roblox.

Luau GitHub License

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 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: The Instance whose children will be scanned for ModuleScripts.
  • optional predicate: A function (moduleScript: ModuleScript) -> boolean. If provided, a module is only loaded if it returns true.

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: The Instance whose descendants will be scanned for ModuleScripts.
  • optional predicate: A function (moduleScript: ModuleScript) -> boolean. If provided, a module is only loaded if it returns true.

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 its ModuleScripts are loaded.
  • optional predicate: A function (moduleScript: ModuleScript) -> boolean. If provided, a module is only loaded if it returns true.

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 each ModuleScript'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 pass self, . to not pass self. 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 as callMethod.
  • 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 as callMethod.
  • signal: The RBXScriptSignal to 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 as callMethod.
  • callback: A function that receives a fire function and returns an optional cleanup function. Call fire(...) 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.

๐Ÿ”€ 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

check_circle

Safe for commercial use

Automated license review โ€” not legal advice.