Skip to main content

Wunderframe Applications

A Wunderframe Application is a full-stack web application built using LUAT templating, modern frontend tooling, and a content management system. Applications use a convention-based structure with zero configuration for common tools like TailwindCSS and esbuild.

What is a Wunderframe Application?

Wunderframe Applications combine:

  • 🎨 LUAT Templating - Server-side rendering with Svelte-like syntax
  • ⚡ Zero Config - Pre-configured TailwindCSS, esbuild, and TypeScript
  • 📱 Modern Frontend - Alpine.js, HTMX, and component-based architecture
  • 🗃️ Content Management - Built-in node-based content system
  • 🔧 Live Development - Hot reloading and real-time updates

Project Structure

Every Wunderframe application follows a standard structure:

my-app/
├── app.lua # Application entry point and routing
├── app.js # Frontend JavaScript entry point
├── app.css # Global styles with TailwindCSS
├── wunder.toml # Project configuration
├── package.json # Frontend dependencies
├── lib/ # Application logic and templates
│ ├── components/ # Reusable LUAT components
│ ├── pages/ # Page templates
│ └── utils/ # Utility modules and helpers
└── src/ # Frontend source files
├── directives/ # Alpine.js directives
└── components/ # Web components

Core Files

app.lua - Application Entry Point

The main Lua file that handles routing and renders the appropriate templates:

-- app.lua
local json = require("json")
local getMatchingContentType = require("lib/utils/content_types")

local function render(pageContext, runtime)
local helpers = createContextHelpers(runtime)
helpers.createContext()

local currentNode = pageContext.current_node

-- Handle root path routing
if pageContext.full_path == "/" then
currentNode = get_node("stories/" .. pageContext.story_node.path .. "/site/homepage")
end

-- Set up context for templates
helpers.setContext("currentNode", currentNode)
helpers.setContext("pageContext", pageContext)

-- Find and render the appropriate component
local renderComponent = getMatchingContentType(currentNode.content_type)
if renderComponent then
return renderComponent(currentNode, runtime)
else
error("No matching content type found for: " .. currentNode.content_type)
end
end

return {
render = render
}

app.js - Frontend Entry Point

Configures the client-side JavaScript with Alpine.js, HTMX, and custom components:

// app.js
import "@phosphor-icons/web/light";
import Alpine from 'alpinejs';
import htmx from "htmx.org";
import loopVideo from "./src/directives/loopvideo.js";
import { initializeDev } from './src/helper.js';

// Register Alpine directives
loopVideo(Alpine);
Alpine.start();

// Set up HTMX and development features
document.addEventListener('htmx:load', () => {
// Live editing integration
if(window.raisin) {
const editor = window.raisin.editor;
editor.onMessage("UPDATE", (newData) => {
htmx.ajax('POST', location.href, {
target: '#app',
swap: 'outerHTML',
values: { properties: JSON.stringify(newData.properties) }
});
});
}

// Initialize development mode
initializeDev();
});

app.css - Global Styles

TailwindCSS configuration with custom fonts and animations:

/* app.css */
@import url('https://fonts.googleapis.com/css2?family=Inter...');
@import "tailwindcss";
@plugin "@tailwindcss/typography";

@custom-variant dark (&:where(.dark, .dark *));

@theme {
--font-sans: "Inter", "sans-serif";
--font-playful: "Sour Gummy", sans-serif;
}

/* Custom animations */
@keyframes fade-in-up {
0% { opacity: 0; transform: translateY(32px);}
100% { opacity: 1; transform: translateY(0);}
}
.animate-fade-in-up {
animation: fade-in-up 1s cubic-bezier(.23,1.01,.32,1) both;
}

wunder.toml - Project Configuration

# wunder.toml
name = "my-app"
target = "/"

[sync]
status = "unsynced"
workspaces = []

[frontend_tools]
enabled = ["tailwindcss", "typescript"]

Zero Configuration

Wunderframe applications come with pre-configured tooling:

TailwindCSS

  • ✅ Auto-configured with sensible defaults
  • ✅ Typography plugin included
  • ✅ Custom theme and variant support
  • ✅ Dark mode support built-in

esbuild

  • ✅ Fast JavaScript/TypeScript bundling
  • ✅ ES modules and CommonJS support
  • ✅ Development and production builds
  • ✅ Hot reloading in development

TypeScript

  • ✅ Optional TypeScript support
  • ✅ Pre-configured for Alpine.js and HTMX
  • ✅ Type checking and IntelliSense

Built-in Frontend Stack

Alpine.js

Interactive frontend framework for reactive components:

<div x-data="{ open: false }">
<button @click="open = !open">Toggle</button>
<div x-show="open">Content</div>
</div>

HTMX

Enables server-side rendering with dynamic updates:

<button hx-post="/api/action" hx-target="#result">
Click me
</button>
<div id="result"></div>

Phosphor Icons

Beautiful icon library included by default:

<i class="ph ph-heart"></i>
<i class="ph ph-star-fill"></i>

What's Next?