Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HTMLuau

An HTML, CSS and JavaScript engine written in Luau, for Roblox.

Not a wrapper around Roblox's UI system — a real rendering pipeline. HTML is tokenized and parsed into a DOM, CSS cascades onto it, a layout engine computes geometry, and a painter emits Roblox Instances. JavaScript runs in an interpreter written from scratch, with the DOM bound to it, so a page can drive its own interface.

local HTMLuau = require(game.ReplicatedStorage.HTMLuau)

local app = HTMLuau.app([==[
    <style>
        .card { width: 260px; padding: 16px; border-radius: 10px;
                background: #fff; border: 1px solid #d8dee9; }
        .card h2 { margin: 0 0 8px 0; font-size: 17px; }
        .row { display: flex; gap: 8px; }
        .row button { flex: 1; }
    </style>

    <div class="card">
        <h2>Hello</h2>
        <p id="status">Not clicked yet.</p>
        <div class="row">
            <button id="go">Click me</button>
        </div>
    </div>

    <script>
        let count = 0;
        document.getElementById('go').addEventListener('click', () => {
            count++;
            document.getElementById('status').textContent = `Clicked ${count} times`;
        });
    </script>
]==], { parent = playerGui.ScreenGui })

That renders, lays out, paints, and responds to clicks — with no Luau in the page.

557 tests, run headlessly against the upstream Luau interpreter with a Roblox API shim. Every feature listed in docs/SUPPORTED.md is covered by them.

📖 Read the wiki — installation, a first page, the DOM API, calling Luau from a page, networking, porting an existing CodePen, and the limits. Source lives in wiki/; ./tools/publish-wiki.sh pushes it.


Install

Rojo

git clone https://github.com/Alexpoopy1/HTMLuau
cd HTMLuau
rojo serve

Syncs to ReplicatedStorage.HTMLuau, with the examples alongside it.

Drag-in, no tooling

python3 tools/bundle.py --mode library --out dist/HTMLuau.luau

Produces one self-contained file. Paste it into a ModuleScript named HTMLuau in ReplicatedStorage and require it. ./tools/check-bundle.sh verifies that build actually runs, so it is never shipped untested.


What you get

HTML — spec-shaped tokenizer with RAWTEXT and RCDATA handling, a tree builder that repairs malformed markup (implied end tags, void elements, synthesised html/head/ body), and ~250 character references.

DOM — the node tree, querySelector with a full selector engine, classList, dataset, innerHTML, and events with real capture/target/bubble dispatch.

CSS — cascade with correct origin and !important precedence, inheritance, 148 named colours, media queries, and the shorthand set. element.style reads and writes through to the live attribute.

Layout — block with margin collapsing and the full box model, inline with real line breaking and baseline alignment, flexbox with both axes, and absolute/relative positioning.

Rendering — Roblox Instances with pooling, so a repaint mutates rather than rebuilds. Borders, radii, gradients, opacity and scrolling map onto UIStroke, UICorner, UIGradient, CanvasGroup and ScrollingFrame.

JavaScript — lexer, parser and tree-walking interpreter. Closures, classes, destructuring, template literals, optional chaining, try/catch, Promises, a regex engine, and the standard built-ins. Bound to the DOM with stable wrapper identity, plus setTimeout/setInterval/requestAnimationFrame on a proper event loop.

NetworkingHttpService with redirects, retries and caching, and a client bridge guarded by an allowlist, per-player rate limits and size caps. fetch and XMLHttpRequest for page scripts.


Calling Luau from a page

The recommended path. Register capabilities; the page calls them.

local app = HTMLuau.app(html, { parent = screenGui })

app.runtime:register("shop", {
    balance = function()
        return leaderstats.Coins.Value
    end,
    buy = function(itemId)
        return purchase(player, itemId)   -- validate here, as with any player input
    end,
})
document.getElementById('buy').addEventListener('click', () => {
    if (shop.balance() >= 100) shop.buy('sword');
});

Nothing arbitrary executes — the page reaches exactly the functions you chose. See docs/SECURITY.md for the other two paths and when they are appropriate.


Fetching over HTTP

HttpService is server-only in Roblox, so client code goes through a bridge. Nothing is permitted until you name it.

-- Server
HTMLuau.serve({
    allowlist = HTMLuau.Net.allowlist({ hosts = { "api.yourgame.com" } }),
})
-- Client
local browser = HTMLuau.browser({
    parent = screenGui,
    allowlist = HTMLuau.Net.allowlist({ hosts = { "api.yourgame.com" } }),
})
browser:navigate("https://api.yourgame.com/leaderboard")

Private and loopback addresses are refused even if listed. Read docs/SECURITY.md before pointing this at anything.


Third-party JavaScript libraries

Register the source locally, or fetch it from an allowlisted CDN:

app.runtime:loadLibrary("mini", require(Libraries.mini))          -- local
app.runtime:libraries():loadFromUrl("https://cdn.example/x.js")   -- CDN

Libraries that stay within ES5 plus common ES2015 have a real chance of working. Check before you commit to one:

local blockers = HTMLuau.Script.LibraryLoader.probe(source)
-- {} means nothing obvious is in the way; a non-empty list names what is.

The usual blockers are generators, Symbol, Proxy, Map/Set, typed arrays and async/await. This engine is a large documented subset of JavaScript, not V8 — docs/SUPPORTED.md has the full list.


Known limits

Stated up front rather than discovered later:

  • Remote images cannot render. ImageLabel.Image only accepts Roblox asset URLs, so <img src="https://…"> is unrenderable on this platform. Supply an assetMap of URL → asset id for images you have uploaded; anything else gets an alt-text placeholder.
  • No CSS Grid, floats, or transitions. Flexbox and block layout are solid.
  • No calc() or var(). A length using either resolves to 0 and collapses the element — substitute literal values.
  • No async/await syntax. Promises work; the syntax is not parsed yet.
  • Strings are byte sequences. Identical to a browser for ASCII; astral-plane characters count as multiple units.

Examples

examples/movable-card is the reference: a draggable card written entirely in HTML, CSS and JavaScript. Dragging it exercises every layer of the engine in one gesture, which is why the test suite drives it end to end.

local Example = require(ReplicatedStorage.HTMLuauExamples["movable-card"])
Example.mount(playerGui.ScreenGui)

Development

./tools/run-tests.sh          # full suite
./tools/run-tests.sh css      # filter by substring
./tools/check-bundle.sh       # verify the drag-in build
python3 tools/gen-docs.py     # regenerate the CSS table in SUPPORTED.md

The suite builds the upstream Luau CLI on first run and executes against a Roblox API shim, so no Studio is needed. docs/ARCHITECTURE.md explains how the pipeline fits together and why the significant decisions went the way they did.

About

HTMLuau is an advanced system that builds HTML directly into Roblox Luau. Also if you like this please ⭐️ this repo!

Resources

Security policy

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages