Skip to content

Web Applications (std.web)

The std.web module is Flame’s official framework and compiler toolchain for building high-performance, reactive web applications.

Instead of shipping a heavy runtime framework (like React or Angular) or forcing bulky WebAssembly binaries for simple UI updates, Flame compiles your UI and component declarations directly into clean, fine-grained reactive JavaScript, HTML5, and CSS. When heavy mathematical computations are needed, Flame compiles functions marked with @Wasm into genuine WebAssembly bytecode (dist/app.wasm) and streams it directly to the browser.


Architectural Philosophy: Polyglot Compilation

Section titled “Architectural Philosophy: Polyglot Compilation”

The browser’s user interface is fundamentally a JavaScript and DOM environment. Access to the DOM tree, events, animations, and Web APIs are native to browser JavaScript engines.

Flame cleanly divides responsibilities between JavaScript and WebAssembly (WASM):

Flame Source (*.fm)
│
Blaze
│
@Web Target
│
┌───────────┴───────────┐
│ │
UI / DOM Computation
│ │
JavaScript WASM
(Fine-Grained) (dist/app.wasm)
│ │
└───────────┬───────────┘
↓
Modern Browser
  • UI & DOM Manipulation: Compiles to minimal, human-readable JavaScript with direct DOM node updates.
  • WebAssembly Engine (@Wasm): Compiles CPU-intensive routines into native .wasm binaries.
  • Pure Algorithms (@Compute): High-efficiency mathematical evaluation without runtime overhead.
  • Browser APIs: Maps 1:1 with native browser globals (web.window, web.document, localStorage).
  • Networking: Idiomatic std.net.http client compiled to high-speed asynchronous requests.
  • Zero Heavy Runtimes: Output bundle dist/app.js is typically under 2 KB before minification!

Unlike traditional frameworks like React that re-render entire component functions and perform expensive virtual DOM tree diffing on every state change, Flame’s compiler performs fine-grained static dependency tracking:

React Virtual DOM vs. Flame Compiler Updates

Section titled “React Virtual DOM vs. Flame Compiler Updates”
React (Runtime Overhead):
state change ──> re-render component ──> allocate virtual DOM ──> tree diffing ──> DOM mutation
Flame (Zero Virtual DOM):
state change ──> known compiler dependency ──> direct DOM node update

Because Flame knows statically which text nodes and attributes reference @State variables, generated JavaScript modifies only the exact affected DOM text node or attribute:

count changes ──> _t0.textContent = "Counter: " + count

The surrounding <main>, <h1>, and <button> elements are untouched, and no component re-renders take place.


Flame provides 12 specialized web annotations that control compilation, routing, reactivity, layout wrapping, styling, and WebAssembly execution:

Annotation Target Parameters Description
@Web Entry Function title: String, port: Int Marks the application entrypoint. Configures the HTML page title and local dev server port.
@Page Function path: String Declares a client-side routable page view (e.g. @Page("/"), @Page("/compute")). Automatically registered in the SPA router.
@Layout Function children: Node Defines an application shell wrapping page content. Preserves persistent chrome across route transitions.
@Component Function props... Declares a reusable UI component that returns JSX elements. Can be instantiated like HTML tags (<NavBar />).
@State let mut var None Declares a fine-grained reactive signal. Mutations directly update dependent DOM nodes.
@Computed Function / Variable None Declares a derived reactive calculation. Automatically tracks dependencies on @State variables and recalculates on demand.
@Effect Function None Declares a reactive side-effect that automatically executes whenever its referenced @State dependencies change.
@Compute Function None Marks high-performance mathematical and algorithmic functions for maximum optimization.
@Wasm Function None Compiles heavy computational routines directly into WebAssembly binary bytecode (dist/app.wasm).
@Style Function None Declares CSS styling rules compiled directly into dist/app.css without overwriting custom styles.
@Client Function / Block None Marks code as strictly client-side browser code for the compiler’s frontend pass.
@Route Function path: String, method: String Declares a server or client route endpoint with explicit HTTP method matching.

