This can then be called from the Client or Server dependant on which required it - To be honest, this currently works for me and it’s the best method I’ve found of formatting and organising my system utilities.
You could start by not using shared/global table. It’s 2022, developers have zero reason to be using globals besides backwards compatibility for older projects that they intend to eventually upgrade to phase out the use of globals for a better approach such as a framework.
I don’t exactly see what you’re hoping to accomplish here though or why you don’t like the current way of writing out your imports. At a fundamental level, both the code you dislike and the code you think “works” are equivalents. You’re essentially writing boilerplate to do the same thing in a worse fashion, opening up a venue for potential bad practice like namespace collision/pollution.
There might be a legitimate case for styling your code this way if you were using a framework of some sorts wherein a single script is handling networking and communication between scripts because your framework core would be doing much more than just providing a different way to index your codebase. If that’s not what you have going on though and you’re doing this purely to change the way you index code, then it’s entirely pointless and you’re trying to resolve a non-issue.
Like I said in the post above, what you’re attempting to do and what you “don’t see a point in” are virtually the exact same thing. The difference is that you’re storing a table of references in a place where you shouldn’t be (the global table) and inviting practice problems along with it versus taking a few minutes to write out a few imports, first services and then your codebase.
If “ease of access” (which this isn’t) is your only goal and you aren’t including any abstractions in the process then you don’t have a reason to write your code this way. You should look into a framework so that you can take your codebase beyond just changing the way you access parts of your module.
AeroGameFramework, although colloquially superseded by Knit, is a good example of where you may want to look in terms of achieving this style. All parts of code (services, controllers and modules) are put into a “main table” that you can access across your code. Take a look at the documentation to see how the access model works. As for Knit, it prefers a more traditional style of importing modules into your framework… it’s more of a mix between what you want and what’s idiomatic.
--- Accessing another module with AeroGameFramework
local MyModule = self.Modules.Foobar
--- Accessing another module with Knit
-- First it has to be added
Knit.Modules = game:GetService("ReplicatedStorage").Modules
-- Then you can require
local MyModule = require(Knit.Modules.Foobar)
A big catch is that these abstract communication and networking, so they’re doing more than just providing you a way of talking with different parts of your codebase. If you don’t have any abstractions present that are managing a core part of your game and you’re only looking at accessing things a different way, “then it’s entirely pointless and you’re trying to resolve a non-issue”.
I hope these two posts help you see that my point is that there’s no use in doing this and that it’s actually bad practice but if you insist then that’s your prerogative. I’m sort of parroting myself here so that’s all the advice I can leave you with!