
Security News
Deno 2.2 Improves Dependency Management and Expands Node.js Compatibility
Deno 2.2 enhances Node.js compatibility, improves dependency management, adds OpenTelemetry support, and expands linting and task automation for developers.
HyperApp is a 1kb
JavaScript library for building modern UI applications.
npm i hyperapp
ES6
import { h, app } from "hyperapp"
CommonJS
const { h, app } = require("hyperapp")
HyperApp is also distributed as a minified file, hosted on a CDN.
<script src="https://unpkg.com/hyperapp/dist/hyperapp.js"></script>
For a complete introduction to HyperApp see the User Guide.
app({
model: 0,
update: {
add: model => model + 1,
sub: model => model - 1
},
view: (model, actions) =>
<div>
<button onclick={actions.add}>+</button>
<h1>{model}</h1>
<button onclick={actions.sub} disabled={model <= 0}>-</button>
</div>
})
app({
model: "",
update: {
text: (_, value) => value
},
view: (model, actions) =>
<div>
<h1>Hi{model ? " " + model : ""}.</h1>
<input oninput={e => actions.text(e.target.value)} />
</div>
})
const model = {
dragging: false,
position: {
x: 0, y: 0, offsetX: 0, offsetY: 0
}
}
const view = (model, actions) =>
<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 })
const FilterInfo = { All: 0, Todo: 1, Done: 2 }
app({
model: {
todos: [],
filter: FilterInfo.All,
input: "",
placeholder: "Add new todo!"
},
view: (model, actions) =>
<div>
<h1>Todo</h1>
<p>
Show: {Object.keys(FilterInfo)
.filter(key => FilterInfo[key] !== model.filter)
.map(key =>
<span><a data-no-routing 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 =>
<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>,
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 })
}
})
Via .babelrc
.
{
"plugins": [
[ "transform-react-jsx", { "pragma": "h" } ]
]
}
Alternatively, use the jsx pragma.
import { h, app } from "hyperapp"
/** @jsx h */
Bundle with Browserify, Webpack, Rollup, etc.
browserify \ -t babelify \ -g uglifyify \ -p bundle-collapser/plugin index.js | uglifyjs > bundle.js
HyperApp can be used with ES6 template functions via Hyperx.
npm i hyperx
const { h, app } = require("hyperapp")
const hyperx = require("hyperx")
const html = hyperx(h)
app({
model: "Hi.",
view: model => html`<h1>${model}</h1>`
})
browserify \ -t hyperxify \ -t babelify \ -g uglifyify \ -p bundle-collapser/plugin index.js | uglifyjs > bundle.js
Use app
to start the app.
app({ model, update, view, effects, subscriptions, root, router })
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 entirely new model or part of a model. If a reducer returns part of a model, it will be merged with the current model.
Reducers can be triggered inside a view, effect or subscription.
Reducers have the signature (model, data, params)
:
model
is the current model.data
is the data sent to the reducer.When using the Router, the view receives an additional argument:
params
an object with the matched route parameters.A function that returns an HTML element using jsx or the html
function.
A view has the signature (model, actions)
:
To use actions:
actions.action(data)
data
is any data you want to send to action
.action
is the name of the reducer or effect.app({
model: true,
view: (model, actions) => <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.
HTMLElement
)HTMLElement
)HTMLElement
)app({
view: _ => <div oncreate={e => console.log(e)}>Hi.</div>
})
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 => <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 can be asynchronous, like writing to a database, or sending requests to servers.
Effects have the following signature: (model, actions, data, error)
.
model
is the current model.actions
is an object used to trigger reducers and effects.data
is the data sent to the effect.error
is a function you may call to throw an error.const wait = time => new Promise(resolve => setTimeout(_ => resolve(), time))
const model = {
counter: 0,
waiting: false
}
const view = (model, actions) =>
<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)
.
app({
model: { x: 0, y: 0 },
update: {
move: (_, { x, y }) => ({ x, y })
},
view: model => <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. There are three: onUpdate
, onAction
, and onError
.
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)
.
app({
model: true,
view: (model, actions) =>
<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 container.
HyperApp provides a router out of the box.
import { h, app, router } from "hyperapp"
app({ view, router })
When using the router, view
must be an object that consists of routes, each with a corresponding view function.
view: {
"/": (model, actions) => {},
"/about": (model, actions) => {},
"/:key": (model, actions, params) => {}
}
const Anchor = ({ href }) => <h1><a href={"/" + href}>{href}</a></h1>
app({
view: {
"/": _ => <Anchor href={Math.floor(Math.random() * 999)}></Anchor>,
"/:key": (model, actions, { key }) =>
<div>
<h1>{key}</h1>
<a href="/">Back</a>
</div>
},
router
})
/
matches the index route or when no other route matches.
/:key
matches a route using the regular expression [A-Za-z0-9]+
. The matched key is passed to the route's view function via params
.
A special action available when using the Router. Use actions.setLocation(path)
to update the location.pathname. If the path matches an existing route, the corresponding view will be rendered.
const Page = ({ title, target, onclick }) =>
<div>
<h1>{title}</h1>
<button onclick={onclick}>{target}</button>
</div>
app({
view: {
"/": (model, actions) =>
<Page
title="Home"
target="About"
onclick={_ => actions.setLocation("/about")}>
</Page>
,
"/about": (model, actions) =>
<Page
title="About"
target="Home"
onclick={_ => actions.setLocation("/")}>
</Page>
},
router
})
HyperApp intercepts all <a href="/path">...</a>
clicks and calls action.setLocation("/path")
for convenience. External links and links that begin with a #
character are not intercepted.
app({
view: {
"/": (model, actions) =>
<div>
<h1>Home</h1>
<a href="/about">About</a>
</div>
,
"/about": (model, actions) =>
<div>
<h1>About</h1>
<a href="/">Home</a>
</div>
},
router
})
Add a custom data-no-routing
attribute to anchor elements that should be handled differently.
<a data-no-routing>Not a route</a>
FAQs
The tiny framework for building hypertext applications.
The npm package hyperapp receives a total of 3,576 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
Deno 2.2 enhances Node.js compatibility, improves dependency management, adds OpenTelemetry support, and expands linting and task automation for developers.
Security News
React's CRA deprecation announcement sparked community criticism over framework recommendations, leading to quick updates acknowledging build tools like Vite as valid alternatives.
Security News
Ransomware payment rates hit an all-time low in 2024 as law enforcement crackdowns, stronger defenses, and shifting policies make attacks riskier and less profitable.