Is there some way to change the set of globals that are available to a ModuleScript?

I make heavy use of modules for organizing my code, and one thing that’s super annoying is repeatedly typing out the full path of helper modules. This is especially bad in LocalScripts, because for example imagine I have this hierarchy:

StarterPlayer
\-- [Folder] StarterPlayerScripts
    \-- [LocalScript] Client
        |-- [ModuleScript] Foo
        \-- [ModuleScript] Bar

Inside of Foo, if I want to use Bar, I can do something like require(script.Parent.Bar), but this is not ideal because it might not be a one-level-up kind of thing, I could be several levels deep and doing a bunch of back-tracking up the hierarchy and then down into another hierarchy is error prone and makes refactoring harder. What I really would like to do is, inside of Bar, say local Foo = require (Client.Foo). But this of course doesn’t work because Bar doesn’t know what Foo is. So now I have to do

local Client = require(game:GetService("Players").LocalPlayer.PlayerScripts.Client)
local Foo = require(Client.Foo)

This is really obnoxious, and I would like it if every single required ModuleScript ever just automatically inherited this global variable. Is there any way I can achieve this? This is super easy in something like Python, for example, but I’m a bit new to Lua so I’m not sure what the right way to achieve this is.

1 Like

I don’t know if there’s any way to directly inject it into the module environment from the main script, but even if there was, I would stay away from doing so for performance reasons. Your best option is probably some kind of init function for the module where you have to pass the Client as an argument. Here’s a quick example of what it would look like, but basically you just pass the Client and set a variable in the outer scope to it.

local module = {}
local client = nil
-- and the module goes on until here...
return function(clientReference)
	if clientReference then
		client = clientReference
	end

	return module
end

-- In the script that requires it:
local client = game:GetService"Players".LocalPlayer.PlayerScripts.Client
local foo = require(client.Foo)(client)

Alternatively, you could use FindFirstAncestorOfClass, assuming that client is the only local script in its hierarchy. That would be as simple as putting local client = script:FindFirstAncestorOfClass("LocalScript") in the module.

2 Likes

I wrote a project in Lua designed to run in Roblox and outside of it in LuaJIT. One of the biggest issues I ran into is that the regular lua require takes in a string with ‘/’ delimiters while Roblox’s require takes in an instance which use ‘.’ instead. I wrote a custom require function for the Roblox version of it that splits a string around ‘/’ and traverses instances starting from the project root.

The problem is that I can’t require modules to define custom settings when even my require function changes depending on context. Let me phrase that differently: I couldn’t require a module that defined my settings / constants. You also can’t edit a module’s environment before it runs. The solution I cam up with is to prevent the module from immediately running! simply wrap the entire module in:

return function(settings)
...
end

and this will allow you to either modify the environment (not recommended) or pass in settings or even a table of other modules that have already been required. It allowed me to pass in a custom require function.

1 Like

Mmm, interesting. That could work. Could you show a sample of how you used it?

Ok yea I got it working. I did this:

-- Some module
return function(Globals)
    -- require(game:GetService("Players").LocalPlayer.PlayerScripts.Config)
    print("Config.Foo = " .. Globals.Config.Foo)

    -- require(game:GetService("Players").LocalPlayer.PlayerScripts.Utility)
    print("Utility.DoSomething() = " .. Globals.Utility.DoSomething())
end
-- Main LocalScript
local Config = require(game:GetService("Players").LocalPlayer.PlayerScripts.Config)
local Utility = require(game:GetService("Players").LocalPlayer.PlayerScripts.Utility)

local Globals = {
	Config = Config,
	Utility = Utility
}

local TestModule = require(script.TestModule)(Globals)

Is this basically what you had in mind?

Hmm, this also means I can never require a module more than once right? Because then calling the function again would return a different copy of the module.

Yup, that is how I did it. You can require it more than once and it would return the same function. But yes, if you called the function with globals again it might create new values.

There are ways to get around this, like having a table of modules that is passed around and when a script tries to access a missing module it fires the __index metamethod and loads in the module and stores the result. Tbh my use case only required each file to be required once. This seems like it would make for a nice loading system though.