Free use Module Loader

Module loaders are a very powerful resource that most newer developers are unaware of. Here is a simple one I made using lemonSignal. You can either use rojo with wally to install, or just get the lemonSignal module from the data-orient-house github here and redirect the require to the right place. Anyway, here is a module loader that works well for most projects:

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local RunService = game:GetService("RunService")
local ServerScriptService = game:GetService("ServerScriptService")

-- [[IMPORTANT]] If you arent using rojo/wally, you will need to install this manually
local lemonsignal = require(ReplicatedStorage.Packages.lemonsignal)

local _isServer = RunService:IsServer()
local _inst

-- [[IMPORTANT]] Make sure this directory points to your client and server modules
local _root = _isServer and Players.LocalPlayer.PlayerScripts.Controllers or ServerScriptService.Services

local modules: {[string]: any}

local finishedLoading = false
local finishedSignal = lemonsignal.new()

local Private = {}
local ModuleLoader = {}
ModuleLoader.__index = ModuleLoader

function Get()
    if not _inst then
        _inst = setmetatable({}, ModuleLoader)

        Private._RequireModules()
    end

    return _inst
end

function ModuleLoader.Import(moduleIndex: string)
    if modules[moduleIndex] and finishedLoading then
        return modules[moduleIndex]
    else
        local running = coroutine.running()
        local _timeoutThread
        local _conn
        
        _timeoutThread = task.delay(10, function()
            coroutine.resume(running)
        end)

        _conn = finishedSignal:Connect(function()
            coroutine.resume(running)
        end)

        coroutine.yield(running)

        coroutine.close(_timeoutThread)
        _conn:Disconnect()

        return modules[moduleIndex]
    end
end

function Private._SetupModules(bulk: {[string]: any})
    for _index: string, contents: any in bulk do
        if contents.Init then
            contents:Init()
        end
    end

    for _index: string, contents: any in bulk do
        if contents.Start then
            task.defer(contents.Start, contents)
        end
    end
    modules = bulk

    task.wait()

    finishedLoading = true
    finishedSignal:Fire()
end

function Private._RequireModules()
    local unloadedModules = _root:GetChildren()
    local toSetup = {}

    for _, module: ModuleScript in unloadedModules do
        if module.ClassName ~= "ModuleScript" then
            continue
        end
        toSetup[module.Name] = require(module)
    end

    Private._SetupModules(toSetup)
end

return Get

To use this, just put the module in replicated storage and have a local and server script require it from PlayerScripts or ServerScriptService. Alternatively, if your not on rojo, you can put those scripts under the module in replicated storage and set their run context to client and server. If you end up doing that instead, the runcontext property only exists on server scripts but you can change the client on to run on the client
Explorer


For using them in script, theres 2 phases you need to understand in order to use this module loader effectively: the initializing phase, and the start phase. The initializing phase focusing on seting up the module and making sure everything other scripts would use is available. For example, if you wanted to have a signal in your module that fires when a player gets a point, you could do something like this:

PointService:

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local lemonsignal = require(path.to.lemonSignal)

local Private = {}
local PointService = {}
PointService.__index = PointService

function PointService:Init()
    PointService.PointsAwared = lemonsignal.new()
end

function PointService:AwardPoints(player: Player, points: number)
    PointService.PointsAwared:Fire(player, points)
    print(`Player: {player} got {points} points!`)
end

return setmetatable({}, PointService)

AnotherService:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ModuleLoader = require(path.to.ModuleLoader)

local PointService = ModuleLoader.Import("PointService")

local Private = {}
local AnotherService = {}
AnotherService .__index = AnotherService 

function AnotherService:Init()
    -- Dont connect here, as it creates a race condition.
    -- Connecting in the Start function is ideal as it will be called after the
    -- init function, where the connection is made, eliminating race conditions entirely.
end

function AnotherService:Start()
    PointService.PointsAwarded:Connect(function(plr: Player, points: number)
        print(`AnotherService knows that player: {plr} was awarded: {points} points!`)
    end)
end

return setmetatable({}, AnotherService)

If you have any concerns or questions, reply here or talk to me on discord: Sjdinsd.

7 Likes

Hey, thanks for sharing this!
It looks like you’re trying to offer a cleaner approach to module management, especially with the way you separate the Init and Start phases. That’s a great practice and definitely something newer developers can benefit from. I appreciate that you included code samples and a working example. that’s more than a lot of people post.

That said, if the goal is to make this a true community resource, I’d recommend a few improvements to make it easier to follow, especially for beginners:

  1. Add a clear introduction:
    Start with a short paragraph explaining what a module loader is, why it’s helpful, and who the post is for. Right now, the post jumps into the code too quickly without setting the stage.

  2. Explain dependencies like lemonSignal:
    Not everyone knows what it is. Just one or two lines explaining what it does and why you chose it would go a long way.

  3. Include a step-by-step setup guide:
    Even a numbered list or a short bullet-point guide would help beginners understand how to install and use your loader in their own projects.

  4. Clarify how Init/Start phases work:
    I like that you separated them. maybe add a short note on when each phase is called and why it matters.

  5. Use consistent formatting and comments:
    Consider cleaning up the formatting a bit, adding more inline comments, and breaking up large blocks of code. It makes it more readable and easier to follow.

