What is the purpose of a module loader, and how does it work?

I’ve started using ModuleScripts to make my code more efficient and easier to manage. I’m also attempting to use a one-script architecture for similar reasons. However, in doing this I’ve quickly run into a problem: every single time I create a new ModuleScript that I need to require, I need to manually require it in main (the script on the server, or the localscript on the client). I suspect that a “module loader” is the solution to that problem, but because documentation on it is very scarce, I was hoping someone could confirm my suspicions, and explain exactly how it works. Also, if there’s anyone who’s had a lot of success with a single-script architecture/modulescript implementation, I have a lot of questions that I’d like to ask about it, perhaps in private. Thanks!

5 Likes

Question’s been asked before.

If it’s not this question: single-script architecture, frameworks and library loaders have all been asked about in the past and are things that you can find with some quick research. Please do make sure you search first before asking questions.

1 Like

I made my own Framework similar to Aero Game Framework that is essentially just a modulescript loader. I’ll explain roughly how AGF works.

You mainly have two types of modules: Your regular ones that will be required, and the lines that act like scripts. AGF has a modules folder and a Services (controller for client) folder to differentiate these. First you make an internal representation of the directories. (Services → Service, Modules → module) etc Loop through all of the modules and services and store their internal values in the representation.
You can lazy load the modules instead by attaching some metatable work to try to find a module when a non loaded module is trying to be accessed. That internal representation just gets injected into the module script table so you can do something like self.Services.Service Which are really all just tables.

The services can be run by trying to call a specific function in the modulescript that serves as an entry point.

In my implementation I created a standard library so you can ‘import’ them. This makes the library good for generic functions and the modules folder good for game specific ones.

6 Likes

Another important piece is the lifecycle. Lifecycles are really important in many frameworks (e.g. React) to dictate when and where certain actions should occur.

When it comes to writing module loaders like in AGF, it’s important that the modules don’t try to access other modules until the right time. For instance, if it tries to access modules immediately, it might fail because they might not be loaded yet.

I split my startup lifecycle into two parts: Init and Start (both of which occur after all modules have been required). This allows each module to not only be loaded, but also run custom code to “set up” the module if needed (in the Init portion) before actually starting up. I also run the Start portion on separate threads per module.

10 Likes

Good addition. :+1:
My implementation is pretty much the same in those regards.

To continue further of the topic, one of the benefits I found with this kind of architecture is how extensible it is. It becomes trivial to add things like equivalents to Unity’s “Update” function or use metatables to allow Roblox methods on the internal representation table and redirect the call to the object. Not to mention making cross-script communication a breeze and allowing for whatever FE implementation you’d like. Studying your framework and making my own taught me a lot on how modulescript loaders work and what I want in a environment.

2 Likes