Is this a fair use of global variables?

I understand that global variables on highly frowned upon in the roblox development community, but if all of my studio members are okay with using them I don’t see an issue. My usage is to make a library importer that can work for any script. This is how its used.

local module = shared.import("ModuleName")

I much prefer this to inputting the entire directory into the require() function because I frequently change folder names and how my scripts are set up, I’m also used to python environments so this makes code feel more natural to me. The only downside is that the auto predict is lost but I’m considering making a vscode extension that will bring it back.

Is there anything that will actually make development more difficult with my approach besides global variables just being “bad” or “ugly”.

Also in terms of performance, this is with 10,000 iterations so I’d say its negligible.
shared

5 Likes

Just make sure that there is only one point of entry into your codebase on the server and one on the client. This way, you can assure that you set the import function before it gets accessed anywhere.

This isn’t awful, the only problem is that with a global namespace like this external code could also set the “import” key to something else, leading to a conflict, but as long as you keep this in mind when using other people’s libraries, you should be fine.

Some other people make the import function a module and require that from a predictable location instead, but I understand why you’d go for this approach.

1 Like

So I plan on having a module script that will have this import function, and at the point of entry in both server and client they will require this module, that way making sure there will never be a conflict

I do this sometimes, but I’ve found that I like to sort of do the structuring myself so I can extend my code easier. This is how I’ve been doing it lately (and I’ve started liking this a lot). I have a module named “Directory” which is a fancy function which will return certain things with a path name.

Sometimes I put the result of the module in shared or _G (fun fact: two different tables with different values). Sometimes I copy and paste two lines to require it from somewhere like ReplicatedStorage.

1 Like

I made a module that requires other modules for me, and it lazy loads them, so it doesn’t require any of the modules until I want it to

I have this in my main script on both server and client

local Quire = require(game:GetService("ReplicatedStorage"):WaitForChild("Shared").Quire)
_G.Quire = Quire

and this allows me to retrieve it easily in other scripts

local Quire = _G.Quire
local SomeModule = Quire("SomeModule")

--[[
	simplified requiring of modules
	use: local module = Quire(moduleName)

	searches if there is already a module cached in cachedRequiredModules, if there it's returned, otherwise it will iterate through
	the ancestorsToCheck table, if it's found it returns the module and caches it
--]]

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

local isServer = RunService:IsServer()

local ancestorsToCheck = {ReplicatedStorage:WaitForChild("Shared")}
local cachedRequiredModules = {}

if isServer then
	cachedRequiredModules["DataStore2"] = require(ServerScriptService.DataStore2)
	-- all the places to check for server modules
	table.insert(ancestorsToCheck, ServerScriptService.Server)
else
	-- all the places to check for client modules
	table.insert(ancestorsToCheck, ReplicatedStorage:WaitForChild("ClientModules"))
end

local Quire = {
	__call = function(_, moduleName)
		local module = cachedRequiredModules[moduleName]
		if not module then -- no module found, searching for it
			for _, ancestorToCheck in ipairs(ancestorsToCheck) do
				local foundModule = ancestorToCheck:FindFirstChild(moduleName, true)
				if foundModule then -- module found, returning it
					module = require(foundModule)
					cachedRequiredModules[moduleName] = module
					break
				end
			end
		end
		return module
	end
}

setmetatable(Quire, Quire)

return Quire

I’d say it’s worth doing, if it saves you time and makes things easier, why wouldn’t you do it?

Edit: might of misunderstood your post, might just be a opinion, but depending on how much variables you do it for, you will probably end up forgetting them and that could cause confusion

2 Likes

That’s actually the same thing I’m doing, I have a module called import which returns the import function (forgot I could make a metatable __call) and then I just set shared.import to that module.

One annoying thing about this is that most vscode extensions block _G and shared because they don’t like it so I might switch back to doing things in roblox studio.

1 Like

There’s no need for it… set up a modular hierarchy for all your library needs

It makes it easier and quicker to load modules, if theres nothing wrong with it I don’t see a reason to not do it

Dirtying up your global environment is not needed and if proceeded down this path it can be tricky having control of what parts of your code can access this and what’s stored within it st all times. It’s good to keep your code de coupled as possible.

2 Likes

the first line in both client and server define the shared.import function so that wouldn’t be a issue

Probably works for small projects. However, the model quickly breaks if you plan to include third party modules which are unaware of your naming scheme.

You could have separate inclusion models for different parts of your code, but that sort of inconsistency often leads to bugs.

That said, I have a utility require as well that leverages loadstring when script is nil. This allows me to make changes and test without having to press play.