The nexus command
nexus create <name> Start a new resource in the folder <name>nexus dev Work on the UI in a browser, with a mock in place of the gamenexus dev --game Serve the UI to the game with hot reloadnexus build Build web/dist and the Lua bridge, and check fxmanifest.luanexus check Type-check, validate the contract, and report what Chromium 103 cannot runnexus --helpnexus --versiondev, build and check run in the folder of a resource, next to fxmanifest.lua. Running
them from inside web/ works too. In a resource made by nexus create they are also npm
scripts: npm run dev, npm run build, npm run check.
Every command exits with 0 when it succeeded and 1 when it did not, and an error says what to do next. An unknown option is an error, not something that is ignored.
The layout of a resource
Section titled “The layout of a resource”my_shop/ fxmanifest.lua client/main.lua your client Lua server/main.lua your server Lua locales/en.json strings, read by Lua and by the mock web/ contract.ts every message between the page and Lua mock.ts what stands in for the game under `nexus dev` screens/Main.nexus one file per screen nexus-contract.d.ts generated: the types of `nui` dist/ generated by `nexus build`: the page nexus/ generated by `nexus build`: the Lua bridge vite.config.ts tsconfig.json package.jsonweb/components/ and web/lib/ are common places for components and modules, but only
web/screens/, web/contract.ts and web/mock.ts have a fixed meaning.
nexus create <name>
Section titled “nexus create <name>”npm create nexus-ui <name> runs this command from the newest release, with nothing installed
beforehand.
Copies the resource template into a new folder <name> and fills in the name. The name may use
letters, digits, _ and -, and starts with a letter. The folder must not exist yet, or be
empty.
The result works as it is: one screen, one call with its server handler, a mock and a locale.
After npm install and npm run build, ensure <name> starts it, and /<name> in game opens
the screen.
nexus dev
Section titled “nexus dev”Starts the Vite dev server and prints its address, http://localhost:5173/ unless the port is
taken.
In a browser, the page talks to a mock host instead of FiveM. A bar at the bottom of the window has:
- one button per screen, which opens it with the props from
web/mock.tsor closes it; phone appandtablet app, when a screen has asurface: each shows or hides the app in a frame of the size LB gives it, 390 by 844 for the phone and 1280 by 800 for the tablet;- one button per world screen, under its name: it shows or hides the screen in a frame of its
size, opened with the props underworldsinweb/mock.ts. The mouse works in the frame as it does on a prop, the page draws its own cursor, and the keyboard is sent to it as the messages a display gets in game; - the buttons that components add with
dev.action(label, fn)and the mock adds withaction; log, which shows what crossed the bridge: calls with their answer and duration, pushes, client messages and state updates.
Calls are answered by the handlers in web/mock.ts, and validated and rate-limited exactly as
the server would. See The mock.
Saving a .nexus file mounts the open screen again with the props it had. A change to styles
only swaps the stylesheet. After a full reload, the screens that were open are opened again.
A problem in web/contract.ts is printed in the terminal each time the file is saved.
The server stops with whatever started it. When npm run dev is ended from outside, by a task
runner or a closed editor, Windows ends npm and leaves its children running. nexus dev watches
the five processes above it and exits when one of them is gone, so the port is free again.
| Option | |
|---|---|
--port <n> |
The port to listen on. Default: 5173. |
--game |
See below. |
nexus dev --game
Section titled “nexus dev --game”Runs the same server for the page inside FiveM, so that hot reload works in game:
- It writes the Lua bridge into
nexus/, asnexus builddoes. - It changes
ui_pageinfxmanifest.luato the address of the dev server. The original line is kept in a comment on the same line. - It tells you to run
refreshandensure <name>in the server console, which loads the changed manifest.
While it runs, a change to web/contract.ts or to a <screen> tag updates nexus/contract.lua
or nexus/screens.lua, and the terminal says to ensure the resource again, because Lua is
only loaded when a resource starts. A contract or a screen that is broken at the moment leaves
the last working bridge in place.
When you stop it with Ctrl+C, ui_page is put back. If the process was killed instead, the next
nexus dev or nexus build puts it back.
With --game the port must be free, because FiveM is given one address. If it is taken, the
command says so and suggests another.
The game and the dev server must run on the same machine, since the page is loaded from
localhost. In game the page talks to the real Lua, not to the mock.
nexus build
Section titled “nexus build”-
Loads
web/contract.ts. A contract the bridge could not enforce stops the build. -
Compiles every component. An error stops the build, with the file, the line, a code frame and what to write instead.
-
Builds the page into
web/distfor Chromium 103: newer syntax is rewritten, and each screen becomes its own file that is loaded when the screen is first opened. An app in LB Phone or LB Tablet is the same page, and so is a world screen, so a project with either builds once. What only world screens need is a file of its own, which a page without one never loads. Calls todev.actionare taken out. -
Writes
nexus/client.lua,nexus/server.lua,nexus/contract.luaandnexus/screens.lua. A file that would not change is not touched. -
Checks
fxmanifest.lua:lua54 'yes';ui_page 'web/dist/index.html';filescovers everything inweb/dist;nexus/contract.lualoads on both sides before the two runtimes;nexus/screens.luaand thennexus/client.luaload on the client;nexus/server.lualoads on the server;- no script list loads a bridge file on the wrong side.
When something is missing, the build fails and prints the exact lines to add.
The build also runs the checks between apps, world screens, the contract and Lua that
nexus check runs, and stops on an error among them. It lists the apps and the
world screens it found.
The four files in nexus/ and the folder web/dist are what a release has to ship, next to your
own Lua and fxmanifest.lua. Both are build output that the template’s .gitignore leaves out
of the repository, so a release made from a fresh clone has to be built first. node_modules
and the sources under web/ are not needed on a server.
nexus check
Section titled “nexus check”Finds what is wrong without running anything, and prints every problem with its file, line, column and a code frame. It exits with 1 when there is at least one error, which makes it suitable for CI.
- The contract:
web/contract.tsloads and can be enforced. - Components: everything the compiler reports, such as unclosed tags and blocks, unknown
directives, a
bind:on something that cannot be bound. - Chromium 103: CSS and JavaScript that FiveM’s browser cannot run, in components and in
plain
.css,.tsand.jsfiles underweb/. - Apps: a screen with a
surfacethat the contract gives props (an error: LB opens an app and passes nothing along), asurfacethat no Lua registers withNexus.app(a warning: LB will not show it), and aNexus.appfor a surface no screen declares (an error). Lua is searched for the call, not parsed. - World screens: one without a
sizeor withfocus,keep-input,closeorlayer, aNexus.openorNexus.closewith the name of a world screen, and aNexus.worldwith a name that is not one. The last two are reported at their line in the Lua file. A name that Lua builds at run time is not seen. - Types: the scripts, the expressions in the templates, the props passed to components, and
the
.tsfiles underweb/. A problem in a component is reported at its line in the.nexusfile. Calls, pushes, client messages, state and the props of the screens the contract names are checked against it. The parameter of an inline handler is typed from its element and event:on:input={(event) => ...}on an<input>needs no annotation.
error web/screens/Shop.nexus:14:26: Property 'mesage' does not exist on type '{ message: string; }'. Did you mean 'message'? (TS2551)
12 | try { 13 | const result = await nui.call('shop:buy', { item: props.item, amount: amount.value });> 14 | reply.value = result.mesage; | ^ 15 | } catch (error) {Type checking uses the TypeScript installed in the resource (npm install --save-dev typescript)
and the resource’s tsconfig.json. Its include and exclude decide which .ts files are
checked, so a folder of scripts that run in Node can be left out. Built-ins are checked against
what Chromium 103 has, whatever lib the tsconfig.json names. import.meta.glob and
import.meta.env are typed as Vite provides them.
nexus check also brings web/nexus-contract.d.ts up to date.
What it does not check is listed in the roadmap.