A build-time compiler for FiveM Lua resources. Scans client/ and server/ directories, resolves require() and lib.require() calls via AST analysis, and outputs single-file bundles that inject all modules into package.preload.
npm i -g fivem-bundler
# or without installing
npx fivem-bundler <resource-root> [output-dir]fivem-bundler <resource-root> [output-dir]output-dir defaults to <resource-root>/dist.
Examples:
# Bundle resource in current directory, output to ./dist
fivem-bundler .
# Bundle with explicit output path
fivem-bundler ./my-resource ./my-resource/dist
# Use npx without installing
npx fivem-bundler ./my-resource
# Mark framework adapters as lazy (bundled but not auto-executed)
fivem-bundler ./my-resource --lazy "server/frameworks/**"
fivem-bundler ./my-resource --lazy "client/legacy.lua"| Flag | Description |
|---|---|
--lazy <pattern> |
Mark matching files as lazy. Can be used multiple times. |
--debug |
Enable debug output (stack traces on error) |
--version, -v |
Show version number |
--help, -h |
Show help message |
The bundler supports two discovery modes, chosen automatically:
my-resource/
├─ client/
│ ├─ main.lua
│ └─ modules/
│ └─ targeting.lua
├─ server/
│ ├─ main.lua
│ └─ modules/
│ └─ database.lua
└─ shared/ # optional, bundled into both client and server
└─ config.lua
All .lua files under client/, server/, and shared/ are discovered recursively. No fixed layout within those directories is required.
When client/ and server/ directories don't exist, the bundler reads fxmanifest.lua to determine which files belong to which runtime:
my-resource/
├─ fxmanifest.lua
├─ main_client.lua
├─ main_server.lua
├─ config.lua
└─ modules/
└─ utils.lua
-- fxmanifest.lua
client_scripts { 'main_client.lua', 'modules/*.lua' }
server_scripts { 'main_server.lua', 'modules/*.lua' }
shared_script 'config.lua'External resource scripts (prefixed with @) are automatically skipped.
Output:
dist/
├─ client.lua
└─ server.lua
Point your fxmanifest.lua at these files:
client_script 'dist/client.lua'
server_script 'dist/server.lua'Module IDs are derived from file paths relative to the side root:
| File | Module ID |
|---|---|
client/main.lua |
"main" |
client/modules/targeting.lua |
"modules.targeting" |
server/modules/database.lua |
"modules.database" |
Require by module ID — both forms are supported:
local targeting = require("modules.targeting")
local targeting = lib.require("modules.targeting")Arguments must be static string literals. Dynamic requires (require(someVar)) are a hard error.
Each bundle file contains:
package.preloadassignments — every module is wrapped in a function and registered:package.preload["modules.targeting"] = function() -- original module source end
- Entry file execution — files not required by any other module are executed directly at the bottom, in deterministic order.
Lua's built-in require() checks package.preload automatically, so no custom loader is needed. If ox_lib is loaded, lib.require() also resolves from package.preload. Circular dependencies are supported — Lua (and ox_lib) handle them at runtime.
Some files should be bundled (available via require()) but not auto-executed as entry points. These are called lazy modules — they only run when another module explicitly requires them. This is useful for framework adapters, optional features, or conditional code paths.
Place a bundler.config.json in your resource root:
{
"outputDir": "dist",
"lazy": {
"folders": ["**/frameworks/**"],
"files": ["client/legacy.lua"]
}
}fivem-bundler ./my-resource --lazy "server/frameworks/**"
fivem-bundler ./my-resource --lazy "client/optional.lua" --lazy "server/adapters/**"Patterns without a file extension are treated as folder patterns. CLI flags merge with config file values — they don't replace them.
| Behavior | Normal files | Lazy files |
|---|---|---|
Bundled into package.preload |
Yes | Yes |
| Auto-executed as entry point | Yes (if not required by others) | No |
Available via require() |
Yes | Yes |
- Node.js >= 18 (or Bun)
ox_lib is not required — bundles use package.preload, which works with Lua's built-in require(). If your resource uses ox_lib, load it as usual:
shared_script '@ox_lib/init.lua'Both require() and lib.require() resolve from package.preload automatically.
bun install
bun run build # Compile TypeScript
bun test # Run all tests (64 tests)
bun run typecheck # Type-check without emit- Fork the repository
- Create a feature branch
- Make your changes
- Run
bun test— all tests must pass - Run
bun run build— must compile cleanly - Open a pull request
MIT