Compiled
See the plain Luau your ShotScript turns into: a Rojo layout on disk, and strict Luau for every class, module and func.
You never edit compiled Luau, but knowing its shape helps when you read an error message or the scripts in Studio. Each section below shows one kind of file and the Luau the compiler writes for it.
Where files go
Everything lands in ReplicatedStorage.Shot, at the same path as on disk. On disk (shot build) it uses Rojo names. A system folder, Wallet/, becomes:
init.luau | the folder's entry point: a wrapper ModuleScript called Wallet |
Wallet.luau | Wallet.shot, a child of the wrapper |
Bank.luau | every other file, also a child of the wrapper |
Types.luau | the Types module |
BuyRequest.model.json | BuyRequest.instance: {"className": "RemoteEvent"} |
Scripts, such as the two in Init/, are named for their RunContext:
Client.client.luau | a @client script: a Script with RunContext Client |
Server.server.luau | a @server script: RunContext Server |
Name.meta.json | next to a disabled script: {"properties": {"Enabled": false}} |
A folder without an entry point stays a Folder. Because every file of a folder is a child of the same instance, script.Parent means the same thing in all of them.
The wrapper re-exports the system's public types and returns the entry point:
--!strict
local Types = require(script.Types)
export type Reason = Types.Reason
export type Wallet = Types.Wallet
export type Bank = Types.Bank
return require(script.Wallet)Every module file
Each module file starts with its folder's types and ends by returning what it declares:
--!strict
local Types = require(script.Parent.Types) the types line
export type Reason = Types.Reason one line per type of the folder:
export type Wallet = Types.Wallet export type for public ones,
type WalletClass = Types.WalletClass type for internal and imported ones
(the plain Luau above the declaration, unchanged except @shot paths)
(the declaration, below)
(the plain Luau after it)
return Bank--!strict matters: without it, other files see the module's functions as returning any. @shot.X.Y becomes a relative path such as script.Parent.Parent.X.Y.
A class
A class becomes a table with a metatable, its fields listed as comments:
local Shape = {}
Shape.__index = Shape
--@public Name string fields, as comments
--@internal _hp number
--[[$constructor]] function Shape.new(name : string) : Shape
local self : Shape = setmetatable({},Shape) :: any init() expanded
...
end
--[[$public]] function Shape.Damage(self : Shape, amount : number)
...
end
--[[$public]] function Shape.Made() : number a static function: no self- The constructor returns the class, so a missing
return selfis an error there. - When Circle extends Shape:
local Circle = {},Circle.__index = Circle,setmetatable(Circle :: any, Shape). - A parent in the same folder gets
local Shape = require(script.Parent.Shape)added above the class.
Class properties go on the class table:
local Icon = {
Speed = (10) :: (number),
}
Icon.__index = Icon
(with a parent: local Fancy = setmetatable({ Glow = (2) :: (number), }, Icon :: any))A module
A module is a typed table, and module is a second name for it:
local Bank : Bank = {} :: any
local module = Bank
Bank.Wallets = {}
Bank.Count = 0
function Bank.Open(player : Player) : boolean
...
endA func, a split module, a diffsource func
A func is a local function. A split module and a diffsource func load the half for the side they run on:
local function NextId(prefix : string) : string ... end
local Market : Market = {} :: any
if RunService:IsServer() then
Market.Server = require(script.Parent.MarketServer)
else
Market.Client = require(script.Parent.MarketClient)
end
local UniqueIdentifier : UniqueIdentifier = if RunService:IsServer()
then require(script.Parent.UIDServer)
else require(script.Parent.UIDClient)Split modules and diffsource funcs get local RunService = game:GetService("RunService") above them.
The Types module
Each folder's Types.shot becomes a module that only holds types:
--!strict
if true then return {} end
local Signal = require(script.Parent.Parent.Signal)
--[[$internal]] export type Signal = Signal.Signal
--[[$public]] export type Reason = "Pickup" | "Shop"
--[[$public]] export type Shape = {
Name : string,
_hp : number,
Damage : (self : Shape, amount : number) -> (),
}
--[[$internal]] export type ShapeClass = {
new : (name : string) -> Shape,
Made : () -> number,
Damage : (self : Shape, amount : number) -> (),
}if true then return {} end: the module is only types and never runs.- Imports come first, then your own types (
Reason). - Each class gets two types: its object type (
Shape: fields, then methods) and the class table's type (ShapeClass: constructors, statics, methods).
A subclass's class type lists only its own members, so reach an inherited static through the class that declares it (Shape.Made(), not Circle.Made()). A documented member's type is typeof(__docfuncN), a stub function carrying the doc, so editors show it on hover. Every type is export type in the Types module; public and internal only decide what the other files and the wrapper re-export.
