Skip to main content

Lux: An Energy-Observable Web Programming Language

David J. Charlot, PhD Open Interface Engineering, Inc. — Sarasota, FL Revision 2 — June 2026


Abstract

The modern web stack consumes energy without measuring it. A typical web application depends on hundreds of JavaScript packages, compiles through several toolchains, and executes in a runtime that exposes no view into computational cost. We present Lux, a web programming language that unifies markup, style, and logic into one syntax and treats energy as an observable property of running software rather than an afterthought. Lux compiles from a single source to native code (via Cranelift, ahead-of-time and JIT) and to WebAssembly (~398 KB), ships a standard library of 1,723 pure-Rust modules covering the surface area a web application normally pulls from the JavaScript ecosystem, and requires no external package manager for common work. Its grammar is small (~80 EBNF productions) and several of its load-bearing invariants are proven with a bounded model checker rather than merely tested. Energy is reported the way a household electricity meter reports usage: as a receipt for your program’s own execution, sourced from real hardware power sensors where they exist and a calibrated per-platform model where they do not, always carrying its measurement provenance and uncertainty. We argue that the joule — physical, universal, composable — is the right unit of account for computation, and that designing a language around that unit produces software whose cost is legible to the people who write and run it.


1. Introduction

The web platform, as it exists in 2026, is the product of three decades of accretion. HTML (1993), CSS (1996), and JavaScript (1995) were designed independently, for different purposes, in different decades, and are joined by the Document Object Model — an API built for document manipulation, not application development.

The consequences are measurable:

  • A new React application installs 180+ MB of dependencies before a single line of application code is written.
  • The median web page loads 2.2 MB of JavaScript [1].
  • The JavaScript runtime (V8, SpiderMonkey, JavaScriptCore) performs just-in-time compilation, garbage collection, and dynamic dispatch on every execution — work whose energy cost is invisible to the developer.
  • The toolchain (TypeScript compiler, webpack/Vite, Babel, PostCSS, ESLint) consumes energy at development time that is never measured.

None of these systems report their energy consumption. A developer cannot answer a simple question: how many joules does this page render cost?

Lux is designed so that the question has an answer.

1.1 Contributions

  1. A web programming language with one syntax for structure, style, logic, and data.
  2. A compilation model that targets WebAssembly, native AOT, and native JIT (with hot reload) from the same source.
  3. An energy-observability layer that reports a program’s own consumption in joules, with the measurement source and uncertainty attached to every figure.
  4. A standard library of 1,723 pure-Rust modules that removes the need for an external package manager for common web work.
  5. A grammar small enough, and verified enough, that machine generation of correct programs is tractable — with several lexer and printer invariants proven by a bounded model checker.

1.2 What This Document Claims, and What It Does Not

This is a revision. The first draft (March 2026) described an energy receipt with a precision the implementation does not provide, and it omitted the verification work that had since become a real part of the system. This revision corrects both. Where a capability is built, it is described in the present tense. Where a capability is designed but not yet wired end-to-end, it is marked Status: and described as such. The intent is that a reader who opens the source finds what this document says they will.


2. Motivation: Why Energy?

2.1 The Invisible Cost of Computation

Cloud pricing abstracts energy away. When a developer deploys a function to AWS Lambda, they pay per millisecond of execution and per gigabyte of memory. The actual energy consumed — the joules dissipated as heat in a data center — is hidden behind three layers of indirection: the provider’s pricing model, the hypervisor’s resource allocation, and the runtime’s memory management.

This abstraction has consequences. Architects optimize for cost (dollars) rather than efficiency (joules). A function that costs $0.001 per invocation may consume far more energy than necessary, but the economic signal is too weak to drive optimization.

2.2 The Physical Reality

Every computation has a physical cost. An ADD on an ARM Cortex-A78 consumes on the order of a picojoule. A last-level cache miss costs nanojoules. A network round-trip costs on the order of a millijoule. These costs are set by physics; they do not move with pricing models or provider margins.

A language that reports these costs gives a developer the same visibility into computational efficiency that an ammeter gives an electrical engineer. You cannot optimize what you cannot see.

2.3 The Unit of Account

The correct unit of account for computation is the joule — not the token (LLM pricing), not the dollar (cloud pricing), not the millisecond (benchmarking). The joule is:

  • Physical — it corresponds to actual energy dissipated.
  • Universal — it applies to CPU, GPU, NPU, and network operations alike.
  • Composable — the cost of a program is the sum of the costs of its operations.
  • Comparable — one joule on an M3 Ultra is one joule on a Jetson Orin is one joule on a Raspberry Pi.

