Gleb Bahmutov PRO
JavaScript ninja, image processing expert, software quality fanatic
* Web and Service Workers
Boston and NYC and hiring!
I love solving AngularJS performance problems
Simple(r)
WebWorkers
Don't do too much
Using CDN, parallel downloads, caching, small images, above the fold, etc.
HTML
JS
CSS
HTML
JS
CSS
Framework
HTML
JS
CSS
Framework
app
HTML
JS
CSS
Framework
app
get data
HTML
JS
CSS
Framework
app
draw
get data
HTML
JS
CSS
Framework
app
draw
get data
HTML
Hosting / ops
App author
JS
CSS
Framework
app
draw
get data
HTML
Cached by the browser
You should cache
JS
CSS
Framework
app
draw
get data
HTML
This part can be expensive!
AngularJS 1 startup flame chart
ReactJS startup flame chart
nice
what?
If your JS file can overwhelm DevTools - you are doing it wrong.
2.6MB .js file
single 2.6MB .js file
548 "exports" inside
jQuery + 12 plugins
Backbone
React
...
I am not mad, just disappointed
Initial HTML loads
Application code loads
<html>
<script src="app.js"></script>
<body>
<h1>My awesome app</h1>
<div id="app"></div>
<footer>...</footer>
</body>
</html>
- App loads data
- App updates page
<html>
<script src="app.js"></script>
<body>
<h1>My awesome app</h1>
<div id="app"></div>
<footer>...</footer>
</body>
</html>
- App loads data
- App updates page
Sudden change in the page!
<html>
<script src="app.js"></script>
<body>
<h1>My awesome app</h1>
<div id="app">
<ul>
<li>Clean my room</li>
<li>Learn Italian</li>
<li>Finish the slides</li>
</ul>
</div>
<footer>...</footer>
</body>
</html>
Rendered view is in the middle of the page
source: glebbahmutov.com/hydrate-vue-todo/ (no hydration)
app
HTML
data
load
load
render
app
HTML
data
load
load
render
Load # N
- App loads data
- App updates page
Load # N + 1
Load HTML QUICKLY from localStorage
- load normally ...
Save data + HTML in localStorage
Single small JS library bahmutov/hydrate-app
<div id="app">
...
</div>
<script src="hydrate-app.js" id="app"></script>
Works with any web framework
// hydrate-app gives you window.bottle
bottle.drink() // app has loaded and ready to take over
bottle.refill() // save DOM for next reload
app
HTML
data
load
load
render
<div id="app">
<ul>
<li>Clean my room</li>
<li>Learn Italian</li>
<li>Finish the slides</li>
</ul>
</div>
<div id="real-app" class="hidden">
</div>
hydrate.js loads "app" HTML
HTML
app
HTML
data
load
load
render
<div id="app">
<ul>
<li>Clean my room</li>
<li>Learn Italian</li>
<li>Finish the slides</li>
</ul>
</div>
<div id="real-app" class="hidden">
<ul>
<li>Clean my room</li>
...
</ul>
</div>
hydrate.js loads "app" HTML
Web app renders into hidden DOM element
HTML
app
HTML
data
load
load
render
<div id="app">
<ul>
<li>Clean my room</li>
...
</ul>
</div>
bottle.drink()
Static cached HTML content replaced with live web app content
Progressive web app for $0.05
HTML
There is a flash
<body>
<header>...</header>
<div id="app"></div>
<script src="hydrate-app.js"></script>
</body>
const html = localStorage.get ...
$('#app').innerHTML = '...'
Self-rewriting page is never going to be as smooth as server-side rendering
The Incredibles - at the principal's office
The page HTML arrives ready to be rendered by the browser
The browsers are REALLY REALLY good at rendering static pages
Server
browser
webworkers
ServiceWorker
Transforms
the response
Transforms
the request
navigator.serviceWorker.register('app/my-service-worker.js',
{ scope: '/proxied' })
Chrome, Opera
Firefox behind a flag
https!
self.addEventListener('install', ...)
self.addEventListener('activate', ...)
self.addEventListener('message', ...)
self.addEventListener('push', ...)
self.addEventListener('fetch', function (event) {
console.log(event.request.url)
event.respondWith(...)
})
// Cache API
browser | web worker | service worker |
---|---|---|
window | self | self |
localStorage | IndexedDB, Cache API (async) | IndexedDB, Cache API (async) |
XMLHttpRequest, fetch | XMLHttpRequest, fetch |
fetch |
document |
Promises, postMessage
Server
browser
ServiceWorker
<html>
<body>
<header>...</header>
<div id="app"></div>
<footer>...</footer>
</body>
</html>
<html>
<body>
<header>...</header>
<div id="app">
<ul>
<li>Clean my room</li>
...
</ul>
</div>
<footer>...</footer>
</body>
</html>
<ul>
<li>Clean my room</li>
...
</ul>
Cache
Inserts cached HTML fragment into response body HTML
self.addEventListener('fetch', function (event) {
event.respondWith(
fetch(event.request)
.then(function (response) {
// merge HTML
var responseOptions = {
status: 200,
headers: {
'Content-Type': 'text/html charset=UTF-8'
}
}
return new Response(resultHtml, responseOptions)
})
)
})
browser
ServiceWorker
Request
Response
Server
browser
ServiceWorker
Request
Response
http.ClientRequest
http.ServerResponse
Server
express.js
Server
browser
ServiceWorker
Request
Response
express.js
http.ClientRequest
JavaScript
http.ServerResponse
JavaScript
browser
ServiceWorker
Server
express.js
Server
browser
ServiceWorker
express.js
http.ClientRequest(Request)
http.ServerResponse(Response)
express.js (and middleware)
ServiceWorker env
browserify
var express = require('express')
var app = express()
var url = require('url')
self.addEventListener('fetch', function (event) {
const parsedUrl = url.parse(event.request.url)
var req = {
url: parsedUrl.href,
method: event.request.method,
body: body,
headers: {
'content-type': event.request.headers.get('content-type')
},
unpipe: function () {}
}
var res = { /* our mock Node http Server Response object */ }
event.respondWith(new Promise(function (resolve) {
app(req, res)
}))
})
"app" is a regular Express.js application
There is no client-side* JavaScript :)
<link rel="serviceworker" scope="/" href="/sw.js">
*after first ServiceWorker registration
browser | web worker | service worker |
---|---|---|
window | self | self |
localStorage | IndexedDB, Cache API (async) | IndexedDB, Caches API (async) |
XMLHttpRequest, fetch | XMLHttpRequest, fetch |
fetch |
document |
Promises, postMessage, same JS engine
Server
browser
ServiceWorker
// full ES2015 bundle
const foo = ({a, b=2})
=> a + b
var foo = function foo(_ref) {
var a = _ref.a;
var _ref$b = _ref.b;
var b = _ref$b === undefined
? 2 : _ref$b;
return a + b;
};
worker.js
Test ES2015 feature support and transpile
worker.js
JS feature detection
Babel transpiler
const foo = ({a, b=2})
=> a + b
Chrome Canary
Jake Archibald (Google) - Using ServiceWorker in Chrome today, The ServiceWorker is coming, look busy
Matt Gaunt (Google) - Unit testing ServiceWorker
Gleb Bahmutov (Kensho) - glebbahmutov.com/blog/tags/service-workers
* Web and Service Workers
slides.com/bahmutov
@bahmutov
glebbahmutov.com
By Gleb Bahmutov
ServiceWorkers for faster web application boot up. Instant web applications, self-rewriting apps, profiling, ES2015 and modern browsers. Presented at the Web Performance NYC meetup. Video https://youtu.be/4axZ3D75Llg
JavaScript ninja, image processing expert, software quality fanatic