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