A library for rendering HTML via ServerSide Rendering
  • Luau 96.6%
  • Nix 2%
  • Shell 1.4%
Find a file
reinitialized 9950dd214f
fix: move entry logic to src/init.luau for correct alias resolution
BleuMoon's init.luau parent-directory context resolution causes @src
aliases in root init.luau to resolve against the parent directory's
.luaurc instead of the package's own .luaurc. This makes @src point to
the consumer project's src/ instead of the package's src/.

Fix: move all entry logic to src/init.luau where the parent-directory
context resolves to the package root (which has the correct .luaurc).
Root init.luau becomes a thin proxy: require("./bleurender/src").
2026-03-19 17:10:11 -05:00
.forgejo/workflows release 0.2.0 2026-03-11 16:07:26 -05:00
.github release 1.0.0-alpha1 2026-02-20 17:20:56 -06:00
.vscode release 0.2.0 2026-03-11 16:07:26 -05:00
docs release 0.2.2 2026-03-18 20:10:55 -05:00
scripts release 0.2.0 2026-03-11 16:07:26 -05:00
src fix: move entry logic to src/init.luau for correct alias resolution 2026-03-19 17:10:11 -05:00
tests release 0.2.1 2026-03-11 16:59:54 -05:00
.editorconfig Initial commit 2026-02-20 15:25:46 -06:00
.gitignore release 0.2.0 2026-03-11 16:07:26 -05:00
.luaurc release 0.2.2 2026-03-18 20:10:55 -05:00
bleumoon.lock release 0.2.2 2026-03-18 20:10:55 -05:00
bleumoon.toml fix: move entry logic to src/init.luau for correct alias resolution 2026-03-19 17:10:11 -05:00
CHANGELOG.md fix: move entry logic to src/init.luau for correct alias resolution 2026-03-19 17:10:11 -05:00
flake.lock release 0.2.2 2026-03-18 20:10:55 -05:00
flake.nix release 0.2.2 2026-03-18 20:10:55 -05:00
init.luau fix: move entry logic to src/init.luau for correct alias resolution 2026-03-19 17:10:11 -05:00
LICENSE Initial commit 2026-02-20 15:25:46 -06:00
README.md release 1.0.0-alpha1 2026-02-20 17:20:56 -06:00
stylua.toml Initial commit 2026-02-20 15:25:46 -06:00

BleuRender

A Server-Side Rendered HTML framework for BleuMoon. Includes an HTML builder, template engine with layout inheritance, component system, HTTP server with middleware, and static file serving.

Prerequisites

Tip: If you have Nix with flakes enabled, both tools are provided automatically via nix develop.

Getting Started

git clone https://git.ds.reinitialized.net/bleupigs.club/bleurender.git
cd bleurender
nix develop

Without Nix

  1. Install BleuMoon and StyLua manually.

  2. Clone and install dependencies:

    git clone https://git.ds.reinitialized.net/bleupigs.club/bleurender.git
    cd bleurender
    bleumoon pkg install
    

Quick Start

HTML Builder

Build HTML programmatically with h():

local html = require("@pkg/bleurender/src").html

local page = html.h("div", { class = "container" }, {
    html.h("h1", {}, { "Hello, World!" }),
    html.h("p", {}, { "Built with BleuRender." }),
})

print(html.renderToString(page))
-- <div class="container"><h1>Hello, World!</h1><p>Built with BleuRender.</p></div>

Templates

Write templates with a Luau-native syntax:

{@ extends "layouts/base" }

