Appearance
Getting Started
Prerequisites
Experimental Crystal flags required
Lune compiles with -Dpreview_mt -Dexecution_context. These flags enable Crystal's multi-threading execution context API, which Lune uses to run async: bindings on real OS threads without blocking the native GUI event loop.
The lune CLI passes both flags automatically for lune dev and lune build. If you ever invoke crystal build directly, add both flags yourself:
sh
crystal build src/main.cr -Dpreview_mt -Dexecution_context -o build/my_appspawn does not work for background tasks. The native event loop owns the main thread, so fibers in the default cooperative context are never scheduled while the window is open. Use app.async for any long-running background work:
crystal
app.async do
loop do
app.emit("tick", Time.utc.to_rfc3339)
sleep 1.second
end
endmacOS only: Xcode Command Line Tools are required for the native WebView headers.
Linux only: Install WebKit development headers:
sh
# Ubuntu / Debian
sudo apt install libwebkit2gtk-4.1-dev
# Fedora
sudo dnf install webkit2gtk4.1-develInstall the CLI
Pre-built binaries are attached to each GitHub release:
| Platform | File |
|---|---|
| macOS (Apple Silicon) | lune-darwin-arm64 |
| Linux x86_64 | lune-linux-x86_64 |
macOS Intel (x86_64): No pre-built binary is available — the GitHub Actions Intel runner is currently unreliable. Build from source instead (see below).
Download the binary for your platform, make it executable, and put it on your PATH:
sh
chmod +x lune-darwin-arm64
mv lune-darwin-arm64 /usr/local/bin/luneOr build from source:
sh
git clone https://github.com/aristorap/lune
cd lune
make setup # shards install
make deploy # build release binary → /usr/local/bin/luneAdd Lune to a project
Add it to your shard.yml:
yaml
dependencies:
lune:
github: AristoRap/lune
version: ~> 0.6.2Then install:
sh
shards installCreate a new app
sh
lune init my_app # defaults to vanilla template
lune init my_app --template vue # Vue 3 + Vite
lune init my_app --template vanilla # Vanilla JS + ViteThis scaffolds a ready-to-run project:
my_app/
├── src/
│ └── main.cr # Crystal entry point
├── frontend/ # Your Vite frontend
│ ├── src/
│ │ └── main.{js,ts}
│ ├── index.html
│ └── vite.config.{js,ts}
├── lune.yml # Project configuration
└── shard.ymlStart the dev server
sh
cd my_app
lune devThis starts both your Vite dev server and the Crystal backend. The app opens in a native window. Saving frontend files hot-reloads the UI; saving Crystal files triggers a recompile and refresh.
Call Crystal from JavaScript
Open src/main.cr. You'll find a module that includes Lune::Bindable:
crystal
require "lune"
class GreetModule
include Lune::Bindable
@[Lune::Bind]
def greet(name : String) : String
"Hello, #{name}!"
end
end
app = Lune::App.new
app.install(GreetModule.new)
Lune.run(app, assets: "frontend/dist") do |opts|
opts.title = "My App"
opts.width = 1200
opts.height = 800
endIn your frontend, import the generated API and call it:
js
import api from "../lunejs/app/App.js";
const message = await api.GreetModule.Greet("world");
console.log(message); // "Hello, world!"Build for production
sh
lune buildOr with optimizations:
sh
lune build --releaseThis builds the frontend with Vite, then compiles the Crystal binary with the frontend embedded via the assets: argument in your Lune.run call. The output is a single self-contained executable in build/.
Note: The
assets: "frontend/dist"argument inLune.runis what tells Lune to embed the frontend into the binary. Without it, the built app will have no frontend to serve. See Assets & Build for details.
Demo app
The repository ships with a full showcase in demo/ — a Vue 3 app that exercises every part of the Lune API in one window:
| Section | What it shows |
|---|---|
| Bindings | @[Lune::Bind] — calling Crystal methods from JS as async functions |
| Events | Live clock (Crystal → JS), ping/pong roundtrip with latency, async file-progress |
| System | environment(), screenInfo(), native notifications |
| Clipboard | clipboardRead / clipboardWrite |
| Window | minimize, maximize, center, setTitle, setSize |
| Dialogs | File pickers (openFile, openFiles, openDir, saveFile) and message dialogs |
| Tray | Status-bar icon with a context-menu and event log |
Run it from the repo root:
sh
cd demo
lune devThe frontend lives in demo/frontend/src/ and is structured as a real Vue project — views, components, composables, and a useLuneEvent composable that handles on/off cleanup automatically on unmount. It is a good starting point to copy patterns from.
What's next
- Learn how the Crystal ↔ JavaScript bridge works: How It Works
- Explore the full binding API: Bindings
- See all CLI commands: CLI Reference
- Understand
lune.yml: Configuration