#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