First App
This guide walks through the default scaffold end to end — from sprag new to a built artifact.
Scaffold and run
sprag new myapp
cd myapp
sprag dev
Open http://localhost:8000/counter. You'll see a counter with an increment button.
What's happening
The server: app/routes/counter/server.py
from sprag import Controller, Field, Schema, action
class CounterController(Controller):
route = "/counter"
def load(self):
return {"count": 0}
@action(schema=Schema("increment", {"count": Field(int, required=True)}))
def increment(self, count):
return {"count": count + 1}
load()returns the initial data for the page. This runs on the server during SSR and is sent to the browser as the boot payload.@actiondeclares a named mutation. The browser can call it withcall_action("increment", {count: N}).
The browser module: app/routes/counter/modules.py
from sprag import Module
class CounterModule(Module):
def on_start(self):
self.delegate(self.element, "click", "[data-role='increment']", self.on_increment)
def on_increment(self, event, target):
event.prevent_default()
self.call_action("increment", {"count": self.state["count"]}).then(self.on_result)
def on_result(self, result):
self.set_state(result.value)
Moduleowns non-visual lifecycle — event listeners, server calls, state.self.call_action("increment", payload)calls the server@actionand returns a Promise-like result; update local state in the success handler.- This Python compiles to Ragot ESM JavaScript at build time.
The browser component: app/routes/counter/components.py
from sprag import Component, ui
class CounterCard(Component):
def render(self, props=None):
return ui.div(
ui.span(str(self.state["count"])),
ui.button("Increment", data_role="increment"),
)
Componentowns a DOM subtree.render()returns aui.*tree.- When state changes, the component re-renders via morphDOM diffing.
Make a change
Add a reset button. In components.py:
ui.button("Reset", data_role="reset"),
In modules.py, add a handler:
self.delegate(self.element, "click", "[data-role='reset']", self.on_reset)
def on_reset(self, event, target):
event.prevent_default()
self.set_state({"count": 0})
Save both files. The dev server rebuilds and the browser updates.
Build for production
sprag build
This emits the full site into dist/. Open dist/counter/index.html in a browser — the counter still works, fully static.
For production optimization:
sprag pack
This minifies, gzips, fingerprints, and optimizes the dist/ output.