2.4 A Meter, Not a Benchmark

Lux’s energy reporting is not a comparative marketing claim and not a published benchmark. It is in-product telemetry: a program reports the energy it spent, on this machine, the way a household meter reports the kilowatt-hours this house drew. The point is legibility for the person running the software, not a leaderboard. Comparative figures, where they appear at all, are a byproduct — never the headline.


3. Language Design

3.1 Design Goals

Lux is built around five constraints:

  1. One file, one language. A .lux file contains structure, style, logic, and data. There is no split into HTML, CSS, and JS files.

  2. Sequential, familiar syntax. Lux uses indentation-based blocks (like Python), reactive signals (like SolidJS), and declarative views (like SwiftUI). A developer fluent in any of these can read Lux immediately.

  3. No null. Absence is expressed as none with option types (T?), removing the single largest category of runtime error in JavaScript.

  4. Gradual typing. Types are inferred. Annotations are available for documentation and safety but are never required.

  5. Small, unambiguous grammar. The grammar is roughly 80 EBNF productions. That is deliberate: a small grammar with few ways to be wrong makes both human and machine authorship more reliable. JavaScript’s grammar requires ~300+ productions and carries ambiguities that trip even experienced developers.

3.2 Syntax Overview

A complete Lux application:

app Counter:
  count = signal(0)

  view:
    column gap=16 padding=24:
      text "Count: {count}" size=24
      row gap=8:
        button "+" on:click -> count.update(n -> n + 1)
        button "-" on:click -> count.update(n -> n - 1)
      text "{energy.total()} J" color=gray size=12

Ten lines define a reactive counter with layout, styling, event handling, and an energy readout. The equivalent React + TypeScript + CSS implementation runs roughly 45 lines across three files plus a build configuration.

3.3 What the Parser Actually Accepts

The grammar described here is the grammar the parser implements. The following constructs are present and exercised: fn (block body and = expression body), let / let mut, app, component (with scoped styles), server (GET/POST/PUT/DELETE/PATCH routes), style blocks, type, trait, impl, use, test; control flow if/else, unless, for (with optional index binding), while, until, match with guards; expressions including signal(...), memo(...), lambdas, the pipe operator |>, string interpolation "{expr}", ranges (1..10, 1..=10), field access, indexing, await, yield, and trailing do blocks; concurrency and effects via effect:, spawn:, batch:, try/catch, throw. Pattern matching supports wildcards, bindings, literals, list patterns with ...rest, record patterns, or-patterns, ranges, and variants, and is checked for exhaustiveness.

The following are deliberately not claimed, because the parser does not implement them: an async fn declaration form (the keyword await parses, but there is no async modifier and no async executor), generic type parameters on free functions (generics exist on traits and impls only), user-defined operators, and named/keyword call arguments. Documenting this boundary is part of the contract.

3.4 Reactive Model

Lux’s reactive system is fine-grained signals, in the lineage of SolidJS and the Rust frameworks Leptos and Sycamore:

  • signal(value) creates a reactive value. Reading it inside an effect or memo registers a dependency automatically.
  • memo -> expr derives a value that recomputes only when a dependency changes.
  • effect: block runs side effects on dependency change.
  • batch: block groups updates into a single notification.

These lower onto the reactive runtime in the standard library (reactive.rs: ReadSignal<T>, WriteSignal<T>, Effect, batch(), with read-time dependency tracking and write-time invalidation).

3.5 View System

Lux views replace HTML + CSS + DOM with a declarative component model. Elements are named (not angle-bracketed), styled by properties (not classes), and composed by indentation (not closing tags):

column gap=16:
  text "Title" size=24 weight=bold
  row gap=8:
    button "Save" on:click -> save()
    button "Cancel" on:click -> cancel()

This lowers to a virtual-DOM tree with O(n) keyed diffing (vdom.rs: a VNode tree and a Patch set — Replace, InsertChild, RemoveChild, UpdateAttrs, UpdateText, ReorderChildren). Style properties lower to deduplicated CSS at build time.

3.6 Server Syntax

Lux includes server-side syntax for HTTP APIs, removing the need for Express, Fastify, or similar:

server Api port=8080:
  get "/users":
    json(db.query("SELECT * FROM users"))

A response can carry an X-Energy-Joules header reporting the energy attributed to the handler. Status: route handlers and the energy header path are implemented; energy attribution at handler granularity uses the model described in §5 rather than a per-handler hardware read.


4. Compilation Model

4.1 Pipeline