Marks the entry function as targeting the web browser environment. Accepts optional configuration parameters, supporting both direct file paths and imported resource aliases:

import std.web
import "scrypt.js" as scrypt
import "theme.css" as theme
import "style.css" as style
import "navigation.json" as navData
@Web(
title: "Flame Showcase",
port: 3000,
css: [theme, style],
js: [scrypt]
)
fn main() {
// Application setup, global state, cursor initialization, and layout definition
}
Parameter Type Description
title String Sets the default <title> tag in dist/index.html and the browser window header.
port Int Specifies the local development server listening port (defaults to 3000).
css / styles Array or String Accepts imported CSS aliases (css: [theme, style]), path strings (css: ["style.css", "theme.css"]), or a single string. Flame copies each stylesheet into dist/ and imports them at the top of dist/app.css using @import "./<file>";, keeping them synchronized alongside any inline @Style declarations.
js / scripts Array or String Accepts imported JS aliases (js: [scrypt]), path strings (js: ["custom.js"]), or a single string. Flame copies each script into dist/ and imports them as ES modules at the top of dist/app.js. Exported functions are automatically bound to their alias (e.g. window.scrypt and const scrypt) and to window for immediate execution.

Using External JavaScript Functions in Event Handlers

Section titled “Using External JavaScript Functions in Event Handlers”

When you import an external JavaScript module (import "playground.js" as scrypt), you can invoke its functions directly inside Flame event handlers:

scrypt.js
export function loadExample(name) {
const editor = document.getElementById("code");
if (editor) {
editor.value = "// Loaded example: " + name;
}
}
export function clearEditor() {
const editor = document.getElementById("code");
if (editor) editor.value = "";
}
export function runFlameCode() {
console.log("Compiling and running Flame source...");
}
main.fm
import std.web
import "scrypt.js" as scrypt
import "theme.css" as theme
import "style.css" as style
fn handleExampleSelect(e: Unknown) {
// Read DOM event data directly
let selected = e.target.value
// Invoke external JS function with arguments
scrypt.loadExample(selected)
}
fn handleRunClick(e: Unknown) {
scrypt.runFlameCode()
}
fn handleClearClick(e: Unknown) {
scrypt.clearEditor()
}
@Web(title: "Flame Playground", css: [theme, style], js: [scrypt])
fn main() {
@Page("/")
fn playground() -> HtmlNode {
return (
<div class="playground-app">
<select id="example-select" onChange={handleExampleSelect}>
<option value="hello">Hello World</option>
<option value="counter">Counter</option>
</select>
<button class="btn btn-primary" onClick={handleRunClick}>
<span>Run</span>
</button>
<button class="btn btn-secondary" onClick={handleClearClick}>
<span>Clear</span>
</button>
</div>
)
}
}

How External JS & CSS Integration Works Under the Hood

Section titled “How External JS & CSS Integration Works Under the Hood”
  1. Static Analysis & Typechecking:
    • Flame’s typechecker inspects playground.js, discovers loadExample(name), and registers it under scrypt.loadExample.
    • The IDE displays a single, clean Flame signature (fn loadExample(name: Unknown) -> Unknown) with documentation noting External JavaScript function in playground.js.
    • Go-to-Definition on loadExample jumps directly to line 1 in playground.js.
  2. ES Module Bundling:
    • In dist/app.js, the compiler generates:
      import * as _custom_mod0 from "./playground.js";
      const scrypt = _custom_mod0;
      if (typeof window !== 'undefined') window.scrypt = _custom_mod0;
    • All exported functions from playground.js are also registered on window if not already defined.
  3. Stylesheets (dist/app.css):
    • Imported stylesheets (theme.css, style.css) are placed into dist/ and prepended via @import "./theme.css"; at the top of dist/app.css.

Embedded Data Resources in Web Applications

Section titled “Embedded Data Resources in Web Applications”

You can also import static JSON or text resources directly into your @Web frontend without writing client-side fetch() boilerplate:

import "navigation.json" as navData
fn main() {
// navData is compiled directly as a JavaScript object literal in dist/app.js
web.console.log($"Navigation items: {navData.items.len()}")
}

