@cfg(server)
function save(player: Player) end
@cfg(server)
print("the server starts")
@cfg(client and not studio)
const hud = new Instance("ScreenGui")Code for one side. One rule covers every form: code with nothing to give does nothing on the wrong side; code that owes the caller a value says it cannot run there.
A function that gives back no value returns at once where the condition fails. This is what a shared bootstrap needs: startup calls start_server and start_client, and each half runs on its own side. A function that gives back a value keeps its type and raises there. A silent nil where the signature promises a Player breaks somewhere else instead of naming the mistake, which is calling a server function from the client.
The signature decides. function start() with no return of a value skips, function start(): () skips, and function key(): string raises. An optional return type raises too: the type says the caller gets a value, and nil is one of its answers.
A statement runs where the condition holds and the other side skips it. A statement gives nobody a value, so a skip tells no lie. The emit is a guard on the statement's own line: if __alloy.cfg.server() then start_server() end. Two @cfg lines over one statement read as one condition, joined by and. A @cfg on a return changes what the rest of the block does on each side, so read the statements under it. @cfg is the only attribute a statement takes; every other one goes on a declaration.
A local reads its value only where the condition holds, and stays typed as the value; elsewhere it is nil. On a namespace, each member reaches the table only where the condition holds, so the name is nil on the other side.
The conditions: server, client, studio, edit, running, and test (an alloy test run). Join them with not, and, or, or any(...) and all(...).
A shared module loads on both sides, so no form is removed at build time. Each condition is a read of RunService when the code runs.
Applies to function · local · namespace · statement