Gleb Bahmutov PRO
JavaScript ninja, image processing expert, software quality fanatic
#boscc
Gleb Bahmutov, PhD
Boston Code Camp April 2018
C / C++ / C# / Java / CoffeeScript / JavaScript / Node / Angular / Vue / Cycle.js / functional
Cypress.io open source E2E test runner
1 kB JavaScript framework for building web applications.
"Introducing Hyperapp 1.0 🎉"
We have aggressively minimized the concepts you need to understand to be productive while remaining on par with what other frameworks can do.
github.com/hyperapp/hyperapp README
Hyperapp holds firm on the functional programming front when managing your state, but takes a pragmatic approach to allowing for side effects, asynchronous actions, and DOM manipulations.
github.com/hyperapp/hyperapp README
Do more with less. Hyperapp combines state management with a virtual DOM engine that supports keyed updates & lifecycle events — all with no dependencies.
github.com/hyperapp/hyperapp README
This is the most heartwarming picture of the Elm Architecture I've ever seen. 😍
Richard Feldman
actions
<head>
<script src="https://unpkg.com/hyperapp"></script>
</head>
<body>
<script>
const {h, app} = window.hyperapp
const view = () =>
h('div', {class: 'hello'}, 'Hello World!')
app(null, null, view, document.body)
</script>
</body>
"Hello World!" Hyperapp
<head>
<script src="https://unpkg.com/hyperapp"></script>
</head>
<body>
<script>
const {h, app} = window.hyperapp
const view = () =>
h('div', {class: 'hello'}, 'Hello World!')
app(null, null, view, document.body)
</script>
</body>
"Hello World!" Hyperapp
Single script
<head>
<script src="https://unpkg.com/hyperapp"></script>
</head>
<body>
<script>
const {h, app} = window.hyperapp
const view = () =>
h('div', {class: 'hello'}, 'Hello World!')
app(null, null, view, document.body)
</script>
</body>
"Hello World!" Hyperapp
Virtual DOM helper
<head>
<script src="https://unpkg.com/hyperapp"></script>
</head>
<body>
<script>
const {h, app} = window.hyperapp
const view = () =>
h('div', {class: 'hello'}, 'Hello World!')
app(null, null, view, document.body)
</script>
</body>
"Hello World!" Hyperapp
Application helper
<head>
<script src="https://unpkg.com/hyperapp"></script>
</head>
<body>
<script>
const {h, app} = window.hyperapp
const view = () =>
h('div', {class: 'hello'}, 'Hello World!')
app(null, null, view, document.body)
</script>
</body>
"Hello World!" Hyperapp
View function
<head>
<script src="https://unpkg.com/hyperapp"></script>
</head>
<body>
<script>
const {h, app} = window.hyperapp
const view = () =>
h('div', {class: 'hello'}, 'Hello World!')
app(null, null, view, document.body)
</script>
</body>
"Hello World!" Hyperapp
Element name "div"
<head>
<script src="https://unpkg.com/hyperapp"></script>
</head>
<body>
<script>
const {h, app} = window.hyperapp
const view = () =>
h('div', {class: 'hello'}, 'Hello World!')
app(null, null, view, document.body)
</script>
</body>
"Hello World!" Hyperapp
Element attributes
<head>
<script src="https://unpkg.com/hyperapp"></script>
</head>
<body>
<script>
const {h, app} = window.hyperapp
const view = () =>
h('div', {class: 'hello'}, 'Hello World!')
app(null, null, view, document.body)
</script>
</body>
"Hello World!" Hyperapp
Element contents
View function returns virtual node
<head>
<script src="https://unpkg.com/hyperapp"></script>
</head>
<body>
<script>
const {h, app} = window.hyperapp
const view = () =>
h('div', {class: 'hello'}, 'Hello World!')
app(null, null, view, document.body)
</script>
</body>
"Hello World!" Hyperapp
Start application
<head>
<script src="https://unpkg.com/hyperapp"></script>
</head>
<body>
<script>
const {h, app} = window.hyperapp
const view = () =>
h('div', {class: 'hello'}, 'Hello World!')
app(null, null, view, document.body)
</script>
</body>
"Hello World!" Hyperapp
Start application
View function
<head>
<script src="https://unpkg.com/hyperapp"></script>
</head>
<body>
<script>
const {h, app} = window.hyperapp
const view = () =>
h('div', {class: 'hello'}, 'Hello World!')
app(null, null, view, document.body)
</script>
</body>
"Hello World!" Hyperapp
Start application
Target DOM element
const state = {
message: 'Boston!'
}
const view = (state) =>
h('div',
{class: 'hello'},
`Hello ${state.message}`
)
app(state, null, view, document.body)
"Hello <message>!" Hyperapp
const state = {
message: 'Boston!'
}
const view = (state) =>
h('div',
{class: 'hello'},
`Hello ${state.message}`
)
app(state, null, view, document.body)
"Hello <message>!" Hyperapp
Initial state
const state = {
message: 'Boston!'
}
const view = (state) =>
h('div',
{class: 'hello'},
`Hello ${state.message}`
)
app(state, null, view, document.body)
"Hello <message>!" Hyperapp
Pass initial state
const state = {
message: 'Boston!'
}
const view = (state) =>
h('div',
{class: 'hello'},
`Hello ${state.message}`
)
app(state, null, view, document.body)
"Hello <message>!" Hyperapp
Current state
const {h, app} = window.hyperapp
const state = {
message: ''
}
const actions = {
onchange: e => state =>
({message: e.target.value})
}
"Hello <input>!" Hyperapp
const {h, app} = window.hyperapp
const state = {
message: ''
}
const actions = {
onchange: e => state =>
({message: e.target.value})
}
"Hello <input>!" Hyperapp
Object of actions
const {h, app} = window.hyperapp
const state = {
message: ''
}
const actions = {
onchange: e => state =>
({message: e.target.value})
}
"Hello <input>!" Hyperapp
Action takes arguments
const {h, app} = window.hyperapp
const state = {
message: ''
}
const actions = {
onchange: e => state =>
({message: e.target.value})
}
"Hello <input>!" Hyperapp
Then current state
const {h, app} = window.hyperapp
const state = {
message: ''
}
const actions = {
onchange: e => state =>
({message: e.target.value})
}
"Hello <input>!" Hyperapp
and returns new state
const view = (state, actions) =>
h('div',
{class: 'hello'},
[`Hello ${state.message}`,
h('input', {
type: 'text',
placeholder: 'enter your name',
onchange: actions.onchange
})]
)
app(state, actions, view, document.body)
"Hello <input>!" Hyperapp
Pass actions to view
const view = (state, actions) =>
h('div',
{class: 'hello'},
[`Hello ${state.message}`,
h('input', {
type: 'text',
placeholder: 'enter your name',
onchange: actions.onchange
})]
)
app(state, actions, view, document.body)
"Hello <input>!" Hyperapp
Call action on event
const view = (state, actions) =>
h('div',
{class: 'hello'},
[`Hello ${state.message}`,
h('input', {
type: 'text',
placeholder: 'enter your name',
onchange: actions.onchange
})]
)
app(state, actions, view, document.body)
"Hello <input>!" Hyperapp
Hyper app
Uses pure functions
const view = (state, actions) =>
h('div',
{class: 'hello'},
[`Hello ${state.message}`,
h('input', {
type: 'text',
placeholder: 'enter your name',
onchange: actions.onchange
})]
)
View is a pure function, state and actions are arguments
const actions = {
onchange: e => (state, actions) =>
({message: e.target.value})
}
Actions are pure functions with arguments, state, and other actions
Handles view + state
import {state} from './state'
import {actions} from './actions'
import {view} from './view'
import {app} from 'hyperapp'
app(state, actions, view, document.body)
app.js
<body>
<script src="app.js"></script>
</body>
index.html
{
"scripts": {
"start": "parcel serve ./index.html"
},
"dependencies": { "hyperapp": "1.2.5" },
"devDependencies": {
"parcel-bundler": "1.7.0"
}
}
package.json
import {h} from 'hyperapp'
export const view = (state, actions) => (
<div className="hello">
Hello {state.message}
<input type="text"
placeholder="enter your name"
onchange={actions.onchange} />
</div>)
view.js
$ npm start
export type State = {
message: string
}
export const state:State = {
message: ''
}
state.ts
Pro tip: slice state and actions
const state = {
foo: { message: '' },
bar: { age: 21 }
}
const actions = {
foo: {
// slice = current state.foo
onchange: e => slice =>
({message: e.target.value})
},
bar: {},
print: () => state => console.log(state)
}
Pro tip: call actions yourself
const actions = {
...
print: () => state => console.log(state)
}
window.__app = app(state, actions, view, document.body)
Pro tip: split view functions
// pure arguments => virtual node function
const HelloX = ({greeting, name}) =>
h('p', {}, `${greeting} ${name}`)
// call HelloX with arguments
const view = (state) =>
h('div',
{class: 'hello'},
[HelloX({
greeting: 'Hello',
name: state.message
})]
)
Pro tip: actions can call other actions
const actions = {
set: todos => state => ({todos}),
click: () => (state, actions) => {
fetch()
.then(r => r.json())
.then(actions.set)
}
}
Pragmatic approach to side effects
E2E
unit
it('logs user in', () => {
cy.visit('page.com')
cy.get('#login').click()
})
/// <reference types="cypress" />
beforeEach(() => {
cy.visit('http://localhost:1234')
})
it('greets', () => {
cy.get('input').type('tester{enter}')
cy.contains('.hello', 'Hello tester')
.should('be.visible')
})
$ npm i -D cypress start-server-and-test parcel-bundler
{
"scripts": {
"start": "parcel serve ./index.html",
"build": "parcel build ./index.html",
"cy:open": "cypress open",
"cy:run": "cypress run",
"e2e": "server-test 1234 cy:open",
"ci": "server-test 1234 cy:run"
}
}
package.json
cypress/integration/e2e.js
Cypress.io running end to end test
E2E
unit
import {view} from '../src/view'
import {mount} from
'cypress-hyperapp-unit-test'
it('renders', () => {
mount(view)
cy.click(...)
})
Unit testing using Cypress via an adaptor
By Gleb Bahmutov
What is the smallest and purest front end framework out there? Did someone say Elm? I said smallest! The answer is Hyperapp - a really tiny, Elm-inspired pure functional web framework that combines the best features of today's hottest frameworks. It has built-in state management, pure functions, virtual DOM and still is easy to learn in 30 minutes. Presented at Boston Code Camp in April 2018 and BostonJS meetup
JavaScript ninja, image processing expert, software quality fanatic