Binds a function as a routable page view mounted at the specified URL path. Flame’s compiler automatically extracts all @Page annotations across the workspace and injects them into the built-in SPA router:

export @Page("/about")
fn about() -> HtmlNode {
return (
<div class="page-container">
<h1>About Us</h1>
<p>Built with fine-grained Flame reactivity.</p>
</div>
)
}
  • When the user navigates to /about via <a href="/about"> or web.navigate("/about"), the page mounts instantly without a full browser reload.
  • Any @Page function is also usable as a tag-like component (<About />).

Defines a shared outer layout wrapper for all pages. Receives children as a parameter representing the active page view:

@Layout
fn AppLayout(children: Unknown) -> HtmlNode {
return (
<div class="app-layout">
<NavBar />
<main class="page-body">
{children}
</main>
<Footer />
</div>
)
}
  • Persistent State: The layout container (such as navigation bars, background canvases, and music players) remains mounted during route changes.
  • Dynamic Slot Interpolation: The {children} slot dynamically updates with the active @Page DOM tree.

Marks a function as a reusable UI component that returns JSX elements. Components accept typed parameters as props, which map directly to JSX tag attributes:

export @Component
fn Button(label: String, primary: Bool) -> HtmlNode {
return (
<button class={if primary { "btn btn-primary" } else { "btn btn-secondary" }}>
{label}
</button>
)
}
// Instantiation:
<Button label="Submit" primary={true} />

Marks a mutable variable as a reactive signal. When a @State variable is modified, Flame updates only the exact DOM text nodes and attributes that reference it:

@State
let mut count = 0
fn increment() {
count += 1
}
<button onClick={increment}>
Clicked: {count} times
</button>

Under the hood, Flame compiles @State variables into signal getters and setters (_getSignal and _setSignal), subscribing DOM text nodes at compile-time with zero virtual DOM overhead.


Declares a derived computation that depends on one or more @State variables. Flame’s compiler automatically analyzes the function body, traces all referenced @State variables, and subscribes dependent UI nodes directly to the underlying signals:

@State
let mut count = 10
@Computed
export fn double_count() -> Int {
return count * 2
}
@Computed
export fn count_parity() -> String {
if count % 2 == 0 {
return "Even"
}
return "Odd"
}
// In JSX:
<div>Double: {double_count()}</div>
<div>Parity: {count_parity()}</div>

When count changes, double_count() and count_parity() are re-evaluated and their text nodes update automatically.


Registers a side-effect function that automatically executes whenever its accessed @State dependencies update. Flame statically inspects the function body (including string interpolations and conditions) to register subscriptions:

@State
let mut query = ""
@Effect
export fn on_query_change() {
web.window.console.log($"Search query updated to: {query}")
}
  • Runs once on initial application load.
  • Re-runs automatically whenever query is mutated.

8. @Compute (Pure Algorithmic Optimization)

Section titled “8. @Compute (Pure Algorithmic Optimization)”

Directs the compiler to optimize mathematical and computational algorithms:

@Compute
export fn is_prime(n: Int) -> Bool {
if n <= 1 { return false }
let mut d = 2
while d * d <= n {
if n % d == 0 { return false }
d += 1
}
return true
}
@Compute
export fn collatz_steps(n: Int) -> Int {
let mut steps = 0
let mut val = n
while val > 1 && steps < 1000 {
if val % 2 == 0 { val = val / 2 } else { val = 3 * val + 1 }
steps += 1
}
return steps
}

Compiles heavy computational functions directly into WebAssembly binary bytecode (dist/app.wasm).

@Wasm
export fn wasm_fib(n: Int) -> Int {
if n <= 1 {
return n
}
return wasm_fib(n - 1) + wasm_fib(n - 2)
}
@Wasm
export fn wasm_factorial(n: Int) -> Int {
if n <= 1 {
return 1
}
return n * wasm_factorial(n - 1)
}
  1. Compilation: During flame build --web or flame run --web, Flame compiles @Wasm functions into genuine WebAssembly MVP binary modules.
  2. Streaming Loader: In the generated dist/app.js, Flame sets up an asynchronous streaming loader:
    const { instance } = await WebAssembly.instantiateStreaming(fetch("app.wasm"), {});
    _wasmExports = instance.exports;
    window.wasm = _wasmExports;
  3. Graceful Fallback: If the browser environment restricts WebAssembly, Flame automatically delegates calls to an equivalent JavaScript fallback function without throwing runtime errors.
  4. Benchmarking: Benchmark native WASM vs JavaScript execution using web.window.performance.now().