Lux source compiles through a staged pipeline:

  1. Lexing — an indentation-aware tokenizer emitting INDENT/DEDENT, with bracket-depth suppression so multi-line signatures are safe, and full string-interpolation handling.
  2. Parsing — recursive descent with precedence climbing for expressions, building a typed AST of ~60 node variants.
  3. Exhaustiveness checking — match arms and view-node matches are checked for completeness; non-exhaustive matches raise warnings carrying line/column information.
  4. Type inference — Hindley-Milner-style inference with fresh variables, scope-based environments, and unification; gradual, so annotations are optional.
  5. Lowering and optimization — AST to the code generator’s IR; view nodes lower to VDOM builder calls and style properties to CSS emission; constant folding and dead-code elimination where applicable.
  6. Code generation — Cranelift for native targets (AOT and JIT), WASM emission for the browser.

4.2 Three Backends, One Source

Lux does not use a bytecode interpreter as its delivery vehicle. The same source compiles to:

  • Native binary, via Cranelift AOT — for servers, CLI tools, and desktop applications.
  • Native JIT, via Cranelift — compiled into executable memory and hot-swapped on file change, for lux dev.
  • WebAssembly (~398 KB) — instantiated directly by the browser, with DOM glue, a signal-storage arena, and a reactive-expression table.

A tree-walking interpreter also exists (lux-runtime) and is what lux run uses for fast iteration; it is the development convenience, not the deployment target. The deployed artifact is compiled machine code, so the energy cost of a deployed Lux program is the cost of that machine code — no JIT warmup on the hot path, no garbage-collector pauses (memory is reference-counted), no interpreter dispatch.

4.3 Standard Library Compilation

The standard library is written in Rust and compiled ahead of time. When a program uses lux:chart, the compiler links the pre-compiled module. There is no runtime reflection, no dynamic module loading, and no JSON serialization on the hot path.


5. Energy Observability

This is the section the first revision overstated. What follows is what the system actually does.

5.1 A Tiered Measurement Stack

Energy is reported from the most accurate source available on the host, and every figure carries the source it came from and that source’s uncertainty. There is no single number pretending to a precision the hardware cannot deliver.

Hardware sensors, where they exist (verity-joule’s sampler):

SourceMechanismTypical uncertainty
Intel/AMD RAPL (Linux)energy_uj counters under /sys/class/powercap±5%
NVIDIA NVMLpower1_average, trapezoidal integration over wall time±10%
Apple GPU (macOS IOKit)AGXAccelerator / PerformanceStatistics deltas±20–30%
Apple NPU (ANE)best-effort utilization±30–50%
POSIX getrusageCPU time × published watts-per-core±20%
TDP fallbackTDP × wall-clock fraction±50% (worst-case bound)

Each reading is tagged with its MeasurementSource, so a receipt is honest about whether a figure came from a RAPL counter or a TDP estimate.

Calibrated model, where sensors are absent or too coarse (mgai-os-energy’s EnergyModel): a per-platform cost table maps operation classes to energy. Compute is costed in microjoules per MFLOP per compute unit; I/O in microjoules per byte per transport; idle in milliwatts per unit; and discrete operations (radio, motor, sensor, storage, display) in microjoules per op. The tables are calibrated per target — STM32N6, Jetson Orin, Jetson Thor, OpenMV AE3 — so the same program reports plausible figures whether it runs on a workstation or an embedded board.

5.2 The Resolution Floor, and Why Batching Exists

Hardware energy counters have a resolution floor — on the order of a millijoule for RAPL. A single hash or a single array append cannot be read directly; it is below the noise. Two consequences follow, and both are honest features rather than hidden caveats:

  1. Aggregate operations are measured. A render cycle, a route, a fetch, a state update — each is a unit large enough to read or model meaningfully, and these are the units the web framework’s energy layer (joule-web’s EnergyAwareWeb and WebEnergyReport) reports.
  2. Per-operation costs are calibrated, not measured. Sub-millijoule operations draw their figures from the calibrated table, validated against batched measurements: run K operations so total energy is well above the floor, subtract the idle baseline, take a median over repeats. The flowg-energy-xover harness does exactly this to validate, for example, where a matmul stops being cheaper on CPU and starts being cheaper on GPU — measured on the actual machine, not assumed.

5.3 The Energy Receipt

A Lux program produces a receipt: a structured record of where energy went. At the web-framework level the report carries the figures the framework can substantiate — total energy, operation count, and a breakdown across the units it tracks (render, route, fetch, state update):

WebEnergyReport {
  total_energy_mj:   0.342,
  total_operations:  1247,
  render_energy_mj:  0.089,
  route_energy_mj:   0.014,
  fetch_energy_mj:   0.041,
  state_energy_mj:   0.198,
}

