Nexus Project - Simplified Requiring of Resources

Those who use lots of ModuleScripts in their code may have realized how long their require statements have to be. For example, a module called “ColorUtil” in “Util” in “Client” in “ReplicatedStorage” may require ReplicatedStorage:WaitForChild("Client"):WaitForChild("Util"):WaitForChild("ColorUtil"), while something in C# would look like using ReplicatedStorage.Client.Util.ColorUtil. Nexus Project attempts to simplify this for class libraries.

How Does It Work?
Nexus Project resolves paths for projects, starting with a root project and going down. Using the example from above, you can convert the Client folder to a module script with the following contents:

local NexusProject = require(script:WaitForChild("NexusProject"))
local Client = NexusProject.new(script)

return Client

What is the point of this? Rather than having this:

local Client = game:GetService("ReplicatedStorage"):WaitForChild("Client")
local ColorUtil = require(Client:WaitForChild("Util"):WaitForChild("ColorUtil"))
local TweenUtil = require(Client:WaitForChild("Util"):WaitForChild("TweenUtil"))
...

To this:

local Client = require(game:GetService("ReplicatedStorage"):WaitForChild("Client"))
local ColorUtil = Client:GetResource("Util.ColorUtil")
local TweenUtil = Client:GetResource("Util.TweenUtil")
...

Additional Features
Value Storage
Along with fetching modules, you can store arbitrary objects as part of the project. Here is an example of adding the version and name to the Client library from above.

local NexusProject = require(script:WaitForChild("NexusProject"))
local Client = NexusProject.new(script)

Client:SetResource("Client.Version","1.0.0")
Client:SetResource("Client.Name","ClientLib")

return Client

Cyclic Dependency Detection and Correction
Cyclic dependency (A requires B, B requires A) happens a lot and can be hard to detect. Using “contexts”, detecting them can be pretty easy. For example, these three scripts for the project above:

--ReplicatedStorage/Client/Util/ColorUtil
local Client = require(script.Parent.Parent):GetContext(script)
local TweenUtil = Client:GetResource("Util.TweenUtil")
local ColorUtil = {}

return ColorUtil
--ReplicatedStorage/Client/Util/TweenUtil
local Client = require(script.Parent.Parent):GetContext(script)
local TeleportUtil = Client:GetResource("Util.TeleportUtil")
local TweenUtil = {}

return TweenUtil
--ReplicatedStorage/Client/Util/TeleportUtil
local Client = require(script.Parent.Parent):GetContext(script)
local ColorUtil = Client:GetResource("Util.ColorUtil")
local TeleportUtil = {}

return TeleportUtil

Would result in the following warning:

A dependency loop exists:
Util.ColorUtil
Util.TweenUtil
Util.TeleportUtil
Util.ColorUtil
Use NexusProject::SetResource or NexusProjectContext::SetContextResource to allow the loop to end without having to load the resources.

The last line shows how it can be mitigated. All 3 modules end up in a deadlock since each need each other to finish loading to continue. The quick way to resolve this is to set one of the resources so the others can finish loading.

--ReplicatedStorage/Client/Util/TweenUtil
local Client = require(script.Parent.Parent):GetContext(script)
local TweenUtil = {}
Client:SetContextResource(TweenUtil)
local TeleportUtil = Client:GetResource("Util.TeleportUtil")

return TweenUtil

Custom Base Paths
You may have additional resources for a project that are stored externally. They can be easily added to have a base path. Adding a “Common” folder to the example above results in the following:

local NexusProject = require(script:WaitForChild("NexusProject"))
local Client = NexusProject.new(script)
Project:CreatePathLink("Common",game:GetService("ReplicatedStorage"):WaitForChild("CommonModules"))

local MessageUtil = Client:GetResource("Common.Util.MessageUtil")

return Client
15 Likes