HyperApp is a 1kb JavaScript library for building modern UI applications.
With npm or yarn.
npm i hyperapp
CommonJS
const { app, html } = require("hyperapp")ES6
import { app, html } from "hyperapp"With Browserify.
browserify -t hyperxify -g uglifyify index.js | uglifyjs > bundle.js
HyperApp is also distributed as a minified file, hosted on a CDN.
<script src="https://unpkg.com/hyperapp/dist/hyperapp.js"></script>For a more thorough introduction and advanced usage see the HyperApp User Guide.
Counter
app({
model: 0,
update: {
add: model => model + 1,
sub: model => model - 1
},
view: (model, actions) => html`
<div>
<button onclick=${actions.add}>+</button>
<h1>${model}</h1>
<button onclick=${actions.sub} disabled=${model <= 0}>-</button>
</div>`
})Input
app({
model: "",
update: {
text: (_, value) => value
},
view: (model, actions) => html`
<div>
<h1>Hi${model ? " " + model : ""}.</h1>
<input oninput=${e => actions.text(e.target.value)} />
</div>`
})Drag & Drop
const model = {
dragging: false,
position: {
x: 0, y: 0, offsetX: 0, offsetY: 0
}
}
const view = (model, actions) => html`
<div
onmousedown=${e => actions.drag({
position: {
x: e.pageX, y: e.pageY, offsetX: e.offsetX, offsetY: e.offsetY
}
})}
style=${{
userSelect: "none",
cursor: "move",
position: "absolute",
padding: "10px",
left: `${model.position.x - model.position.offsetX}px`,
top: `${model.position.y - model.position.offsetY}px`,
backgroundColor: model.dragging ? "gold" : "deepskyblue"
}}
>Drag Me!
</div>`
const update = {
drop: model => ({ dragging: false }),
drag: (model, { position }) => ({ dragging: true, position }),
move: (model, { x, y }) => model.dragging
? ({ position: { ...model.position, x, y } })
: model
}
const subscriptions = [
(_, actions) => addEventListener("mouseup", actions.drop),
(_, actions) => addEventListener("mousemove", e =>
actions.move({ x: e.pageX, y: e.pageY }))
]
app({ model, view, update, subscriptions })Todo
const FilterInfo = { All: 0, Todo: 1, Done: 2 }
const model = {
todos: [],
filter: FilterInfo.All,
input: "",
placeholder: "Add new todo!"
}
const view = (model, actions) => {
return html`
<div>
<h1>Todo</h1>
<p>
Show: ${
Object.keys(FilterInfo)
.filter(key => FilterInfo[key] !== model.filter)
.map(key => html`
<span><a href="#" onclick=${_ => actions.filter({
value: FilterInfo[key]
})}>${key}</a> </span>
`)}
</p>
<p><ul>
${model.todos
.filter(t =>
model.filter === FilterInfo.Done
? t.done :
model.filter === FilterInfo.Todo
? !t.done :
model.filter === FilterInfo.All)
.map(t => html`
<li style=${{
color: t.done ? "gray" : "black",
textDecoration: t.done ? "line-through" : "none"
}}
onclick=${e => actions.toggle({
value: t.done,
id: t.id
})}>${t.value}
</li>`)}
</ul></p>
<p>
<input
type="text"
onkeyup=${e => e.keyCode === 13 ? actions.add() : ""}
oninput=${e => actions.input({ value: e.target.value })}
value=${model.input}
placeholder=${model.placeholder}
/>
<button onclick=${actions.add}>add</button>
</p>
</div>`
}
const update = {
add: model => ({
input: "",
todos: model.todos.concat({
done: false,
value: model.input,
id: model.todos.length + 1
})
}),
toggle: (model, { id, value }) => ({
todos: model.todos.map(t =>
id === t.id
? Object.assign({}, t, { done: !value })
: t)
}),
input: (model, { value }) => ({ input: value }),
filter: (model, { value }) => ({ filter: value })
}
app({ model, view, update })Use html to compose HTML elements.
const hello = html`<h1>Hello.</h1>`Import the h function and include the jsx pragma, in any order.
import { h, app } from "hyperapp"
/** @jsx h */
app({
model: "Hi.",
view: model => <h1>{model}</h1>
})Or, add it to your .babelrc configuration.
{
"plugins": [
["transform-react-jsx", { "pragma": "h" }]
]
}
Use app to start your app.
app(options)A primitive type, array or object that represents the state of your application. HyperApp applications use a single model architecture.
An object composed of functions often called reducers. A reducer describes how to derive the next model from the current model.
const update = {
increment: model => model + 1,
decrement: model => model - 1
}Reducers can return an entire new model or part of a model. If a reducer returns part of a model, it will merged with the current model.
Reducers can be triggered inside a view, effect or subscription.
Reducers have the signature (model, data), where
modelis the current model, anddatais the data sent to the reducer. See view.
A function that returns an HTML element using jsx or html function.
The view has the signature (model, actions), where
actions.action(data)datais any data you want to send toaction, andactionthe name of the reducer or effect.
Example
app({
model: true,
view: (model, actions) => html`<button onclick=${actions.toggle}>${model+""}</button>`,
update: {
toggle: model => !model
}
})Functions that can be attached to your virtual HTML nodes to access their real DOM elements.
- oncreate(e :
HTMLElement) - onupdate(e :
HTMLElement) - onremove(e :
HTMLElement)
app({
view: _ => html`<div oncreate=${e => console.log(e)}>Hi.</div>`
})Example
const repaint = (canvas, model) => {
const context = canvas.getContext("2d")
context.fillStyle = "white"
context.fillRect(0, 0, canvas.width, canvas.height)
context.beginPath()
context.arc(model.x, model.y, 50, 0, 2 * Math.PI)
context.stroke()
}
app({
model: { x: 0, y: 0 },
view: model => html`<canvas
width="600"
height="300"
onupdate=${e => repaint(e, model)} />`,
update: {
move: (model) => ({ x: model.x + 1, y: model.y + 1 })
},
subscriptions: [
(_, actions) => setInterval(_ => actions.move(), 10)
]
})Actions that cause side effects and are often asynchronous, like writing to a database, or sending requests to servers.
Effects have a signature (model, actions, data, error), where
modelis the current model,actionsis an object used to trigger reducers and/or effects,datais the data send to the effect, anderroris a function you may call to throw an error
Example
const wait = time => new Promise(resolve => setTimeout(_ => resolve(), time))
const model = {
counter: 0,
waiting: false
}
const view = (model, actions) =>
html`
<button
onclick=${actions.waitThenAdd}
disabled=${model.waiting}>${model.counter}
</button>`
const update = {
add: model => ({ counter: model.counter + 1 }),
toggle: model => ({ waiting: !model.waiting})
}
const effects = {
waitThenAdd: (model, actions) => {
actions.toggle()
wait(1000).then(actions.add).then(actions.toggle)
}
}
app({ model, view, update, effects })Subscriptions are functions scheduled to run only once when the DOM is ready. Use a subscription to register global events, open a socket connection, attached mouse or keyboard event listeners, etc.
A subscription has the signature (model, actions, error).
Example
app({
model: { x: 0, y: 0 },
update: {
move: (_, { x, y }) => ({ x, y })
},
view: model => html`<h1>${model.x}, ${model.y}</h1>`,
subscriptions: [
(_, actions) => addEventListener("mousemove", e => actions.move({ x: e.clientX, y: e.clientY }))
]
})Function handlers that can be used to inspect your application, implement middleware, loggers, etc.
Called when the model changes. Signature (lastModel, newModel, data).
Called when an action (reducer or effect) is triggered. Signature (name, data).
Called when you use the error function inside a subscription or effect. If you don't use this hook, the default behavior is to throw. Signature (err).
Example
app({
model: true,
view: (model, actions) => html`
<div>
<button onclick=${actions.doSomething}>Log</button>
<button onclick=${actions.boom}>Error</button>
</div>`,
update: {
doSomething: model => !model,
},
effects: {
boom: (model, actions, data, err) => setTimeout(_ => err(Error("BOOM")), 1000)
},
hooks: {
onError: e =>
console.log("[Error] %c%s", "color: red", e),
onAction: name =>
console.log("[Action] %c%s", "color: blue", name),
onUpdate: (last, model) =>
console.log("[Update] %c%s -> %c%s", "color: gray", last, "color: blue", model)
}
})The HTML element container of your application. If none is given, a div element is appended to document.body and used as the root node of your application.
app returns an object that consists of
- the same input options passed to
app, and - a
render(view)function that can be used to render and alternate between views.
The render function can be used to implement routing in your application.
HyperApp does not provide a router out of the box. If your application needs a router use HyperApp Router.