At the subsystem level, individual operations record an EnergyReceipt { operation, consumed_uj, subsystem } and aggregate into a meter. The receipt is the program’s own statement of account: what it spent, on which units, by which measurement source. It is not a comparison against another framework.

5.4 Heterogeneous Energy Routing

The longer-term aim is for the compiler to place an operation on whichever backend costs the fewest joules — CPU, GPU, NPU — using the calibrated tables and measured crossovers. Status: the cost model and the calibration harness are built; automatic energy-optimal dispatch is partially wired (the type signatures and placement pass exist; runtime backend selection still resolves first-claim-wins rather than by cost). This is described as the destination, not as a present capability.


6. Standard Library

6.1 Scope

Lux ships 1,723 modules covering the work a web application normally assembles from the JavaScript ecosystem. They are written in Rust, compile to native and WASM, and carry energy instrumentation. They span core web primitives, UI components, 2D/3D graphics, audio, video, data visualization, rich text, cryptography, data formats, typography, ML primitives, geospatial, and developer tooling, among others. These are implementations, not placeholders: reactive.rs is a working fine-grained signal system, vdom.rs a working keyed differ, crypto.rs real SHA-256/HMAC/encoders, chart.rs real SVG chart generation.

6.2 Why Ship the Library

The JavaScript dependency model has well-documented failure modes: supply-chain attacks (event-stream, ua-parser-js, colors.js), version conflicts, bloated node_modules, and irreproducible builds. Lux ships the library with the runtime instead. A developer who installs Lux has the modules immediately, with no additional download. Because they ship together, they are audited together, share one API style and error model, are compiled ahead of time, and carry consistent energy instrumentation.

6.3 External Packages

For functionality beyond the standard library, Lux resolves Git-based packages by content hash — a model in the spirit of Nix and Go modules, giving exact reproducibility without semver resolution.


7. Verification

A small grammar is only half the reliability story. The other half is proving that the parts most likely to be wrong are right.

7.1 Bounded Model Checking

Lux’s lexer and printer carry invariants proven with Kani, a bounded model checker, rather than only sampled by tests. The proven properties include: that string escaping and unescaping round-trip across printable ASCII and control characters, including DEL (0x7F); that escaped output never contains a bare quote or a bare newline; that escaping never shrinks its input; that safe characters pass through unchanged; and that the lexer’s indentation stack is always strictly increasing and always rooted at zero after any sequence of indent/dedent operations. These are theorems over the input space within the checker’s bounds, run via cargo kani, not anecdotes.

7.2 Property-Based Testing

Beyond the proofs, a suite of property tests (proptest) asserts language-level invariants over generated inputs: that parse → emit → parse → emit is idempotent (the formatter has a fixed point); that the lexer and parser never panic on arbitrary bytes; that nesting beyond a depth limit degrades to a graceful error rather than a stack overflow; that every keyword tokenizes; and that item counts survive a round-trip. Shrunk counterexamples are archived so regressions stay fixed.

7.3 Exhaustiveness

match exhaustiveness is checked at compile time across the nine pattern forms, including the interactions that catch people out — that a guard does not make an arm a catch-all, that an or-pattern containing a wildcard is exhaustive, that covering both booleans is exhaustive while covering one is not. Gaps raise warnings with source locations.

The combination matters: the grammar is small enough to generate, the formatter has a proven fixed point so generated and hand-written code converge, and the invariants most likely to bite are checked by a solver rather than trusted.


8. Toolchain

Lux is a working toolchain, not a parser with documentation. The pieces:

  • lux (the CLI, crates/bin/lux-cli) — run, new, check, build (native / WASM / HTML / JS / PWA), test, dev (hot reload), repl, add / remove / install, lift, audit, pyinstall, list, serve, remote, tui, version.
  • lux-fmt — the canonical formatter, a standalone binary (format in place, --check, --stdin, recursive). Formatting is deterministic; the round-trip fixed point is property-tested.
  • lux-lsp — a language server providing diagnostics, completion, hover, and go-to-definition.
  • lux-mcp — an MCP server exposing six tools (lux_run, lux_check, lux_audit, lux_lift, lux_modules, lux_docs) so an AI client can run, check, audit, and read Lux.
  • lux-lift — foreign-code energy analysis and conversion across 14 languages (C, JS, TS, Ruby, Python, HTML, CSS/SCSS, PHP, Go, SQL, Dart, Rust, YAML, JSON), with an energy-recommendation engine of a dozen-plus detectors: nested-loop complexity, allocation-in-loop, string-concat-in-loop, linear search where a hash would serve, loop-invariant recomputation, missing early exits, unnecessary float precision, reallocation growth, N+1 queries, redundant length checks, deep field access in hot loops, and clone/slice in loops. Each finding carries a category, severity, and an estimated savings factor.
  • Bindings — lux-py (PyO3), lux-go (CGo/C-ABI), lux-julia (ccall), lux-node (NAPI, published as @openie/lux) — each exposing parse/emit/check, so Lux can be embedded in an existing codebase.

