Skip to content

Granular Imports#214

Open
comicallynathan wants to merge 4 commits into
luau-lang:masterfrom
comicallynathan:master
Open

Granular Imports#214
comicallynathan wants to merge 4 commits into
luau-lang:masterfrom
comicallynathan:master

Conversation

@comicallynathan

@comicallynathan comicallynathan commented Jun 25, 2026

Copy link
Copy Markdown

This RFC proposes syntax for allowing named imports

Rendered view

Expanded the summary and design sections, added examples for granular imports, and outlined drawbacks and alternatives.
Clarified the phrasing regarding first-class variables in imports.
@soyuta

soyuta commented Jun 26, 2026

Copy link
Copy Markdown

I support this.
Destructuring syntax was marked as "not planned" for Luau, and Luau has also said that it does not want to introduce too many new keywords or syntax that differs too much from Lua
but honestly, after adding export and class, i feel like that principle has started to lose a lot of its meaning

i think it's worth taking another look at this
Without destructuring syntax for imports, I honestly haven't liked any of the solutions proposed so far
I think the TS/JS-style approach is the cleanest

Corrected the phrasing from 'repeated prefixing' to 'repeatedly prefixing'.
@comicallynathan

comicallynathan commented Jun 26, 2026

Copy link
Copy Markdown
Author

I support this. Destructuring syntax was marked as "not planned" for Luau, and Luau has also said that it does not want to introduce too many new keywords or syntax that differs too much from Lua but honestly, after adding export and class, i feel like that principle has started to lose a lot of its meaning

i think it's worth taking another look at this Without destructuring syntax for imports, I honestly haven't liked any of the solutions proposed so far I think the TS/JS-style approach is the cleanest

Destructuring syntax has been a hot topic throughout the existence of Luau. It has been requested two times, and they were denied twice. The reasoning behind them was the lack of predictability and concern for Luau’s identity.

It’s getting harder to maintain an identity once you’ve recognized the need for convenience and tooling. Hence, this is why such features like const, classes, and exports are entering the Luau workspace.

Also, leaving export on its own isn’t very predictable. Once you have export, you’ll need a polarizing keyword to leave it symmetrical. At this point, I would rather have a full ESM-style control on Luau. This addition would be pragmatic, and it leaves less repetition for eye-strain and helps writers track their code easier without the incompetency of repeatedly prefixing commonly used variables as stated in the document. It’s a big leap if we’re asking the question of human-readability.

@comicallynathan

comicallynathan commented Jun 26, 2026

Copy link
Copy Markdown
Author

This opens the discussion for a potential structure. Will import be static and top-level? It makes sense to separate require and import as they both serve totally different audiences. Imports may be used for importing external static typing without prefixing. I think that import would make sense as static, while require() would be dynamic. Import could serve as gathering critical utilities and components while requiring will handle dynamic objects like skills and assets.

@jackhexed

Copy link
Copy Markdown
Contributor

The proposed syntax is not backwards compatible or parsable. import { foo } from "bar" is valid syntax today.

@jon9933

jon9933 commented Jul 3, 2026

Copy link
Copy Markdown

This feels like a needless change. you call require() "visual strain" yet how is your proposed solution any less eye straining?

@sammygrey

Copy link
Copy Markdown

I understand the premise of a different programming language having different syntax than what you are used to resulting in an "unlearning process" that takes some time to get used to. However, this problem does not exist for programmers who already are proficient in Luau and a change like this would create needless friction for existing developers.

@soyuta

soyuta commented Jul 4, 2026

Copy link
Copy Markdown

This feels like a needless change. you call require() "visual strain" yet how is your proposed solution any less eye straining?

I understand the premise of a different programming language having different syntax than what you are used to resulting in an "unlearning process" that takes some time to get used to. However, this problem does not exist for programmers who already are proficient in Luau and a change like this would create needless friction for existing developers.

Discussions about adding import have been going on for quite a while
you can find one of the earlier discussions in #103 as well.

That RFC #103 also proposed an import syntax without relying on require():

import myname, type mytype, myname2, type mytype2 from "mymodule"

However, I think adopting destructuring imports with a TypeScript/JavaScript-style syntax would be a better approach.
It would support namespace imports (*), type-only imports, and other familiar patterns. since Luau's syntax is already fairly close to TypeScript in many ways, I think this style would fit the language better.

Instead of writing:

const Module = require("./Module")

const A = Module.A
const AnotherB = Module.B
type U = Module.U
type V = Module.V

you could simply write:

import { A, B as AnotherB, type U, type V } from "./Module"

Or, if you want to import everything:

import * from "./Module"

print(A, B)

const u: U = "U"
const v: V = "V"

It's much more concise and significantly easier to read.

More than anything, now that the export keyword has been merged in #179, it feels inconsistent to have export without import.

Handling everything with require() alone doesn't feel sufficient anymore.
Since export now allows cyclic requires, I think Luau also needs an import system that supports them in the same way.

@soyuta

soyuta commented Jul 7, 2026

Copy link
Copy Markdown

There is another practical reason why import would be useful.

In Luau, when writing modules, it is common for the value returned by a module to have the same name as the module itself.

src/
  Car/
    init.luau -- returns Car
    Bus.luau -- returns Bus

For example, if Bus uses Car, the code often ends up looking like this:

const Car = require("./")
const Passenger = require("../Entity/Passenger")

const Bus = {}
Bus.__index = Bus

export type Bus = setmetatable<{
    Passengers: {Passenger.Passenger}
}, typeof(Bus)> & Car.Car

function Bus.new(): Bus end
function Bus:GetNearestCar(): Car.Car end

return Bus

One issue here is that types such as Car.Car or Passenger.Passenger become fairly awkward to read.
The repeated name is visually noisy, especially when this pattern appears across many modules.

A common workaround is to export the type under a generic name such as Car.Object or Passenger.Object, but that also has a downside. When reading type hints outside the module, the type may appear as just Object, which makes it harder to distinguish from other similarly named exported types.

Another workaround is to create local aliases:

type Car = Car.Car
type Passenger = Passenger.Passenger

export type Bus = setmetatable<{
    Passengers: {Passenger}
}, typeof(Bus)> & Car

This works, but it adds extra declarations purely to avoid repetitive type names.
In small cases, that means adding extra lines and increasing the diff just to use a type once or twice.
It also adds another step when tracing where a type originally came from, since the reader has to follow the local alias back to the required module.

The import syntax removes the need for separate local type aliases.

import { type Car } from "./"
import { type Passenger } from "../Entity/Passenger"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants