Roblox-TS: Conventions¶
TypeScript strictness¶
- Prefer strict TypeScript settings for shared packages.
- Avoid
any. - Use branded types for ids (
PlayerId,MatchId) to prevent mixups.
Monorepo package rules¶
- Each
@rbx/*package compiles independently with--type package - Games compile with
--type gameto get RuntimeLib requires - Package
typesfield points toout/index.d.ts(compiled declarations) - Packages should not import from other
@rbx/*packages at compile time (inline shared types if needed)
Package Configuration (Critical)¶
Every @rbx/* package must have proper configuration for both Node.js (vitest) and roblox-ts:
tsconfig.roblox.json¶
Must include the rbxts section with type: "package":
{
"compilerOptions": {
// ... compiler options
},
"rbxts": {
"type": "package", // ⚠️ REQUIRED - without this, packages expect their own RuntimeLib
},
"include": ["src/**/*.ts"],
"exclude": ["src/**/*.test.ts"],
}
package.json¶
Must point main to out for roblox-ts, with exports for Node.js:
{
"main": "out",
"types": "out/index.d.ts",
"exports": {
".": {
"types": "./src/index.ts",
"import": "./src/index.ts",
"require": "./src/index.ts",
"default": "./out"
}
}
}
main: "out"→ roblox-ts uses this to find compiled Luauexports.types/import/require→ Node.js/vitest uses these for testingexports.default→ Fallback for roblox-ts
Rojo project.json¶
Package paths need nested out folder to match compiled imports:
{
"@rbx": {
"$className": "Folder",
"shared-types": {
"$className": "Folder",
"out": {
"$path": "node_modules/@rbx/shared-types/out"
}
}
}
}
Why nested? The compiled Luau does TS.getModule(script, "@rbx", "shared-types").out - it expects .out to be a child of the package folder.
Folder intent¶
client/: UI, camera, input, effects, predictionserver/: authority, persistence, matchmaking, validationshared/: DTOs, schemas, constants, deterministic math
Error handling¶
- Use stable error codes for anything crossing boundaries.
- Never leak internal server details to clients.
Roblox services¶
- Access services through
game.GetService()once per module and reuse. - Treat
Players,ReplicatedStorage,ServerStorage,ServerScriptServiceas deliberate boundaries.
Cleanup¶
- Everything that connects events must be disconnected on teardown.
- Every controller/service should support stop/dispose.