
Security News
OWASP 2025 Top 10 Adds Software Supply Chain Failures, Ranked Top Community Concern
OWASP’s 2025 Top 10 introduces Software Supply Chain Failures as a new category, reflecting rising concern over dependency and build system risks.
HyperApp is a 1kb functional JavaScript library for building modern UI applications
HyperApp is a 1kb functional JavaScript library for building modern UI applications.
npm i hyperapp
CDN
<script src="https://cdn.rawgit.com/hyperapp/hyperapp/master/dist/app.min.js"></script>
<script src="https://cdn.rawgit.com/hyperapp/hyperapp/master/dist/html.min.js"></script>
Browserify
browserify -g uglifyify index.js | uglifyjs > bundle.js
app({
model: 0,
update: {
add: model => model + 1,
sub: model => model - 1
},
view: (model, msg) => html`
<div>
<button onclick=${msg.add}>+</button>
<h1>${model}</h1>
<button onclick=${msg.sub} disabled=${model <= 0}>–</button>
</div>`
})
app({
model: "",
update: {
text: (_, value) => value
},
view: (model, msg) => html`
<div>
<h1>Hi${model ? " " + model : ""}.</h1>
<input oninput=${e => msg.text(e.target.value)} />
</div>`
})
const model = {
dragging: false,
position: {
x: 0, y: 0, offsetX: 0, offsetY: 0
}
}
const view = (model, msg) => html`
<div
onmousedown=${e => msg.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 subs = [
(_, msg) => addEventListener("mouseup", msg.drop),
(_, msg) => addEventListener("mousemove", e =>
msg.move({ x: e.pageX, y: e.pageY }))
]
app({ model, view, update, subs })
const FilterInfo = { All: 0, Todo: 1, Done: 2 }
const model = {
todos: [],
filter: FilterInfo.All,
input: "",
placeholder: "Add new todo!"
}
const view = (model, msg) => {
return html`
<div>
<h1>Todo</h1>
<p>
Show: ${
Object.keys(FilterInfo)
.filter(key => FilterInfo[key] !== model.filter)
.map(key => html`
<span><a href="#" onclick=${_ => msg.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 => msg.toggle({
value: t.done,
id: t.id
})}>${t.value}
</li>`)}
</ul></p>
<p>
<input
type="text"
onkeyup=${e => e.keyCode === 13 ? msg.add() : ""}
oninput=${e => msg.input({ value: e.target.value })}
value=${model.input}
placeholder=${model.placeholder}
/>
<button onclick=${msg.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 World!</h1>`
html is a tagged template string. If you are familiar with React, this is like JSX, but without breaking JavaScript.
Use app to create your app. The app function receives an object with any of the following properties.
A value or object that represents the entire state of your app.
To update the model, you send actions describing how the model should change. See view.
An object composed of functions known as reducers. These are a kind of action you send to update the model.
A reducer describes how the model should change by returning a new model or part of a model.
const update = {
increment: model => model + 1,
decrement: model => model - 1
}
If a reducer returns part of a model, that part will be merged with the current model.
You call reducers inside a view, effect or subscription.
Reducers have a signature (model, data), where
model is the current model, anddata is the data sent along with the action.The view is a function that returns HTML using the html function.
The view has a signature (model, msg, params), where
model is the current model,msg is an object you use to send actions (call reducers or cause effects) andparams are the route parameters.Use msg to send actions.
msg.action(data)
where data is any data you want to pass to the reducer / effect.
app({
view: {
"/": _ => html`<h1>Home</h1>`,
"/about": _ => html`<h1>About</h1>`
}
})
The view object may accommodate multiple views too. See routing.
app({
model: true,
view: (model, msg) => html`<button onclick=${msg.toggle}>${model+""}</button>`,
update: {
toggle: model => !model
}
})
Effects cause side effects and are often asynchronous, like writing to a database, or sending requests to servers. They can dispatch other actions too.
Effects have a signature (model, msg, error), where
model is the current model,msg is an object you use to call reducers / cause effects (see view), anderror is a function you may call with an error if something goes wrong.const wait = time => new Promise(resolve => setTimeout(_ => resolve(), time))
const model = {
counter: 0,
waiting: false
}
const view = (model, msg) =>
html`
<button
onclick=${msg.waitThenAdd}
disabled=${model.waiting}>${model.counter}
</button>`
const update = {
add: model => ({ counter: model.counter + 1 }),
toggle: model => ({ waiting: !model.waiting})
}
const effects = {
waitThenAdd: (model, msg) => {
msg.toggle()
wait(1000).then(msg.add).then(msg.toggle)
}
}
app({ model, view, update, effects })
Subscriptions are functions that run once when the DOM is ready. Use a subscription to register global events, like mouse or keyboard listeners.
While reducers and effects are actions you cause, you can't call subscriptions directly.
A subscription has a signature (model, msg, error).
app({
model: { x: 0, y: 0 },
update: {
move: (_, { x, y }) => ({ x, y })
},
view: model => html`<h1>${model.x}, ${model.y}</h1>`,
subs: [
(_, msg) => addEventListener("mousemove", e => msg.move({ x: e.clientX, y: e.clientY }))
]
})
Hooks are functions called for certain events during the lifetime of the app. You can use hooks to implement middleware, loggers, etc.
app({
model: true,
view: (model, msg) => html`
<div>
<button onclick=${msg.doSomething}>Log</button>
<button onclick=${msg.boom}>Error</button>
</div>`,
update: {
doSomething: model => !model,
},
effects: {
boom: (model, msg, 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)
}
})
Called when the model changes. Signature (lastModel, newModel, data).
Called when an action (reducer or effect) is dispatched. 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).
The root is the HTML element that will serve as a container for your app. If none is given, a div element is appended to the document.body.
Instead of a view as a single function, declare an object with multiple views and use the route path as the key.
app({
view: {
"*": (model, msg) => {},
"/": (model, msg) => {},
"/:slug": (model, msg, params) => {}
}
})
/ index route, also used when no other route matches
/:a/:b/:c matches a route with three components using the regular expression [A-Za-z0-9]+ and stores each captured group in the params object, which is passed into the view function.
The route path syntax is based in the same syntax found in Express.
const { app, html } = require("hyperapp")
const anchor = n => html`<h1><a href=${"/" + n}>${n}</a></h1>`
app({
view: {
"/": _ => anchor(Math.floor(Math.random() * 999)),
"/:key": (model, msg, { key }) => html`
<div>
<h1>${key}</h1>
<a href="/">Back</a>
</div>`
}
})
To update the address bar relative location and render a different view, use msg.setLocation(path).
app({
view: {
"/": (model, msg) => html`
<div>
<h1>Home</h1>
<button onclick=${_ => msg.setLocation("/about")}>About</button>
</div>`,
"/about": (model, msg) => html`
<div>
<h1>About</h1>
<button onclick=${_ => msg.setLocation("/")}>Home</button>
</div>`
}
})
As a bonus, we intercept all <a href="/path">...</a> clicks and call msg.setLocation("/path") for you. If you want to opt out of this, add the custom attribute data-no-routing to any anchor element that should be handled differently.
<a data-no-routing>...</a>
app({
view: {
"/": (model, msg) => html`
<div>
<h1>Home</h1>
<a href="/about">About</a>
</div>`,
"/about": (model, msg) => html`
<div>
<h1>About</h1>
<a href="/">Home</a>
</div>`
}
})
FAQs
The tiny framework for building hypertext applications.
The npm package hyperapp receives a total of 2,867 weekly downloads. As such, hyperapp popularity was classified as popular.
We found that hyperapp demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OWASP’s 2025 Top 10 introduces Software Supply Chain Failures as a new category, reflecting rising concern over dependency and build system risks.

Research
/Security News
Socket researchers discovered nine malicious NuGet packages that use time-delayed payloads to crash applications and corrupt industrial control systems.

Security News
Socket CTO Ahmad Nassri discusses why supply chain attacks now target developer machines and what AI means for the future of enterprise security.