Output and Sync
The project file Rogen writes, and how its paths reach your compiled code.
A build is one complete run that turns root directories into an output for one config. Everything on this page is about that output.
Output
The output is the Rojo project file a config writes, named by outFile. It defaults to the config's own name: default.rogen.json writes default.project.json, and lobby.rogen.json writes lobby.project.json. outFile is never inherited through extends, so every config writes its own file.
Rogen owns the output. A build creates the file when it's absent and replaces whatever is there, hand-written or not. Anything Rogen can't know belongs in the template. Rogen leaves the file untouched when its bytes wouldn't change, so Rojo doesn't re-sync and watch doesn't rebuild on its own write.
One config writes one output. A second project file is a second config.
The default output is the one tools find
rojo serve with no argument picks up default.project.json, and so does luau-lsp when it generates its sourcemap. With Darklua the two need different files, so default stays rooted at your source and you serve sync.project.json. See the Darklua guide.
Template
The template is the Rojo project file that Rogen merges its generated tree into, set by template. It's a path to a *.project.json, never an inline tree. It holds what Rogen can't know: the DataModel name, package mounts and $properties.
- Where the template and the generated tree define the same instance, the template wins, and Rogen warns. A
$pathbelow a service, such as a package mount, is that folder's whole content, so generated files under it are left out too. A service's own$pathstill takes generated children. - A child config's
templatereplaces its parent's, and--templatereplaces both. - Without a template, Rogen starts from an empty
DataModelnamed after the config's directory.
{
"name": "my-game",
"tree": {
"$className": "DataModel",
"ReplicatedStorage": {
"Packages": { "$path": { "optional": "Packages" } }
}
}
}rogen init writes a template when it finds packages to mount, and references it from the config. When a hand-written project file exists that the build would replace, init copies it to template.project.json instead, or lets you reference another project file as it is.
Sync Directory
Rojo syncs from your compiler's output, not from your source. The sync directory, set by syncDir, is that output: out for roblox-ts, dist for Darklua. Emitted $path entries point there instead of at the root directories. Plain Luau has none, so its paths point at the root directories themselves.
A file at p is emitted at syncDir / relative(commonRoot, p), with .ts and .tsx rewritten to .luau. The common root is the deepest directory that contains every root directory. It's derived, never written in the config, and rogen list --json prints it. With one root directory the common root is that directory, so syncDir simply replaces it:
Darklua processes src into dist, so with "syncDir": "dist":
src/Inventory/Server/Save.luau -> $path: dist/Inventory/Server/Save.luauWith several root directories the common root moves up, and each path keeps the part that tells the root directories apart. ["core", "places/lobby"] has the repo root as its common root, so paths land at dist/core/… and dist/places/lobby/…. That's what compilers do: tsc roots its output at the common prefix of its inputs.
syncDirapplies only to paths Rogen generates. A$paththat comes from the template, like Wally'sPackages, is copied through untouched.- After a build, Rogen warns once for each root directory whose emitted paths all fail to exist, naming the path it expected and the nearest one that does exist. That usually means the compiler hasn't run or roots its output differently.
- Rogen never reads the sync directory. Output left in it by a source that no longer exists still syncs, so clean the directory as part of the compile step.
One Tree, Two Renderings
A rendering is the tree written out as a project file, with its $paths rooted at one directory. The same tree rendered from a different directory is a second config that extends the first and adds a syncDir. That's how Darklua setups get a project file for Studio and one for Darklua. See the Darklua guide.
Building While Watching
rogen build is safe to run while rogen watch writes the same output. Each process stages its write through a file of its own before renaming it into place, and neither writes when the bytes wouldn't change. After adding, moving or renaming files, rogen build is always correct, whether or not watch is running.