.jslop cheatsheet
One-page reference for every construct currently implemented. Each section links to a focused page with details and gotchas.
File shape
import Default from "./Other.jslop"
import { Helper, OtherHelper as Renamed } from "./widgets.jslop"
component Name {
// declarations (any order)
view { <root /> }
}
component Sibling {
view { <span/> }
}
- Many components per file. First is the default export, all are named exports. → Components
.jslopimport paths are rewritten to the compiled module by the bundler.
Declarations
| Keyword | Reactive | Serialized | Use for |
|---|---|---|---|
prop x = d |
yes | parent | input from a parent |
state x = d |
yes | yes | anything the view reads |
derived x = expr |
yes (read) | no (recomputed) | memoized value computed from other reactives |
let x = d |
no | no | per-instance bookkeeping the view never reads |
function f() {} |
— | — | actions / event handlers |
head { ... } |
— | — | per-component <head> fragment (<title>, meta, …) |
style { ... } |
— | — | scoped CSS for this component |
load { ... } |
— | — | server-side data fetching for routes/layouts |
action f(p) {} |
— | — | server-only mutation handler (POST endpoint) |
derived — memoized reactive value
state n = 1
derived doubled = n * 2
derived quad = doubled * 2
Recomputes only when an input it read actually changes; cached otherwise. RHS identifiers that match state/prop/derived are rewritten to .get(). → Components, Reactivity
head { ... } — per-component <head> fragment
component About {
head {
<title>About · Site</title>
<meta name="description" content="…"/>
}
view { <main>…</main> }
}
Reactive interpolations work (<title>{post.title}</title>). When multiple components on a page declare head, the route’s fragment is rendered after layouts, so its <title> wins. → SSR & resumability
load { ... } — server-side data fetching (routes & layouts)
import { findPost } from "../lib/posts.js"
component PostPage {
prop slug = ""
prop post = null
load {
const post = await findPost(params.slug)
if (!post) notFound()
return { post }
}
view { <article><h1>{post.title}</h1></article> }
}
Runs on the server before render; returned object merges into props (URL params → layout loads → route load). May be async. Call notFound() from @jslop/runtime to trigger the 404 chain. → Routing → load
action { ... } — server mutation handler
component Inbox {
action create(input) {
const { createTask } = await import("../store.js")
return await createTask(input)
}
function submit(e) { e.preventDefault(); create({ title: "buy milk" }) }
view { <form onsubmit={submit}>…</form> }
}
Body runs only on the server. The compiler emits a client stub that POSTs to the route URL with header x-jslop-action: <name>. On success the route’s load { ... } re-runs and the new HTML swaps in. Throw redirect(url) from @jslop/runtime to navigate elsewhere instead (useful after a delete that would 404 the current page). The body sees params, url, and request in scope. → Actions
style { ... } — scoped CSS
component Card {
style {
.row { color: red; }
p { font-size: 0.875rem; }
}
view { <div class="row"><p>…</p></div> }
}
The compiler adds a jslop-<name>-<hash> class on the root element and rewrites every selector to be prefixed by .jslop-…-…, so styles don’t leak. Use it alongside global CSS or Tailwind — they don’t conflict. → Styling
View — elements & attributes
view {
<div class="card">
<h1>{title}</h1>
<p>{count} item{count === 1 ? "" : "s"}</p>
<a href="/x" class={active ? "on" : ""}>x</a>
<img src={url} alt="" />
<input disabled /> {/* boolean shorthand */}
</div>
}
- Exactly one root element per view.
- Lowercase tag → DOM element. PascalCase → component.
- String literals or
{expr}for attribute values.{expr}is reactive.
Components
<Stepper label="+" onstep={inc} />
<Display value={count} label="Count" />
on*on a component tag is a regular prop (not auto-bound to DOM events).
Events
<button onclick={inc}>+</button>
<button onclick={() => count++}>+</button>
<input oninput={e => draft = e.target.value} />
<form onsubmit={e => { e.preventDefault(); save() }}>...</form>
→ Events
Bindings
<input bind:value={draft} />
<input type="checkbox" bind:checked={agreed} />
<select bind:value={pick}>
<option value="a">A</option>
</select>
- Bound expression must be a writable reactive lvalue (
stateor reactiveprop). - Can’t combine
bind:valuewithvalue=oroninput=on the same element.
→ Bindings
Logic blocks
{#if cond}
<p>yes</p>
{:else}
<p>no</p>
{/if}
{#each list as item, i (item.id)}
<li>{i}: {item.label}</li>
{/each}
- Always key any
{#each}that mutates by anything other than append-only. {:else if}is not supported yet; nest a fresh{#if}inside{:else}.
<children/>
component Layout {
view {
<div><children/></div>
}
}
Marks where wrapped content renders. Used by layouts (and, in the future, by any wrapper component receiving children).
Reactivity runtime
When you need to drop down a level (custom helpers, derived values, ad-hoc effects):
import { cell, derived, effect, batch, untrack } from "@jslop/runtime";
const x = cell(0);
const y = derived(() => x.get() * 2);
effect(() => console.log(y.get()));
batch(() => { x.set(1); x.set(2); }); // y/effect run once
What the compiler does (in one breath)
For each state / prop: declare a cell. For each let: declare a plain JS variable. Inside functions and view {expr}s: identifiers that match a state/prop name become .get() on read and .set(...) on write; ++/--/+= etc. expand via .peek(). Everything else (parameters, locals, let, each bindings) is left alone.
The view is emitted as a tree of node descriptors (element, text, bind, if, each, component). The server walks it to produce HTML; the client walks the same tree and wraps each bind in an effect for fine-grained updates.
Not in the DSL yet
Typed server function (split-bundled RPC with auth context) · mount/cleanup blocks · {#await} · {#snippet} · {:else if} · catch-all routes · fragments · spread props (parsed, partial impl) · style Name { variants: ... } first-class variants. (The boring action { ... } block above is the shipping stepping stone; the full typed-RPC story still belongs to a future release.)
→ Roadmap