Working Off the Main Thread

@henrikjoreteg

Pocket-sized JS

dotJS 2015

How to approach the Mobile Web

Actually test on mobile devices

If you want to write fast software use a slow computer

Send HTML

<!doctype html>
<script src="app.js"></script>

Ship less code

Google+ < 60kb JS
Soundslice ~94kb JS

It may be hard but it's not impossible

WEBWORKERS!

Even low-end android phones have multiple cores

Ask any iOS or Android developer how we make our apps so fast,
and most likely you'll hear about two major strategies

Eliminate network calls
Use background threads

Anything unrelated to the UI should be
offloaded to a background thread.

Working in the Main Thread should be the exception

feather-app

~8.5kb JS

http://feather.surge.sh

The Main Thread has 3 responsibilities

sending "state of the world" to worker on start
listening for and sending serializable actions back to the worker
applying DOM patches when received from worker

worker.thread.js

import diff from 'virtual-dom/diff'
import serializePatch from 'vdom-serialized-patch/serialize'
import fromJson from 'vdom-as-json/fromJson'
import app from './views/app'

let currentVDom
const state = {
  count: 0,
  url: '/'
}

self.onmessage = ({data}) => {
  const { type, payload } = data
  
  switch (type) {
    case 'start': {
      currentVDom = fromJson(payload.virtualDom)
      state.url = payload.url
      break
    }
    case 'setUrl': {
      state.url = payload
      break
    }
    case 'increment': {
      state.count++
      break
    }
    case 'decrement': {
      state.count--
      break
    }
  }
  
  const newVDom = app(state)
  const patches = diff(currentVDom, newVDom)
  currentVDom = newVDom
  
  self.postMessage({url: state.url, payload: serializePatch(patches)})
}

main.js

import WorkerThread from './worker.thread'
import virtualize from 'vdom-virtualize'
import toJson from 'vdom-as-json/toJson'
import applyPatch from 'vdom-serialized-patch/patch'
import { getLocalPathname } from 'local-links'

// webpack's worker-loader
const worker = new WorkerThread()
const rootElement = document.body.firstChild

worker.onmessage = ({data}) => {
  const { url, payload } = data
  requestAnimationFrame(() => {
    applyPatch(rootElement, payload)
  })
  
  if (location.pathname !== url) {
    history.pushState(null, null, url)
  }
}

// init virtual-dom with server-rendered html
worker.postMessage({type: 'start', payload: {
  virtualDom: toJson(virtualize(rootElement)),
  url: location.pathname
}})

// Support back/forward buttons
window.addEventListener('popstate', () => {
  worker.postMessage({type: 'setUrl', payload: location.pathname})
})

// listen for all events globally
document.body.addEventListener('click', (event) => {

  const pathname = getLocalPathname(event)
  if (pathname) {
    event.preventDefault()
    
    worker.postMessage({type: 'setUrl', payload: pathname})
    return
  }
  
  const click = event.target['data-click']
  if (click) {
    event.preventDefault()
    worker.postMessage(click)
  }
})

app.js

import home from './home'
import about from './about'

export default (state) => {
  const { url } = state
  let page
  
  if (url === '/') {
    page = home(state)
  } else if (url === '/about') {
    page = about()
  }
  
  return (
    <main>
      <h1>Feather POC App</h1>
      <nav>
        <a href='/'>home</a> | <a href='/about'>about</a>
      </nav>
      {page}
    </main>
  )
}

home.js

export default ({count}) => (
  <div>
    <p>This app weighs about 8.5kb</p>
    <button data-click={{type: 'decrement'}}> - </button>
    <span> {count} </span>
    <button data-click={{type: 'increment'}}> + </button>
  </div>
)

How to approach the Mobile Web

At best

Unless you can do all your work in 16ms 8ms

At worst

Thanks

@markbrown4