{@ block "content" }
  <h1>Welcome, {= user.name }</h1>

  {# if user.isAdmin }
    <p>Admin panel</p>
  {# else }
    <p>Regular dashboard</p>
  {/# if }

  <ul>
    {# each items as item, idx }
      <li>{= idx }. {= item.name }</li>
    {/# each }
  </ul>
{/@ block }

Components

Register reusable function components:

local bleurender = require("@pkg/bleurender/src")

bleurender.components.registerComponent("Button", function(props)
    return bleurender.html.renderToString(
        bleurender.html.h("button", { class = props.class or "btn" }, { props.label or "Click" })
    )
end)

Use in templates:

{@ component "Button" label="Submit" class="btn-primary" }

Server

Create an HTTP server with routing and middleware:

local bleurender = require("@pkg/bleurender/src")
local Server = bleurender.server

local app = Server.new({
    port = 3000,
    templateDir = "./templates",
})

-- Built-in middleware
app:use(Server.logger())
app:use(Server.errorHandler())

-- Routes
app:get("/", function(_req, res)
    res:render("index", { title = "Home", message = "Hello!" })
end)

app:get("/api/data", function(_req, res)
    res:json({ status = "ok", data = { 1, 2, 3 } })
end)

-- Static files
app:use(Server.static("./public"))

app:listen()

Template Syntax Reference

Syntax Description
{= expr } Escaped output (HTML-safe)
{! expr } Raw output (no escaping)
{# if cond }...{/# if } Conditional
{# elseif cond } Else-if branch
{# else } Else branch
{# each list as item, idx }...{/# each } Loop with optional index
{@ extends "layout" } Inherit from layout
{@ block "name" }...{/@ block } Named block
{@ include "partial" } Include partial
{@ component "Name" k=v } Invoke component
{-- comment --} Template comment (stripped)

Condition expressions

  • Simple: {# if loggedIn }
  • Negated: {# if not loggedIn }
  • Comparison: {# if user.role == "admin" } (supports ==, ~=, >, <, >=, <=)

Dot-path resolution

Expressions like {= user.address.city } are resolved through the scope chain, walking through nested table keys.

API Overview

local bleurender = require("@pkg/bleurender/src")

-- HTML builder
bleurender.html.h(tag, attrs?, children?)
bleurender.html.renderToString(vnode)
bleurender.html.renderFragment(vnodes)
bleurender.html.rawHtml(str)
bleurender.html.isVNode(value)

-- Templates
bleurender.template.compile(source, options?)
bleurender.template.render(source, data, options?)
bleurender.template.renderFile(filePath, data, options?)
bleurender.template.createCache(options?)

-- Components
bleurender.components.registerComponent(name, fn)
bleurender.components.getComponent(name)
bleurender.components.hasComponent(name)
bleurender.components.unregisterComponent(name)
bleurender.components.clearRegistry()
bleurender.components.listComponents()

-- Server
bleurender.server.new(options)
bleurender.server.static(root, options?)
bleurender.server.logger()
bleurender.server.compression()
bleurender.server.errorHandler()
bleurender.server.cookieParser()

Running Tests

bleumoon run tests/init

Formatting

stylua --check .   # check
stylua .           # apply

Project Structure

├── bleumoon.toml              # Project manifest & dependencies
├── flake.nix                  # Nix flake for reproducible dev env
├── CHANGELOG.md               # Version history
├── docs/
│   └── architecture/
│       └── bleurender-design.md  # Architecture design document
├── src/
│   ├── init.luau              # Public API entry point
│   ├── main.luau              # Demo application
│   ├── html/
│   │   ├── init.luau          # h(), renderToString(), renderFragment()
│   │   ├── elements.luau      # VNode type, void elements, boolean attrs
│   │   └── attributes.luau    # Attribute serialization
│   ├── template/
│   │   ├── init.luau          # compile(), render(), renderFile()
│   │   ├── parser.luau        # Template tokenizer / AST
│   │   ├── compiler.luau      # AST → render function
│   │   ├── context.luau       # Scope chain for variables
│   │   └── cache.luau         # Template cache with mtime invalidation
│   ├── components/
│   │   └── init.luau          # Component registry
│   ├── server/
│   │   ├── init.luau          # Server.new(), middleware factories
│   │   ├── router.luau        # Route matching (:param, *wildcard)
│   │   ├── request.luau       # Request wrapper
│   │   ├── response.luau      # Response builder
│   │   ├── middleware.luau    # Pipeline composition
│   │   └── static.luau        # Static file serving
│   └── utils/
│       └── init.luau          # MIME types, path helpers, parsing
└── tests/
    ├── init.luau              # Test runner
    ├── html_test.luau         # HTML builder tests
    ├── template_test.luau     # Parser & render tests
    ├── context_test.luau      # Scope chain tests
    ├── components_test.luau   # Component registry tests
    └── server_test.luau       # Router, middleware, request, response tests

License

This project is licensed under the MIT License.