10. @Style (Component & Global CSS Injection)

Section titled “10. @Style (Component & Global CSS Injection)”

Injects CSS rules into dist/app.css without overwriting custom styles. @Style can return CSS strings, take inline strings as annotation arguments, or reference external CSS files:

// 1. Inline CSS string function (can be declared at top-level or inside @Web main)
@Style
fn componentStyles() -> String {
".badge-wasm { background: rgba(16, 185, 129, 0.2); color: #34d399; padding: 0.25rem 0.75rem; border-radius: 9999px; }"
}
// 2. Reference an external stylesheet file
@Style("editor.css")
fn editorTheme() {}

Flame bundles all custom stylesheets specified in @Web(css: ...) and all @Style declarations into dist/app.css automatically on build, so developers do not need to manually touch dist/app.css.


Explicitly marks functions, event listeners, or code blocks as client-side browser code:

@Client
export fn on_sign_out() {
web.window.localStorage.removeItem("session_token")
web.navigate("/login")
}

Defines an HTTP route endpoint for fullstack or client-side routing with HTTP method constraints:

@Route(path = "/api/v1/health", method = "GET")
export fn healthCheck() {
// Endpoint logic
}

Flame provides native JSX-style syntax directly inside the language grammar without needing Babel, TypeScript, or JSX pragma transforms:

<div id="hero" class="container">
<h1 class="title">Welcome to Flame</h1>
</div>

Dynamic Flame expressions are evaluated inside single curly braces:

<div class="result">
Active Count: {count}
Double Count: {double_count()}
Calculated: {wasm_fib(10)}
</div>

Flame natively supports iterating over arrays and collections directly inside JSX elements:

<nav class="nav-links">
{
for e in elem {
<a href={getLinkHref(e)} class="nav-link">{e}</a>
}
}
</nav>

Loops can also be declared directly within child elements without surrounding braces:

<ul class="task-list">
for task in tasks {
<li class="task-item">{task}</li>
}
</ul>

4. Native String Interpolation in Handlers

Section titled “4. Native String Interpolation in Handlers”

Flame supports $"..." interpolated strings inside event handlers, effects, and signals:

export fn runWasmFib() {
let start = web.window.performance.now()
let res = wasm_fib(compute_num)
let end = web.window.performance.now()
let elapsed = end - start
wasm_output = $"Fibonacci({compute_num}) = {res}"
wasm_speed_badge = $"⚡ WASM executed in {elapsed.toFixed(3)} ms"
}

Attach event listeners using camelCase event attributes (onClick, onInput, onChange, onKeyDown):

// Direct state mutation:
<button onClick={count += 1}>Increment</button>
// Function reference:
<button onClick={fetchProjects}>Fetch Data</button>
// Multi-statement block:
<button onClick={
count += 1
toggleColor()
}>Update</button>
<button style={"background-color: " + btn_color + "; color: white;"}>
Dynamic Button
</button>

The std.web package provides comprehensive, type-safe structures mapping to native browser capabilities:

The browser Document instance provides DOM querying and node creation:

  • getElementById(id: String) -> Element
  • createElement(tagName: String) -> Element
  • createTextNode(text: String) -> HtmlNode
  • querySelector(selector: String) -> Element
  • querySelectorAll(selector: String) -> [Element]
  • addEventListener(event: String, handler: Unknown)
  • removeEventListener(event: String, handler: Unknown)

