In the times before me (quite literally), lua created the require
function. This function was the way to import files in lua. It was odd, for it accepted file paths, but instead of using /
as the separator, it used .
. Even weirder, if you pointed the function to a folder that had a child named init.lua
, it would load that file.
- folder
- init.lua
- file.lua
require("folder") -- loads folder/init.lua
This was the way things were.
Roblox
In the last month of 2013, Roblox released a pair of new features: ModuleScript
instances and the require
function. While roblox did use lua, it did not run lua in a filesystem. Instead, it ran lua in roblox’s datamodel, where any instance could have children, and those children could have children, and so on. Importantly, the child of a ModuleScript
could be another ModuleScript
.
- folder
- init (ModuleScript)
- file (ModuleScript)
require(script.folder.init) -- loads folder.init
This was the way things were.
Rojo
In the last month of 2017, lpghatguy released rojo. Rojo was a tool that allowed roblox developers to write their code on the filesystem and then sync it into roblox. Of course, the code was still running in roblox, so roblox’s require
function was still used.
The roblox datamodel had something that the filesystem did not: you could put files inside files in roblox. To allow for this, rojo turned back to lua’s require
function. Any folder with an init.lua
file would get transformed into a ModuleScript
instance, where the init.lua
file’s sibling files would be children of the ModuleScript
.
This filesystem structure
- folder
- init.lua
- file.lua
would be transformed into this roblox datamodel structure
- folder (ModuleScript, was `folder/init.lua`)
- file (ModuleScript, was `folder/file.lua`)
This was the way things were.
Luau
In 2020 Roblox released luau (yes I know it existed before 2020 but that’s when it became open source). Luau was in large portions a rewrite of lua 5.1, and it brought with it a ton of amazing new language features, like continue
and static analysis with types. Due to it’s open source nature, it could be used outside of roblox, and it was. Luau has been used in lune, alan wake 2, warframe, and even in my own toy runtime.
Luau requires
In the last month of 2023, RFCs to add the require
function to luau were accepted and merged. This meant that luau had a defined way to import files, and it was done with strings. These RFCs had a super cool feature, aliases, which allowed you to require from a defined point in the filesystem. Additionally, the require
function was relative to the file that was calling it. They also had the same behavior of loading init.lua
files that lua did.
- folder
- init.luau
- file.luau
require("./folder") -- loads folder/init.luau
This was the way things were.
Luau requires in roblox
In september of 2024, roblox prepared to add luau’s requires into the roblox game engine. Luau requires hadn’t been used extensively up to this point, so another RFC was merged to make a few fixes. This RFC made breaking changes.
Just under two months later, and only a few days before luau’s require was to be added to roblox, an issue of compatibility between rojo and the proposed addition to roblox was discovered and made clear to the luau maintainers. The issue was that rojo’s init.lua
files were turned into ModuleScript
instances with their siblings as children. This meant that what was a ./child
path on the filesystem was a ./{script}/child
path in roblox. As one of the major goals of luau requires was to unify requires across runtimes, this was an issue.
Just under two months later, in janurary of 2025, luau’s requires were announced to be added to roblox. Not a single change had been made. The issue with rojo had not been solved. The ugly syntax for requiring a child had not been changed.
Why?
There was an incredibly heated discussion about the issue in the roblox open source community discord. A discussion so heated that it resulted in the thread being locked by the admins, which was a first for the discord to my knowledge.
I personally proposed several solutions, and even made an RFC for one of them. My solutions worked, they solved the problems and made everything compatible. Other people proposed solutions that solved the problems and made everything compatible. These solutions were not accepted.
In the aftermath I was told by a luau maintainer that “this path maintains the most compatibility with everything that’s already written”. This statement is confusing to me because just 4 months earlier a breaking change was made to luau requires. This statement is confusing to me because the vast majority of code written does not use luau requires, and this alienates rojo users who want to use luau requires.
The luau team appears to be moving toward something called “project-relative requires”. This is not really announced or described anywhere, but we can only assume that it is requires that are relative to the root of a “project”, whatever that is. This idea didn’t really exist until november 2024.
I don’t have access to the internal luau team discussions or communications. I was not privy to the decision making process. But rumors spread, and I’ve heard from multiple reliable sources that a single individual convinced a higher-up that “project-relative requires” were the best option, and that the majority of the luau team disagreed with the reprioritization.
What does this mean for rojo?
Rojo could make a change to the way it transforms the structure of projects as it syncs them into roblox. This change would be breaking for rojo, but it would bring rojo in line with what luau expects.
Before anyone asks: no, Rojo won’t be getting a toggle for this. At least for now. We made our position very clear during the extensive OSS discussion on this, and Roblox went down this path anyway. It won’t matter unless you intend to write cross-runtime code. But for those of you that do want to, blame Roblox. Don’t blame Rojo.
- A comment by dekkonot, a rojo maintainer
That is the second comment on the luau string requires devforum announcement. Multiple rojo maintainers gave negative feedback on luau’s string requires as they were being implemented into roblox, and two months after their feedback no changes had been made. Rojo, as a non-roblox piece of open source tooling, was expected to fall in line with the demands of roblox, like they were forced to in the past with the introduction of RunContext
. They do not want this precedent continued, and are refusing to make changes to support luau’s string requires.
Conclusion
It’s impossible to overstate how disappointing I find it that there was no changes in behavior made from the initial OSS release of this feature to its public announcement, despite the feedback given. It feels borderline disrespectful.
- A comment by dekkonot, a rojo maintainer
This is the way things are.