The lift and audit paths matter for adoption without migration: a team can point lux audit at an existing JavaScript, Python, or Rust codebase and get an energy report with no commitment to rewriting anything.


9. AI Code Generation

9.1 Grammar Size

Lux’s grammar is roughly 80 productions. For comparison:

LanguageApprox. grammar size
JavaScript (ES2024)~300 productions
TypeScript~400 productions
Python~120 productions
Lux~80 productions
HTML~80 productions
CSS~120 productions
HTML + CSS + JS~500+ productions

A generator targeting the web stack must produce correct output in three grammars at once. Lux is one.

9.2 Few Ways to Be Wrong

The grammar is designed to remove ambiguity: indentation replaces braces (no dangling-else); and/or/not replace &&/||/! (no confusion with bitwise operators); no automatic semicolon insertion; no implicit coercion; no == versus ===; no this binding rules. Fewer ways to write incorrect code means a generator that understands the grammar produces correct programs more often.

9.3 Deterministic Formatting

lux-fmt enforces a single canonical style. There is no configuration and no style drift between generated and hand-written code — and because the formatter’s round-trip is property-tested to a fixed point, that canonical form is stable.


10. Performance

  • Compilation. Native via Cranelift (AOT and JIT), browser via WASM emission. No JIT on the deployed hot path, no garbage collector; memory is reference-counted under Rust’s ownership model, invisible to the Lux programmer.
  • Rendering. A virtual DOM with O(n) keyed diffing, implemented in Rust and compiled to WASM, producing a minimal patch set from a flat tree representation.
  • Bundle size. A complete application — reactive runtime, VDOM, CSS engine, and the standard-library modules it uses — compiles to a WebAssembly binary around 398 KB.
  • Startup. A Lux WASM binary needs no parsing, no compilation, and no module resolution at load; the browser instantiates it directly, so first meaningful paint tracks instantiation time.

Elm (2012) pioneered a purpose-built web language with a strong type system and no runtime exceptions. Lux shares Elm’s commitment to correctness and adds energy observability, server syntax, and a larger standard library.

Svelte (2016) showed that a compiler-first approach yields smaller, faster applications. Lux extends the idea by compiling to WASM/native rather than JavaScript, and by measuring the compiled output’s energy.

SolidJS (2021) introduced fine-grained reactivity without a virtual DOM. Lux’s reactive model follows SolidJS signals but keeps a VDOM so one view tree renders in browser, native, and edge contexts.

Leptos (2023) proved Rust-based reactive web frameworks viable. Lux builds on that foundation but provides its own .lux syntax rather than Rust-with-macros.

SwiftUI (2019) demonstrated that declarative UI with inline styling removes the need for separate markup and style languages. Lux adopts that model for the web.


12. Conclusion

Lux is a single language that does the work HTML, CSS, JavaScript, TypeScript, React, webpack, npm, and Node.js do collectively, while adding a property none of them have: a program can report its own energy cost, sourced honestly, with its uncertainty attached.

The web platform is estimated to consume on the order of 4% of global electricity. Most of that consumption is invisible to the people who create it. Lux makes a program’s share of it legible — not as a benchmark, not as a marketing claim, but the way a household meter makes a house’s electricity legible to the people who live there.

Every Lux program can answer the question, how many joules did that cost? The answer is a number, the unit is joules, and the figure says where it came from.


References

[1] HTTP Archive. “State of the Web.” 2025. httparchive.org

[2] Luccioni, A.S., Viguier, S., Ligozat, A.L. “Estimating the Carbon Footprint of BLOOM, a 176B Parameter Language Model.” Journal of Machine Learning Research, 2023.

[3] Freitag, C., et al. “The real climate and transformative impact of ICT: A critique of estimates, trends, and regulations.” Patterns, 2(9), 2021.

[4] Masanet, E., et al. “Recalibrating global data center energy-use estimates.” Science, 367(6481), 2020.

[5] Khan, K.N., et al. “RAPL in Action: Experiences in Using RAPL for Power Measurements.” ACM Transactions on Modeling and Performance Evaluation of Computing Systems, 2018.


Open Interface Engineering, Inc. Sarasota, Florida (941) 256-2032 joulesperbit.com · lux-lang.dev