April 21st, 2020
Q&A and polls at 👉
https://lizkeogh.com/2019/07/02/off-the-charts/
+3 degrees Celsius will be the end.
https://350mass.betterfutureproject.org/
Boston JS Slack #climate_action
survival is possible. but we need to act
C / C++ / C# / Java / CoffeeScript / JavaScript / Node / Angular / Vue / Cycle.js / functional
Me: JavaScript ninja, image processing expert, software quality fanatic
Cambridge, MA
Cypress.io open source E2E test runner
Q&A Sli.do event code #new-server
Poll: Which Worker technology are you familiar with?
If signal falls while you are in the forest ...
<html manifest="example.appcache">
...
</html>
CACHE MANIFEST
# v1 2011-08-14
index.html
style.css
image1.png
# Use from network if available
NETWORK:
network.html
# Fallback content
FALLBACK:
/ fallback.html
declarative list
Turns out declaring caching strategy is hard.
ServiceWorker
Server
browser
Web Workers
ServiceWorker
Transforms
the response
Transforms
the request
Server
browser
service
worker
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
navigator.serviceWorker.register(
'app/my-service-worker.js')
Must be 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
Offline support using ServiceWorker
self.addEventListener('install', e => {
const urls = ['/', 'app.css', 'app.js']
e.waitUntil(
caches.open('my-app')
.then(cache =>
cache.addAll(urls))
)
})
Cache resources on SW install
self.addEventListener('fetch', e => {
e.respondWith(
caches.open('my-app')
.then(cache =>
cache => match(e.request))
)
})
Return cached resource
const cacheResources = async () => {
const urlsToCache = ['/', 'app.css']
const cache = await caches.open('demo')
return cache.addAll(urlsToCache)
}
self.addEventListener('install', event =>
event.waitUntil(cacheResources())
)
Async / await in SW
const cachedResource = async req => {
const cache = await caches.open('demo')
return await cache.match(req)
}
self.addEventListener('fetch', event =>
event.respondWith(cachedResource(event.request))
)
Return cached resource
Use
Preload views using SW
Watch what happens on hover
Server
browser
ServiceWorker
hover
prefetch
next page
(cached)
Server
browser
ServiceWorker
hover
prefetch
next page
(cached)
click
fetch
Server
browser
ServiceWorker
hover
prefetch
next page
(cached)
click
fetch
Server
browser
ServiceWorker
hover
prefetch
next page
(cached)
click
fetch
cached
Server
browser
ServiceWorker
hover
prefetch
next page
(cached)
click
fetch
cached
The blue Δ ms gives you extra time to fetch = faster page loads
<!-- installs Turtle SW -->
<script src="turtle.js"></script>
<script>
turtle.get('/some/url', { code: 502, timeout: 3000 });
</script>
// mocks.js
// 1. Import mocking utils
import { composeMocks, rest } from 'msw'
// 2. Define request handlers and response resolvers
const { start } = composeMocks(
rest.get('https://github.com/octocat', (req, res, ctx) => {
return res(
ctx.delay(1500),
ctx.status(202, 'Mocked status'),
ctx.json({
message: 'This is a mocked error',
}),
)
}),
)
// 3. Start the Service Worker
start()
// src/index.js (your application)
if (process.env.NODE_ENV === 'development') {
require('./mocks')
}
Using CDN, parallel downloads, caching, small images, above the fold, etc.
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!
nice
what?
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>
app
HTML
data
loads
renders
same
same
app
HTML
data
loads
renders
Every time user
changes the page:
On page reload:
Save data + HTML in localStorage
Load HTML QUICKLY from localStorage
show static HTML while bootstrapping web app ...
Single small JS library bahmutov/hydrate-app
<div id="app">
</div>
<script src="hydrate-app.js"></script>
Works with any web framework
// hydrate-app.js
document.getElementById('app').innerHTML =
localStorage.getItem('saved-html')
app
HTML
data
loads
loads
renders
<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 cached "app" HTML
HTML
app
HTML
data
loads
loads
renders
<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>
Real web app renders into hidden DOM element
HTML
hydrate.js loads cached "app" HTML
app
HTML
data
loads
loads
renders
App says "I am ready!"
Live web app content replaces static cached HTML snapshot
HTML
<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>
app
HTML
data
loads
loads
renders
App says "I am ready!"
Live web app content replaces static cached HTML snapshot
Progressive web app for $0.05
HTML
<div id="app">
<ul>
<li>Clean my room</li>
...
</ul>
</div>
There is a flash (sometimes)
<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
Full page reloads on every action are SLOW 👎
Rendering static page on reload is FAST 👍
Server
browser
ServiceWorker
<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
Normal client changes
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
Page reload
Q&A Sli.do event code #new-server
Poll: Based on what you have seen, what do you want to use SW for??
browser
ServiceWorker
Request
Response
Server
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)
Video: https://youtu.be/4axZ3D75Llg?t=31m30s
The library: github.com/bahmutov/express-service
Blog post with examples: Run Express server in your browser
Web application thinks it is interacting with an external server that renders full pages. Yet the server is in ServiceWorker
1. Sending Push events to the site, image or video transcoding, code instrumentation and rewriting on the fly
2. Sent website as a ZIP file
3. Offline analytics
Malicious ServiceWorker injected via XSS can be really hard to get rid of
Please protect yourself from XSS
Q&A Sli.do event code #new-server
Poll: What do you think about ServiceWorkers after this presentation? ⭐️: don't care, ⭐️⭐️⭐️⭐️⭐️: WOW!
Add offline support with ServiceWorker
Go crazy
Use responsibly
like ServiceWorker but outside the browser: https://blog.cloudflare.com/introducing-cloudflare-workers/