The global browser Window instance provides window management, navigation, timers, and dialogs:

  • addEventListener(event: String, handler: Unknown)
  • removeEventListener(event: String, handler: Unknown)
  • alert(message: String)
  • prompt(message: String, defaultText: String = "") -> String
  • confirm(message: String) -> Bool
  • setTimeout(callback: Unknown, delayMs: Int) -> Int
  • clearTimeout(id: Int)
  • setInterval(callback: Unknown, delayMs: Int) -> Int
  • clearInterval(id: Int)
  • requestAnimationFrame(callback: Unknown) -> Int
  • cancelAnimationFrame(id: Int)
  • performance.now() -> Float
  • location: Location
  • history: History
  • localStorage: Storage
  • sessionStorage: Storage
  • innerWidth: Int, innerHeight: Int

Standard browser developer tools logging and debugging:

  • web.console.info(...args: Any): Outputs informational log message to browser console.
  • web.console.log(...args: Any): Outputs general log message to browser console.
  • web.console.warn(...args: Any): Outputs warning message with yellow alert badge.
  • web.console.error(...args: Any): Outputs error message with red alert badge.
  • web.console.debug(...args: Any): Outputs debug level message.
  • web.console.table(data: Any): Displays tabular data as a clean browser console table.
  • web.console.clear(): Clears the browser console output.

Interactive DOM element wrapper:

  • setAttribute(name: String, value: String)
  • getAttribute(name: String) -> String
  • removeAttribute(name: String)
  • appendChild(child: Unknown)
  • removeChild(child: Unknown)
  • addEventListener(event: String, handler: Unknown)
  • removeEventListener(event: String, handler: Unknown)
  • focus()
  • blur()

Web Storage API wrapper:

  • get(key: String) -> String
  • set(key: String, value: String)
  • remove(key: String)
  • clear()
  • length() -> Int

Event, MouseEvent, KeyboardEvent, InputEvent

Section titled “Event, MouseEvent, KeyboardEvent, InputEvent”

Event objects passed to event handlers with event cancellation (preventDefault(), stopPropagation()) and pointer / key coordinates (clientX, clientY, key, code).


Networking & Asynchronous HTTP (std.net.http)

Section titled “Networking & Asynchronous HTTP (std.net.http)”

Flame Web projects leverage Flame’s standard networking module std.net.http for asynchronous REST API communication:

import std.net.http
export fn fetchLivePosts() {
let container = web.document.getElementById("posts-container")
if container {
container.innerHTML = "<div class='loading'>Loading live posts...</div>"
}
// Native asynchronous HTTP GET request
let posts = await http.get("https://jsonplaceholder.typicode.com/posts")
if container {
container.innerHTML = ""
let mut i = 0
while i < 5 && i < posts.length {
let p = posts[i]
let card = web.document.createElement("div")
card.className = "post-card"
card.innerHTML = "<h3>" + p.title + "</h3><p>" + p.body + "</p>"
container.appendChild(card)
i += 1
}
}
}

Flame’s compiler statically analyzes functions and closures. When await is present within any function, event handler, or closure, Flame automatically compiles the declaration into a native JavaScript async function.


Large web applications in Flame are structured into modular components and routes:

my-web-app/
├── flame.toml # Project manifest
├── src/
│ ├── main.fm # App entrypoint (@Web, @Layout, root @Page("/"))
│ ├── navbar.fm # Shared navigation bar (export @Component fn NavBar)
│ ├── compute.fm # WebAssembly & Math Showcase (@Wasm, @Compute, @State, @Effect)
│ ├── about.fm # About page (export @Page("/about"))
│ ├── projects.fm # Live API page with std.net.http (export @Page("/projects"))
│ └── contact.fm # Form with reactive @State (export @Page("/contact"))
└── dist/ # Auto-generated static web bundle
├── index.html # HTML5 entry mounting to #app
├── app.js # Standalone reactive signals + SPA router + compiled UI (<2 KB)
├── app.wasm # Generated WebAssembly binary for @Wasm routines
└── app.css # Preserved custom stylesheet + @Style injections

Below is the complete, working implementation of a multi-file responsive web application demonstrating all web annotations, WebAssembly, reactive signals, and API integration:

