#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 startexport 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