
Research
Security News
Lazarus Strikes npm Again with New Wave of Malicious Packages
The Socket Research Team has discovered six new malicious npm packages linked to North Korea’s Lazarus Group, designed to steal credentials and deploy backdoors.
HyperApp is a JavaScript library for building frontend applications.
1kb
and has no dependencies. We're not opinionated about your stack either; use Browserify with Hyperx; Webpack or Rollup with Babel/JSX, etc.npm i -S hyperapp
In Node.js.
import { h, app } from "hyperapp"
In the browser via the CDN.
const { h, app } = hyperapp
app({
model: 0,
reducers: {
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: "",
reducers: {
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={{
position: "absolute",
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 reducers = {
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, reducers, 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>,
reducers: {
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 })
}
})
Creates a virtual DOM node.
tag
is a tag name, e.g. div
or a function that returns a tree of virtual nodes.data
is an object with attributes, styles, events, properties, lifecycle methods, etc.children
is an array of children virtual nodes. (Optional)See the HyperApp User Guide for JSX/Hyperx setup instructions.
Starts the application.
app({ model, reducers, view, effects, subscriptions, root, router })
The model is a primitive type, array or object that represents the state of your application. HyperApp applications use a single model architecture.
const model = {
count: 0
}
Reducers are actions that describe how to derive a new model from the current model.
const reducers = {
add: model => model + 1,
sub: model => model - 1
}
A reducer can return a new model or part of a model. If it returns part of a model, that part will be merged with the current model.
A reducer can be triggered inside a view, effect or subscription.
A reducer has the following signature: (model, data, params)
.
model
is the current model.data
os the data sent to the reducer.When using the router, reducers receives an additional argument:
params
is an object with the matched route parameters.A view is a function that returns a virtual element tree. See h
.
A view has the following signature: (model, actions)
.
To send actions:
actions.action(data)
app({
model: true,
view: (model, actions) => <button onClick={actions.toggle}>{model+""}</button>,
reducers: {
toggle: model => !model
}
})
Lifecycle methods are functions that can be attached to virtual nodes in order to access actual DOM elements when they are created, updated or before they are removed.
HTMLElement
)HTMLElement
)HTMLElement
)app({
view: _ => <div onCreate={e => console.log(e)}></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)}
/>,
reducers: {
move: model => ({ x: model.x + 1, y: model.y + 1 })
},
subscriptions: [
(_, actions) => setInterval(_ => actions.move(), 10)
]
})
Effects are actions that cause side effects and are often 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 can 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 reducers = {
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, reducers, 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, attach mouse or keyboard event listeners, etc.
A subscription has the following signature: (model, actions, error)
.
app({
model: { x: 0, y: 0 },
reducers: {
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
}))
]
})
Hooks are 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: (oldModel, 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>,
reducers: {
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 root is the container of your application. If none is given, a div
element is appended to document.body and used as the container.
The router is any function with the following signature: (render, options)
.
You can define your own router or use the one provided with HyperApp.
import { h, app, router } from "hyperapp"
To use the router, pass it to app
.
app({
router,
view: {
"/": (model, actions) => {},
"/about": (model, actions) => {},
"/:key": (model, actions, params) => {}
}
})
The view
property is used as a dictionary of routes/views.
The key is the route and the value is the view function.
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
})
/
match the index route or use as a wildcard to select the view when no route matches.
/:key
match a route using the regular expression [A-Za-z0-9]+
. The matched key is passed to the view function via params
.
Call actions.setLocation(path)
to update the location.pathname. If the path matches an existing route, the corresponding view will be rendered. Requires the default Router.
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")
. 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 4,772 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.
Research
Security News
The Socket Research Team has discovered six new malicious npm packages linked to North Korea’s Lazarus Group, designed to steal credentials and deploy backdoors.
Security News
Socket CEO Feross Aboukhadijeh discusses the open web, open source security, and how Socket tackles software supply chain attacks on The Pair Program podcast.
Security News
Opengrep continues building momentum with the alpha release of its Playground tool, demonstrating the project's rapid evolution just two months after its initial launch.