Web export
Compile to WebAssembly and WebGPU, host it on any static file server, and keep the download small.
The web target compiles the runtime to WebAssembly and renders through WebGPU, falling back to WebGL 2. Output is static files — no server-side runtime — so it hosts on Cloudflare Pages, GitHub Pages, S3 or anything else that serves files.
The web target is in beta. Rendering, input, audio and networking are complete; the gaps are listed under Limitations.
Build#
luauengine build --target web --releasebuild/web/
├── index.html
├── mygame.js # loader
├── mygame.wasm # runtime
├── mygame.lue # your game
├── assets/ # streamed chunks (if streaming is enabled)
└── sw.js # optional service workerServe it locally:
luauengine serve --target web --port 8080Configuration#
[targets.web]
base-url = "/"
compression = "brotli" # brotli | gzip | none
threads = true # SharedArrayBuffer; needs COOP/COEP headers
canvas-id = "game"
service-worker = true
initial-memory = 512 # MB
max-memory = 2048Embedding in a page#
The generated index.html is a starting point. To embed in your own page:
<canvas id="game" width="1280" height="720"></canvas>
<script src="/mygame.js"></script>
<script>
LuauEngine.start({
canvas: document.getElementById('game'),
bundle: '/mygame.lue',
onProgress: (loaded, total) => {
document.getElementById('bar').style.width = (loaded / total * 100) + '%';
},
onReady: () => console.log('running'),
});
</script>Required headers#
If threads = true, the browser needs cross-origin isolation for SharedArrayBuffer:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corpOn Cloudflare Pages or Workers, a _headers file does it:
/*
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: cross-originWithout those headers the runtime falls back to single-threaded mode automatically — slower, but it still runs.
Download size#
Download size decides whether anybody plays. Two levers matter most.
Stream assets rather than packing them:
[assets.delivery]
mode = "stream"
base-url = "/assets/"
preload = ["textures/ui/*", "audio/ui/*", "models/player.glb"]Compress aggressively for the web target:
[assets.textures.web]
format = "astc"
max-size = 1024
[assets.audio.web]
codec = "opus"
bitrate = 64A trimmed runtime is around 8 MB Brotli-compressed. Check where the rest goes:
luauengine build --target web --report-sizeWeb-specific APIs#
if Engine.Platform == Enum.Platform.Web then
print(Engine.Web.URL)
print(Engine.Web.QueryParams.room) -- ?room=abc123
Engine.Web:SetTitle("My Game — Level 3")
-- localStorage-backed; Engine.Storage uses this transparently too
Engine.Web.LocalStorage:Set("save", saveData)
local save = Engine.Web.LocalStorage:Get("save")
Engine.Web:RequestFullscreen()
Engine.Web:LockPointer()
endLimitations#
- No filesystem.
Engine.Storageis backed bylocalStorageand IndexedDB, with the browser's quota (typically 5–50 MB). - Audio needs a gesture. Browsers block audio until the user interacts. The runtime queues playback until the first click or key press.
- Pointer lock needs a gesture too — call
LockPointer()from an input handler, not on load. - No raw sockets. Networking is WebSocket or WebRTC only. A dedicated server must expose a WebSocket endpoint; see Dedicated servers.
- Memory ceiling. 32-bit browsers cap at 2 GB, and
max-memoryabove 2048 needs thememory64proposal. - Future lighting requires WebGPU; the WebGL 2 fallback downgrades to
ShadowMap.
Deploying to Cloudflare Pages#
luauengine build --target web --release
npx wrangler pages deploy build/web --project-name my-gamePut the _headers file above in build/web/ — set [targets.web] extra-files = ["_headers"] and it is copied into every build.