import std.web
import std.net.http
import navbar
import about
import projects
import contact
import compute
@Web(title: "Flame Web Showcase", port: 3000)
fn main() {
@State
let mut count = 0
@State
let mut btn_color = "#3b82f6"
@Computed
fn double_count() -> Int {
return count * 2
}
@Computed
fn count_parity() -> String {
if count % 2 == 0 {
return "Even Number"
}
return "Odd Number"
}
fn toggleColor() {
if btn_color == "#3b82f6" {
btn_color = "#10b981"
} else if btn_color == "#10b981" {
btn_color = "#ec4899"
} else if btn_color == "#ec4899" {
btn_color = "#8b5cf6"
} else {
btn_color = "#3b82f6"
}
}
fn setupCursor() {
web.window.addEventListener(
"mousemove",
(e) {
let dot = web.document.getElementById("cursor-dot")
let outline = web.document.getElementById("cursor-outline")
if dot {
dot.style.left = e.clientX + "px"
dot.style.top = e.clientY + "px"
}
if outline {
outline.style.left = e.clientX + "px"
outline.style.top = e.clientY + "px"
}
}
)
}
setupCursor()
@Layout
fn AppLayout(children: Unknown) -> HtmlNode {
return (
<div class="app-layout">
<NavBar />
<div class="app-content">
{children}
</div>
</div>
)
}
@Page("/")
fn index() -> HtmlNode {
return (
<div class="page-container">
<main class="hero-section">
<div class="hero-badge">Next-Gen Reactive Framework</div>
<h1 class="hero-title">High-Performance Web in Flame</h1>
<p class="hero-subtitle">
Compile Flame directly into ultra-fast JavaScript DOM code with fine-grained reactivity, WebAssembly (@Wasm), and zero-overhead routing.
</p>
<div class="interactive-panel">
<button onClick={count += 1} class="btn-counter">
Counter: {count}
</button>
<button onClick={toggleColor} style={"background-color: " + btn_color + ";"} class="btn-color-toggle">
Active Color: {btn_color}
</button>
</div>
<div class="computed-stats" style="margin-top: 1.5rem; display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;">
<div style="background: rgba(255, 255, 255, 0.05); padding: 0.5rem 1rem; border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.1);">
⚡ <strong>@Computed Double:</strong> {double_count()}
</div>
<div style="background: rgba(255, 255, 255, 0.05); padding: 0.5rem 1rem; border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.1);">
🎯 <strong>Parity:</strong> {count_parity()}
</div>
</div>
</main>
</div>
)
}
}

Flame provides a built-in development HTTP server, live-reload watcher, and static bundler:

Start a local development server:

Terminal window
fmp run --web src/main.fm

This compiles your project into dist/ and serves it at http://localhost:3000/.

Enable hot reloading during active development:

Terminal window
fmp run --watch --web src/main.fm

The watcher listens for filesystem changes across your source files, automatically recompiles dist/app.js, dist/app.wasm, and dist/app.css, and immediately refreshes the browser without losing scroll state.

Generate an optimized static distribution:

Terminal window
fmp build --web src/main.fm

When Flame builds your web application, it produces a self-contained static directory ready for instant deployment:

dist/
├── index.html # HTML5 entry point mounting the app to #app
├── app.js # Standalone reactive runtime, router, and compiled UI (<2 KB)
├── app.wasm # Native WebAssembly binary module compiled from @Wasm routines
└── app.css # Merged styles (custom styles preserved + @Style injections)

When you customize dist/app.css, subsequent flame build --web or flame run --web runs never overwrite your custom CSS rules. Instead, Flame merges custom CSS with any @Style declarations in your code.


Deploying your Flame Web application is as simple as hosting the dist/ directory on any static cloud provider:

Cloudflare Pages

  • Build Command: fmp build --web
  • Output Directory: dist

Vercel

  • Framework Preset: Other
  • Build Command: fmp build --web
  • Output Directory: dist

Netlify

  • Publish Directory: dist
  • Build Command: fmp build --web

Docker / Nginx

  • Copy dist/ directly into /usr/share/nginx/html
  • Supports instant caching and sub-millisecond edge delivery.