Overall, the idea is solid and the effort is appreciated. With just a bit more structure and explanation, this could be a really valuable tool for the community.

3 Likes

There’s no explanation for any of it. No intro, no reason why you’d use this, no basic setup steps, not even a sentence to define what lemonSignal is or how it works
— ModuleLoader
Module loaders are a very powerful resource that most newer developers are unaware of. Here is a simple one I made using lemonSignal. You can either use rojo with wally to install, or just get the lemonSignal module from the data-orient-house github here and redirect the require to the right place. Anyway, here is a module loader that works well for most projects:

— no basic setup steps
To use this, just put the module in replicated storage and have a local and server script require it from PlayerScripts or ServerScriptService. Alternatively, if your not on rojo, you can put those scripts under the module in replicated storage and set their run context to client and server. If you end up doing that instead, the runcontext property only exists on server scripts but you can change the client on to run on the client

not even a sentence to define what lemonSignal is or how it works

You saw a full module loader explaining two phases, example of usage, and a literal code example, and yet your decision was to report it for spam cause you didn’t understand it? Everything is a search away, the more they engage the better they learn. If this post simply didn’t suit you, it’s better to just move on rather than reporting it as spam because you didn’t understand what the OP gave.

2 Likes

image

Write in your own words. Don’t rely on AI, even if I agree with the points made it makes your message less credible :slight_smile:

1 Like

That is strange. I am one to take accountability, but I wrote every word of that. Ill check it myself, but its weird that is popping up.

I used GPTZero aswell, its saying its entirely human. Sorry if it sounds like AI, but I wouldn’t post a community resource made from AI. Im better than that as a developer.

AI detectors only detect if you write proper English.
They flag the USA’s Constitution and Declaration of Independence as AI, and I’m sure that AI didn’t exist back then.

Do not use AI detectors – especially when uncalled for.

2 Likes

Yes, I understand that.

His reply read like someone trying to be overly formal, but to me it didn’t read like a human. I sometimes get flagged by AI detectors, but I don’t write like that. It has the hallmarks of AI-generated text. Also IIRC ClassicDev has used AI for (at least assist in) writing topics and such in the past.

I only used GPTZero to provide more evidence than just “this sounds like ai,” and I do 100% agree that accurate AI detection is impossible. People who think it is possible are wrong lol

Oh, sorry. Should’ve been clearer—was responding to ClassicDev. Also, if you look it says it’s confident it’s human, not AI.

3 Likes

In the top-right corner of messages it says who is being responded to.


This means he wasn’t talking about your post, but rather ClassicDev’s feedback on your resource.

1 Like

Didn’t see that. Thats my bad.

1 Like

What does a module loader do? Why should I use this? Doesn’t Roblox already load the module when you use it?

The module loader is good for organizing singletons and other modules alike. In a more understandable approach. The module loader keeps track and holds all the modules for you so you can import them without requiring them everytime. With that, the module loader promotes what is called a “singleton”, this is essentially using the module script functionality, but it acts like a normal script/local script.

The module loader doesnt just load singletons, but again any module. Here is an example of how Im using my module loader right now for a local module (dont worry about the packet or lemonsignal modules, they are irrelevant):

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Packet = require(ReplicatedStorage.Shared.Packet)

local countdownPacket = Packet("Countdown", Packet.NumberU16):Response()

-- I set my public, and object tables to return to the module loader
-- I also set a private table for functions I dont want the module loader to have
local Private = {}
local Public = {}
local Countdown = {}
Countdown.__index = Countdown

-- Here i currently only use :Start(), so theres no reason to have an :Init()
-- function Countdown:Init() end
function Countdown:Start()
	-- Connecting to the server signal to recieve events.
	countdownPacket.OnClientEvent:Connect(Countdown.StartCountdown)
end

function Public.StartCountdown(num: number)
	-- Whenever the server wants, they can send a signal for a countdown to happen
	while num >= 0 do
		print(num)
		task.wait(1)
		num -= 1
	end

	countdownPacket:Fire()
end

return setmetatable(Public, Countdown)

If a different local module/controller wanted to start a countdown, they would simply require the module loader and import the “Countdown” module and do Countdown:StartCoundown(5) and it would start a countdown.

TL;DR
The idea of a module loader is to share functionality between modules in a more systematic manner. If a local script or server script wanted, they could also import these modules and use them aswell. Thats all up to the developer though.

1 Like

Module loaders (sometimes) load modules without needing you to manually require them. They also provide you with lifetime functions like :start and sometimes RunService wrappers like :heartbeat without needing to manually require RunService. You’d also be able to define your own events.


IMO it’s better to make your own module loader if you need one, but it’s nice to have a public one in case you don’t know how they work.

1 Like