As you may have noticed, I’ve been working on a framework for over a year now (previously called Tabby, but renamed to Catwork for general purpose uses).

Motivations

I felt like laying out some of my motivations for Tabby originally, and more greatly, Catwork.

I attempted to address the following limitations I see in many (not all) Roblox- based frameworks.

  1. Many frameworks require you to return something, hurting debugability, and also prevents you from using Scripts (if you really wanted to for some reason)
  2. Many frameworks also hurt typing since Luau typing isn’t the smartest thing out there (this creates some interesting workarounds, that I’ll discuss later)
  3. Many frameworks cant be expanded beyond their original purpose, creating forced logic that most people probably dont want in all code that uses it.

1. Required returns

A naive framework would probably set up a module template like this:

-- init function
return function()

	-- update function
	return function()

	end
end

While I’ll get the reasonings for this not being that good, I’ll address some positives; it’s an insanely simple template, can be spawned multiple times, and is also easy to program around in a loader or runtime. You can also pass in variables related to the framework if you need to.

The issue lies in what if I want to load this object outside the framework for debugging? If I simply required the module as-is, I’d get a function with no external way to load it up as I wanted it to.

Catwork addresses this by using a constructor that initialises a Fragment as soon as you call it.

Catwork.Fragment {
	Init = function()

	end
}

This also provides the advantage of loading up a Fragment in a script if you really want to.

Since Catwork.Fragment just internally clones the passed table, you could even return the Fragment, and have the Fragment, not a table or a function that you cant really do much with.

2. Luau Typing

This is the main motivation behind Catwork, as this expands the issue from earlier. What if we just assign an identifier to the code and load it that way?

Because of the nature of Luau typing, we lose all typing info, which for me is a massive tradeoff that I’m not willing to take, if all it takes is reworking the way code is loaded a little bit.

I wanted to support Luau typing as much as possible because autocompletes save a massive amount of time when programming. I did run into some roadblocks with it (mainly enforcing Fragment params), but being able to just load up a Fragment, and have all of its logic there and there ready to go, it’s worth it.

While not a framework per-sae, Luau typing is also an issue with module loader scripts that just package up modules into a common API that any other code can load. Theres also issues with race conditions here, but thats not what I’m going to go into.

Roblox themselves use static paths in their frameworks, even if it requires over 200 explicit require calls, simply for Luau typing reasons.

3. Enforced Logic

Frameworks tend to fall into the trap of trying to do too much with their frameworks hurting expandability. I took a different approach, instead of trying to force too much native logic, why not instead let the developer decide what they want Fragments to do. This is what services are for.

One framework out there gives each module a RemoteFunction, which for its purpose, makes sense, but if I want to create a module that is purely client-side or server-side, the remote is still there, going unused.

Catwork’s native service has one purpose, create and spawn Fragments, and its entire logic can be overrided with the Catwork.Service constructor.

If you wanted to create the logic I just discussed, just create a Service that connects a RemoteFunction to it, this means Fragments that dont need remotes dont use them.

Catwork.Service {
	Fragment = function(params)
		params.Remote = CreateRemoteFunction(params.Name)
	end
}