@luau-rs/luau
v0.739.0
Published
Luau for JavaScript runtimes through WebAssembly
Readme
@luau-rs/luau
The Luau runtime and compiler for JavaScript runtimes, built with WebAssembly.
It is ESM-only and supports browsers, Node.js, Deno, Bun, and workerd.
Version 0.739.x targets Luau v739; patch versions contain luau-rs fixes.
npm install @luau-rs/luauLuaWorker
LuaWorker runs each execution in a fresh sandboxed state off the calling
thread. Use it for untrusted scripts.
import { LuaWorker } from '@luau-rs/luau/worker';
using lua = new LuaWorker();
lua.addEventListener('print', (event) => console.log(event.text));
const result = await lua.execute('print("hello")\nreturn 6 * 8');
if (!result.ok) throw new Error(result.error?.message);
console.log(result.values); // [48]Memory, interrupt, output, and result limits are enforced in the worker. Their
defaults are exported as DEFAULT_EXECUTION_OPTIONS. execute,
executeBytecode, compile, and dump accept an AbortSignal. With
timing: true, they return { result, durationMs }. Calls to print dispatch
print events.
Pass static modules through ExecuteOptions.modules. A constructor-level
resolveModule loads missing modules and returns source. Its returned name
is the cache key and the base for nested imports. Use serveLuaWorker from
@luau-rs/luau/worker for custom workers whose resolver must return live Lua
values or whose state needs host callbacks and userdata. Await it at the top
level of the worker module.
A successful execution remains available for asynchronous worker callbacks
until another execution replaces it or terminate() is called.
Lua
Lua owns a persistent state in the current JavaScript realm. Its synchronous
methods block that realm.
import { Lua } from '@luau-rs/luau';
const lua = await Lua.create({ sandbox: true });
const twice = lua.createFunction((value) => value * 2, { args: [Number] });
lua.globals.set('twice', twice);
console.log(lua.execute('return twice(18)')); // [36]An argument decoder array describes one callback signature, so
[String, Boolean] accepts two parameters. Use an outer array for overloads:
[[String], [Number, fromLua.option(String)]]. Complete signatures are tried in
order. fromLua.array(Number) describes one array-valued parameter. The same
forms are accepted by createFunction, @method, and @userdata.
Use the async methods for Promise-returning host callbacks. Direct module
resolvers may return source or values owned by the same Lua state. Untyped
callbacks receive live handles such as LuaTable; fromLua defines argument
conversion and intoLua selects ambiguous Lua representations. See the
examples
for analysis, conversions, and userdata.
Bytecode
compile returns a Uint8Array accepted by executeBytecode. Bytecode must be
trusted output from the same Luau release. Invalidate stored bytecode when
upgrading this package.
LuaWorker.executeBytecode preserves its input by default. { transfer: true }
transfers an array that spans its entire ArrayBuffer, detaching that buffer
and all of its views.
Analysis
@luau-rs/luau/analysis provides a persistent module workspace for checking,
linting, autocomplete, type queries, and inferred annotations. Source positions
use zero-based lines and UTF-16 columns.
import { AnalysisWorker } from '@luau-rs/luau/analysis/worker';
using analysis = new AnalysisWorker({ mode: 'strict' });
analysis.setModule('main.luau', 'local value: number = "wrong"');
const result = await analysis.check('main.luau');
console.log(result.diagnostics);AnalysisWorker keeps checking off the calling thread and accepts an
AbortSignal on queries. A worker accepts one query at a time; separate workers
own separate workspaces and caches. Import Analysis from
@luau-rs/luau/analysis for synchronous queries in the current realm. Neither
entrypoint is included by the runtime imports.
Use checkModules to check several roots together. fragmentAutocomplete
accepts updated source while preserving the last checked module. Definitions
may be loaded globally or into an environment selected by each module.
The worker's resolveModule option uses the same source resolver contract as
LuaWorker and may load nested dependencies asynchronously.
Definitions
Export the globals exposed by a TypeScript runtime and generate a definition file for analysis:
export type LuauGlobals = typeof globals;luau-bindgen src/runtime.ts --out runtime.d.luauThe default export name is LuauGlobals; use --export to select another.
The command requires TypeScript in the project. @luau-rs/luau/definition
also provides the declaration model directly.
Project types and immediate parameter and return types are expanded. Deeper
library types stay opaque; repeat --expand TypeName when their members are
needed.
Generic globals are preserved. Finite string and boolean constraints on extern
members become overloads; other type parameters are widened to any.
JS interop
jsInterop is an allowlist of worker globals:
const lua = new LuaWorker({ jsInterop: ['URL', 'WebSocket'] });Constructors use .new, and host-provided objects are read-only. Instances
constructed by Lua are writable. Reflection, dynamic-code constructors, worker
controls, and global aliases are blocked. Direct states use the same allowlist:
exposeJsGlobals(lua, ['URL', 'WebSocket']);exposeJsGlobals also accepts a custom global object and access filter.
Runtime resolution
The root import selects the runtime-specific build. Runtimes that import
WebAssembly as a WebAssembly.Module can use @luau-rs/luau/wasm-module
explicitly. Rust applications should use the
luau crate.
