@oneshot101/luau-bundler
v0.4.13
Published
Bundles multiple Luau/Lua source files into a single self-contained script
Readme
Overview
@oneshot101/luau-bundler bundles a directory of .lua and .luau modules into a single Lua file that can run through loadstring() or any environment that needs one pasted/executed script. It walks the source tree, wraps each module in a loader, generates compatible require() aliases, and emits one self-contained script that starts at an entry module.
The package is part of the rbxdev-ls monorepo and is used by the Roblox executor bridge tooling to ship modular Luau source as one runtime script.
Features
- Single-file output — Converts a folder of Luau/Lua modules into one script
- Require shim — Preserves module-style
require("path/to/module")calls inside the bundle - Rojo project support — Reads a
default.project.jsonand usestree.$pathas the source directory - Deterministic builds — Sorts discovered files before emitting output
- Entry module control — Starts from
initby default, or any custom module name - Header support — Prepends a generated comment header for provenance/versioning
- CLI and API — Use it from npm scripts or directly from Node/Bun tooling
Table of Contents
Quick Start
Bundle a source folder into one Lua file:
npx -y @oneshot101/luau-bundler --src src --out dist/bundle.luaBundle from a Rojo project:
npx -y @oneshot101/luau-bundler --project default.project.json --out dist/bundle.luaUse a custom entry module and header:
npx -y @oneshot101/luau-bundler --src src --out dist/bridge.lua --entry main --header "rbxdev executor bridge"How It Works
The bundler recursively collects every .lua and .luau file under the source directory. Each file is emitted into a _modules table as a lazy loader, then a local require() shim resolves module paths at runtime. The output ends by requiring the configured entry module and returning its value.
The generated require() first checks bundled modules. If a path is not bundled, it falls back to the original runtime require when available. That lets bundled code still use runtime-provided modules when the host environment supports them.
The final output is wrapped like this:
return (function(oldRequire, ...)
-- bundled module registry and require shim
return require("init")
end)(require or function() end, ...)Installation
Prerequisites
- Node.js ≥ 18 —
node --versionto verify - Source files ending in
.luaor.luau
npx (recommended)
No installation required:
npx -y @oneshot101/luau-bundler --src src --out dist/bundle.luaGlobal install
If you prefer a persistent CLI:
npm install -g @oneshot101/luau-bundlerThen run:
luau-bundler --src src --out dist/bundle.luaCLI
luau-bundler --src <dir> --out <file> [--entry <name>] [--header <text>]
luau-bundler --project <rojo.project.json> --out <file> [--entry <name>] [--header <text>]| Option | Required | Default | Purpose |
| ----------- | -------- | ------- | ------------------------------------------------------------ |
| --src | Yes* | none | Source directory containing .lua and .luau files |
| --project | Yes* | none | Rojo project file; uses tree.$path as the source directory |
| --out | Yes | none | Output file path for the generated single-file bundle |
| --entry | No | init | Module name to require after all modules are registered |
| --header | No | none | Text emitted as leading Lua comments in the generated output |
*Use either --src or --project.
JavaScript API
import { bundle, resolveRojoProject } from '@oneshot101/luau-bundler';
const result = bundle({
sourceDir: 'src',
entry: 'init',
header: 'generated by luau-bundler',
});
console.log(result.output);
console.log(`${result.moduleCount} modules in ${result.elapsedMs.toFixed(1)}ms`);bundle(options)
| Option | Type | Default | Purpose |
| ------------- | --------- | ------- | ---------------------------------------------------- |
| sourceDir | string | none | Directory to recursively scan for .lua and .luau |
| entry | string | init | Module name required at the end of the bundle |
| header | string | none | Comment header prepended to the output |
| passVarargs | boolean | true | Whether top-level varargs are forwarded into modules |
Returns:
| Field | Type | Description |
| ------------- | -------- | ----------------------------- |
| output | string | Bundled Lua source |
| moduleCount | number | Number of modules included |
| elapsedMs | number | Bundling time in milliseconds |
resolveRojoProject(projectPath)
Reads a Rojo project file and returns the resolved source directory from tree.$path:
const project = resolveRojoProject('default.project.json');
if (project) {
console.log(project.name, project.sourceDir);
}Module Resolution
Each file gets aliases that match common Luau module paths:
| File path | Valid require paths |
| ---------------------- | --------------------------------------------------------- |
| services/player.luau | services/player.luau, services/player |
| controllers/init.lua | controllers/init.lua, controllers/init, controllers |
Files are sorted before bundling so repeated builds produce stable output when the source files are unchanged.
Part of rbxdev-ls
This bundler is one workspace in the rbxdev-ls monorepo, which also includes:
- packages/server — The Roblox/Luau language server with type checking, completions, and diagnostics
- packages/vscode — The VS Code extension with Game Tree, Properties panel, Remote Spy, and code execution
- packages/mcp — The MCP server for connecting AI assistants to live Roblox game instances
- packages/luau-bundler — This package
License
MIT © Andrew
