Majid Hajian
mhadaily
Reliable
Fast
Engaging
mhadaily
mhadaily
MaterialApp(
ThemeData(
name: "Majid Hajian",
location: "Oslo, Norway",
description: '''
Passionate Software engineer,
Community Leader, Author and international Speaker
''',
main: "Flutter/Dart, PWA, Performance",
homepage: "https://www.majidhajian.com",
socials: {
twitter: "https://www.twitter.com/mhadaily",
github: "https://www.github.com/mhadaily"
},
author: {
Pluralsight: "www.pluralsight.com/authors/majid-hajian",
Apress: "Progressive Web App with Angular, Book",
PacktPub: "PWA development, 7 hours video course",
Udemy: "PWA development, 7 hours video course",
}
founder: "Softiware As (www.Softiware.com)"
devDependencies: {
tea: "Ginger",
mac: "10.14+",
},
community: {
MobileEraConference: "Orginizer",
FlutterVikings: "Orginizer",
FlutterDartOslo: "Orginizer",
GDGOslo: "Co-Orginizer",
DevFestNorway: "Orginizer",
...more
})
);
Find me on the internet by
Cache API
XHR vs. Fetch API
let xhr = new XMLHttpRequest();
xhr.open('GET', 'http://domain/service');
// request state change event
xhr.onreadystatechange = function() {
// request completed?
if (xhr.readyState !== 4) return;
if (xhr.status === 200) {
// request successful - show response
console.log(xhr.responseText);
}
else {
// request error
}
};
// start request
xhr.send();
fetch(
'http://domain/service',
{ method: 'GET' }
)
.then( response => response.json() )
.then( json => console.log(json) )
.catch( error => console.error('error:', error) );
mhadaily
Cache API
Service Worker
mhadaily
self.addEventListener('fetch', (event) => {
console.log('[SW] Fetch ....');
const request = event.request;
event.respondWith()
}
Service worker
mhadaily
event.respondWith(
caches.match(request)
);
Service worker
mhadaily
event.respondWith(
fetch(event.request)
);
Service worker
mhadaily
event.respondWith(
caches.match(request).then( (res) => {
return res || fetch(request).then( (newRes) => {
caches.open(DYNAMIC_CACHE_VERSION)
.then( cache => cache.put(request, newRes) );
return newRes.clone();
});
})
);
Service worker
mhadaily
event.respondWith(
fetch(request)
.then((res) => {
caches.open(DYNAMIC_CACHE_VERSION)
.then(cache => cache.put(request, res));
return res.clone();
}) // Fallback to cache
.catch(err => caches.match(request))
);
Service worker
mhadaily
event.respondWith(
caches
.match(request).then((res) => {
const updatedResopnse = fetch(request)
.then((newRes) => {
cache.put(request, newRes.clone());
return newRes;
});
return res || updatedResopnse;
})
);
Service worker
mhadaily
const promiseRace = new Promise((resolve, reject) => {
let firstRejectionReceived = false;
const rejectOnce = () => {
if (firstRejectionReceived) {
reject('No response received.');
} else {
firstRejectionReceived = true;
}
};
fetch(request)
.then(res => res.ok ? resolve(res) : rejectOnce())
.catch(rejectOnce);
caches.match(request)
.then(res => res ? resolve(res) : rejectOnce())
.catch(rejectOnce);
});
event.respondWith(promiseRace);
Service worker
mhadaily
mhadaily
mhadaily
mhadaily
workbox.routing.registerRoute(
new RegExp('^https:\/\/my-api-server/post\/'),
workbox.strategies.networkOnly({
plugins: [bgSyncPlugin]
}),
'POST'
)
mhadaily
const bgSyncPlugin = new workbox.backgroundSync.Plugin('myQueueName', {
maxRetentionTime: 24 * 60 // Retry for max of 24 Hours
onSync: async((queue)=>{
let entry;
while ((entery = await queue.shiftRequest())){
try {
// Perform fetch request and perhaps show notificaiton to user
// you can also update cache
} catch(error) {
// remove request from the queue
}
}
});
});
mhadaily
const showNotification = () => {
self.registration.showNotification('Post Sent', {
body: 'You are back online and your post was successfully sent!',
icon: 'assets/icon/256.png',
badge: 'assets/icon/32png.png'
});
};
mhadaily
adaptiveLoading
mhadaily
2G
3G
4G
mhadaily
2G
3G
4G
mhadaily
workbox.routing.registerRoute(
new RegExp('/img/'),
workbox.strategies.cacheFirst({
cacheName: 'images',
plugins: [
adaptiveLoadingPlugin,
workbox.expiration.Plugin({
maxEntries: 50,
purgeOnQuotaError: true,
}),
],
}),
);
mhadaily
const adaptiveLoadingPlugin = {
requestWillFetch: async ({request}) => {
const urlParts = request.url.split('/');
let imageQuality;
switch (
navigator && navigator.connection
? navigator.connection.effectiveType
: ''
) {
//...
case '3g':
imageQuality = 'low_quality';
break;
//...
}
const newUrl = urlParts
.splice(urlParts.length - 1, 0, imageQuality)
.join('/')
.replace('.jpg', '.png');
const newRequest = new Request(newUrl.href, {headers: request.headers});
return newRequest;
},
};
mhadaily
PRECACHE
mhadaily
PRECACHE
shell_header.html
shell_footer.html
mhadaily
Dynamically changes
mhadaily
Fetch
Process
Fetch
Render
mhadaily
registerRoute(/\.html$/,
workbox.streams.strategy([
// Get from cache
() => cacheFirst.handle({
request: new Request("/shell_header.html"),
}),
// Get the body from the network
({request}) => networkFirst.handle({
request: `${request.url}?content=true`
});
// Get from cache
() => cacheFirst.handle({
request: new Request("/shell_footer.html"),
}),
]);
);
mhadaily
mhadaily
The web platform today is way more powerful than what we think!
let's embrace it!
Majid Hajian
mhadaily
Slides and link to source code
bit.ly/pwa-patterns
majid[at]softiware[dot]com
SVG icons credited to undraw.co