SlideShare a Scribd company logo
1 of 77
Download to read offline
Media in the
Age of PWAs
Aaron Gustafson
@AaronGustafson
slideshare.net/AaronGustafson


PWA?
What exactly is a

Progressive Web App?
What exactly is a

What exactly is a
Progressive Web App?
What exactly is a
Progressive Web App?
What exactly is a
Progressive Web App?
“Progressive Web App”

is a marketing term
Progressive Web App

Progressive Web App

Progressive Web App

Game
Gallery
Book
Newspaper
Art Project
Progressive Web Site

Who’s behind PWAs?

@AaronGustafson
A Minimum Viable PWA
HTTPS
@AaronGustafson
A Minimum Viable PWA
HTTPS Web App

Manifest
@AaronGustafson
Web App Manifest
{	
		"lang":	"en",	
		"short_name":	"Wash	Post",	
		"name":	"The	Washington	Post",	
		"icons":	[	{	"src":	"img/launcher-icon-2x.png",	
															"sizes":	"96x96",	
															"type":	"image/png"	}	],	
		"start_url":	"/pwa/",	
		"display":	"standalone",	
		"orientation":	"portrait",	
		"background_color":	"black"	
}
@AaronGustafson
A Minimum Viable PWA
HTTPS Web App

Manifest
Service
Worker
Should you

believe the hype?
Maybe?
Carnival:

24% opt-in rate and
42% open rate for
push notifications
Katarzyna Ostrowska
aka.ms/carnival-pwa
Starbucks:

2x increase in daily
active users
aka.ms/google-io-2018
Tinder:

Core experience

with 90% less code
aka.ms/tinder-pwa-2017
Trivago:

97% increase in

click-outs to 

hotel offers
aka.ms/trivago-pwa-2017
West Elm:

15% increase in

time on site

9% increase in
revenue per visit
aka.ms/west-elm-pwa-2017
@AaronGustafson
A Minimum Viable PWA
HTTPS Web App

Manifest
Service
Worker
@AaronGustafson
Let’s talk about Service Worker
@AaronGustafson
Registering a Service Worker
if	(	"serviceWorker"	in	navigator	)	{



		navigator.serviceWorker.register(	"/serviceworker.min.js"	);



}
@AaronGustafson
Registering a Service Worker
if	(	"serviceWorker"	in	navigator	)	{



		navigator.serviceWorker.register(	"/serviceworker.min.js"	);



}
@AaronGustafson
Registering a Service Worker
if	(	"serviceWorker"	in	navigator	)	{



		navigator.serviceWorker.register(	"/serviceworker.min.js"	);



}
Path is important!
@AaronGustafson
The Service Worker Lifecycle
Browser
Install Activation Ready
aka.ms/pwa-lifecycle
@AaronGustafson
How connections are made
Browser
Internet
@AaronGustafson
Along comes Service Worker
Browser
Internet
Cache
@AaronGustafson
Along comes Service Worker
Browser
Internet
Cache
!
@AaronGustafson
Along comes Service Worker
Browser
Internet
Cache
@AaronGustafson
Know your (storage) limits
Temporary Persistent
Browser purges User purges
@AaronGustafson
Know your (storage) limits
Volume Size Domain Limit Overall Limit
≤ 8 GB
20%

of

overall
50 MB
8–32 GB 500 MB
32–128 GB 4% of volume
> 128 GB 4% or 20 GB
Except on iOS.

Safari gives you 50 MB.
Raising limits?

Unlimited storage?
Storage is a privilege,

don’t abuse it.
How?
@AaronGustafson
#1: No animated GIFs
@AaronGustafson
#2: Use responsive images
41
<img	src="medium.jpg"

					srcset="small.jpg	256w,

													medium.jpg	512w,

													large.jpg	1024w"

					sizes="(max-width:	30em)	30em,	100vw"

					alt="It’s	responsive!">
aka.ms/cloudinary-images
@AaronGustafson
#3: Lazy load images
42
<img	src="medium.jpg"

					srcset="small.jpg	256w,

													medium.jpg	512w,

													large.jpg	1024w"

					sizes="(max-width:	30em)	30em,	100vw"

					loading="lazy"

					alt="It’s	responsive	and	lazy	loads!">
aka.ms/img-lazy-loading
@AaronGustafson
#4: Provide alternate formats
43
<picture>

		<source	type="image/webp"	srcset="my.webp">

		<img	src="my.jpg"	alt="Alt	text	goes	here">

</picture>
@AaronGustafson
#4: Provide alternate formats
via Cloudinary URLs:
44
https://res.cloudinary.com/demo/image/upload/w_300,f_auto/my.jpg
aka.ms/cloudinary-webp
@AaronGustafson
#5: Have fallback images
45
self.addEventListener(	"install",	function(	event	){

		event.waitUntil(

				caches.open(	"static"	).then(	cache	=>	{

						return	cache.addAll(	["/i/fallbacks/offline.svg"]	);

				})

		);

});



function	respondWithOfflineImage()	{

		return	caches.match(	"/i/fallbacks/offline.svg"	);

}
aka.ms/ag-sw
@AaronGustafson
#5: Have fallback images
46
self.addEventListener(	"install",	function(	event	){

		event.waitUntil(

				caches.open(	"static"	).then(	cache	=>	{

						return	cache.addAll(	["/i/fallbacks/offline.svg"]	);

				})

		);

});



function	respondWithOfflineImage()	{

		return	caches.match(	"/i/fallbacks/offline.svg"	);

}
aka.ms/ag-sw
@AaronGustafson
#5: Have fallback images
47
self.addEventListener(	"install",	function(	event	){

		event.waitUntil(

				caches.open(	"static"	).then(	cache	=>	{

						return	cache.addAll(	["/i/fallbacks/offline.svg"]	);

				})

		);

});



function	respondWithOfflineImage()	{

		return	caches.match(	"/i/fallbacks/offline.svg"	);

}
aka.ms/ag-sw
@AaronGustafson
#5: Have fallback images
48
self.addEventListener(	"install",	function(	event	){

		event.waitUntil(

				caches.open(	"static"	).then(	cache	=>	{

						return	cache.addAll(	["/i/fallbacks/offline.svg"]	);

				})

		);

});



function	respondWithOfflineImage()	{

		return	caches.match(	"/i/fallbacks/offline.svg"	);

}
aka.ms/ag-sw
@AaronGustafson
#5: Have fallback images
49
self.addEventListener(	"install",	function(	event	){

		event.waitUntil(

				caches.open(	"static"	).then(	cache	=>	{

						return	cache.addAll(	["/i/fallbacks/offline.svg"]	);

				})

		);

});



function	respondWithOfflineImage()	{

		return	caches.match(	"/i/fallbacks/offline.svg"	);

}
aka.ms/ag-sw
@AaronGustafson
#5: Have fallback images
50
self.addEventListener(	"install",	function(	event	){

		event.waitUntil(

				caches.open(	"static"	).then(	cache	=>	{

						return	cache.addAll(	["/i/fallbacks/offline.svg"]	);

				})

		);

});



function	respondWithOfflineImage()	{

		return	caches.match(	"/i/fallbacks/offline.svg"	);

}
aka.ms/ag-sw
@AaronGustafson
#5: Have fallback images
51
self.addEventListener(	"fetch",	event	=>	{

		const	request	=	event.request,

								url	=	request.url;

		if	(	request.headers.get("Accept").includes("image")	)	{

				event.respondWith(

						return	fetch(	request,	fetch_config.images	)

															.then(	response	=>	{

																	return	response;

															})

															.catch(

																	respondWithOfflineImage

															);

				);

		}

});



aka.ms/ag-sw
@AaronGustafson
#5: Have fallback images
52
self.addEventListener(	"fetch",	event	=>	{

		const	request	=	event.request,

								url	=	request.url;

		if	(	request.headers.get("Accept").includes("image")	)	{

				event.respondWith(

						return	fetch(	request,	fetch_config.images	)

															.then(	response	=>	{

																	return	response;

															})

															.catch(

																	respondWithOfflineImage

															);

				);

		}

});



aka.ms/ag-sw
@AaronGustafson
#5: Have fallback images
53
self.addEventListener(	"fetch",	event	=>	{

		const	request	=	event.request,

								url	=	request.url;

		if	(	request.headers.get("Accept").includes("image")	)	{

				event.respondWith(

						return	fetch(	request,	fetch_config.images	)

															.then(	response	=>	{

																	return	response;

															})

															.catch(

																	respondWithOfflineImage

															);

				);

		}

});



aka.ms/ag-sw
@AaronGustafson
#5: Have fallback images
54
self.addEventListener(	"fetch",	event	=>	{

		const	request	=	event.request,

								url	=	request.url;

		if	(	request.headers.get("Accept").includes("image")	)	{

				event.respondWith(

						return	fetch(	request,	fetch_config.images	)

															.then(	response	=>	{

																	return	response;

															})

															.catch(

																	respondWithOfflineImage

															);

				);

		}

});



aka.ms/ag-sw
@AaronGustafson
#5: Have fallback images
55
self.addEventListener(	"fetch",	event	=>	{

		const	request	=	event.request,

								url	=	request.url;

		if	(	request.headers.get("Accept").includes("image")	)	{

				event.respondWith(

						return	fetch(	request,	fetch_config.images	)

															.then(	response	=>	{

																	return	response;

															})

															.catch(

																	respondWithOfflineImage

															);

				);

		}

});



aka.ms/ag-sw
@AaronGustafson
#5: Have fallback images
56
self.addEventListener(	"fetch",	event	=>	{

		const	request	=	event.request,

								url	=	request.url;

		if	(	request.headers.get("Accept").includes("image")	)	{

				event.respondWith(

						return	fetch(	request,	fetch_config.images	)

															.then(	response	=>	{

																	return	response;

															})

															.catch(

																	respondWithOfflineImage

															);

				);

		}

});



aka.ms/ag-sw
@AaronGustafson
Result!
@AaronGustafson
Result!
@AaronGustafson
#6: Respect Save Data
58
let	save_data	=	false;

if	(	'connection'	in	navigator	)	{

		save_data	=	navigator.connection.saveData;

}
aka.ms/ag-sw
@AaronGustafson
#6: Respect Save Data
59
self.addEventListener(	"fetch",	event	=>	{

		const	request	=	event.request,

								url	=	request.url;

		if	(	request.headers.get("Accept").includes("image")	)	{

				event.respondWith(

						if	(	save_data	)	{

								return	respondWithFallbackImage(	url	);

						}

						//	…

				);

}



aka.ms/ag-sw
@AaronGustafson
#6: Respect Save Data
60
const	fallback_avatar	=	"/i/fallbacks/avatar.svg",

						fallback_image	=	"/i/fallbacks/image.svg",

						avatars	=	/webmention.io/;



//	…



function	respondWithFallbackImage(	url	)	{

		const	image	=	avatars.test(	url	)	?	fallback_avatar

																																				:	fallback_image;

		return	caches.match(	image	);

}
aka.ms/ag-sw
@AaronGustafson
#6: Respect Save Data
61
const	fallback_avatar	=	"/i/fallbacks/avatar.svg",

						fallback_image	=	"/i/fallbacks/image.svg",

						avatars	=	/webmention.io/;



//	…



function	respondWithFallbackImage(	url	)	{

		const	image	=	avatars.test(	url	)	?	fallback_avatar

																																				:	fallback_image;

		return	caches.match(	image	);

}
aka.ms/ag-sw
@AaronGustafson
#6: Respect Save Data
62
const	fallback_avatar	=	"/i/fallbacks/avatar.svg",

						fallback_image	=	"/i/fallbacks/image.svg",

						avatars	=	/webmention.io/;



//	…



function	respondWithFallbackImage(	url	)	{

		const	image	=	avatars.test(	url	)	?	fallback_avatar

																																				:	fallback_image;

		return	caches.match(	image	);

}
aka.ms/ag-sw
@AaronGustafson
Result!
@AaronGustafson
#7: Prioritize certain images
64
const	high_priority	=	[

								/aaron-gustafson.com/,

								/adaptivewebdesign.info/

						];



function	isHighPriority(	url	)	{

		let	i	=	high_priority.length;

		while	(	i--	)	{

				if	(	high_priority[i].test(	url	)	)	{

						return	true;

				}

		}

		return	false;

}


aka.ms/ag-sw
@AaronGustafson
#7: Prioritize certain images
65
const	high_priority	=	[

								/aaron-gustafson.com/,

								/adaptivewebdesign.info/

						];



function	isHighPriority(	url	)	{

		let	i	=	high_priority.length;

		while	(	i--	)	{

				if	(	high_priority[i].test(	url	)	)	{

						return	true;

				}

		}

		return	false;

}


aka.ms/ag-sw
@AaronGustafson
#7: Prioritize certain images
66
const	high_priority	=	[

								/aaron-gustafson.com/,

								/adaptivewebdesign.info/

						];



function	isHighPriority(	url	)	{

		let	i	=	high_priority.length;

		while	(	i--	)	{

				if	(	high_priority[i].test(	url	)	)	{

						return	true;

				}

		}

		return	false;

}


aka.ms/ag-sw
@AaronGustafson
#8: Clean up after yourself
67
const	version = "v2:",

						sw_caches	=	{

								static:	{

										name:	`${version}static`

								},

								images:	{

										name:	`${version}images`,

										limit:	75

								},

								pages:	{

										name:	`${version}pages`,

										limit:	5

								},

								posts:	{

										name:	`${version}posts`,

										limit:	10

								},

								other:	{

										name:	`${version}other`,

										limit:	50

								}

						};
aka.ms/ag-sw
@AaronGustafson
#8: Clean up after yourself
68
function	trimCache(	cache_name,	limit	)	{

		caches.open(	cache_name	).then(	cache	=>	{

				cache.keys().then(	items	=>	{

						if	(	items.length	>	limit	)	{

								cache.delete(	items[0]	).then(

										trimCache(	cache_name,	limit	)

								);

						}

				});

		});

}
aka.ms/ag-sw
@AaronGustafson
#8: Clean up after yourself
69
function	trimCache(	cache_name,	limit	)	{

		caches.open(	cache_name	).then(	cache	=>	{

				cache.keys().then(	items	=>	{

						if	(	items.length	>	limit	)	{

								cache.delete(	items[0]	).then(

										trimCache(	cache_name,	limit	)

								);

						}

				});

		});

}
aka.ms/ag-sw
@AaronGustafson
#8: Clean up after yourself
70
function	trimCache(	cache_name,	limit	)	{

		caches.open(	cache_name	).then(	cache	=>	{

				cache.keys().then(	items	=>	{

						if	(	items.length	>	limit	)	{

								cache.delete(	items[0]	).then(

										trimCache(	cache_name,	limit	)

								);

						}

				});

		});

}
aka.ms/ag-sw
@AaronGustafson
#8: Clean up after yourself
71
if	(	"serviceWorker"	in	navigator	)	{

		navigator.serviceWorker.register(	"/serviceworker.min.js"	);

		

		if	(	navigator.serviceWorker.controller	)	{

				window.addEventListener(	"load",	function(){

						navigator.serviceWorker.controller.postMessage(	"clean	up"	);

				});

		}

}
aka.ms/ag-sw
@AaronGustafson
#8: Clean up after yourself
72
addEventListener("message",	messageEvent	=>	{

		if	(messageEvent.data	==	"clean	up")	{

				for	(	let	key	in	sw_caches	)	{

						if	(	sw_caches[key].limit	!=	undefined	)	{

								trimCache(	sw_caches[key].name,	sw_caches[key].limit	);

						}

				}

		}

});
aka.ms/ag-sw
@AaronGustafson
#8: Clean up after yourself
73
addEventListener("message",	messageEvent	=>	{

		if	(messageEvent.data	==	"clean	up")	{

				for	(	let	key	in	sw_caches	)	{

						if	(	sw_caches[key].limit	!=	undefined	)	{

								trimCache(	sw_caches[key].name,	sw_caches[key].limit	);

						}

				}

		}

});
aka.ms/ag-sw
@AaronGustafson
Make good choices
1. No animated GIFs (especially as backgrounds)
2. Use responsive images
3. Lazy load images
4. Provide alternate image formats
5. Provide fallback images via Service Worker
6. Pay attention to the Save Data header
7. Prioritize certain images
8. Clean up after yourself
74
© Marvel
Thank you!
@AaronGustafson
aaron-gustafson.com
slideshare.net/AaronGustafson

More Related Content

More from Aaron Gustafson

Delivering Critical Information and Services [JavaScript & Friends 2021]
Delivering Critical Information and Services [JavaScript & Friends 2021]Delivering Critical Information and Services [JavaScript & Friends 2021]
Delivering Critical Information and Services [JavaScript & Friends 2021]Aaron Gustafson
 
Adapting to Reality [Guest Lecture, March 2021]
Adapting to Reality [Guest Lecture, March 2021]Adapting to Reality [Guest Lecture, March 2021]
Adapting to Reality [Guest Lecture, March 2021]Aaron Gustafson
 
Designing the Conversation [Beyond Tellerrand 2019]
Designing the Conversation [Beyond Tellerrand 2019]Designing the Conversation [Beyond Tellerrand 2019]
Designing the Conversation [Beyond Tellerrand 2019]Aaron Gustafson
 
Progressive Web Apps: Where Do I Begin?
Progressive Web Apps: Where Do I Begin?Progressive Web Apps: Where Do I Begin?
Progressive Web Apps: Where Do I Begin?Aaron Gustafson
 
Adapting to Reality [Starbucks Lunch & Learn]
Adapting to Reality [Starbucks Lunch & Learn]Adapting to Reality [Starbucks Lunch & Learn]
Adapting to Reality [Starbucks Lunch & Learn]Aaron Gustafson
 
Conversational Semantics for the Web [CascadiaJS 2018]
Conversational Semantics for the Web [CascadiaJS 2018]Conversational Semantics for the Web [CascadiaJS 2018]
Conversational Semantics for the Web [CascadiaJS 2018]Aaron Gustafson
 
Better Performance === Greater Accessibility [Inclusive Design 24 2018]
Better Performance === Greater Accessibility [Inclusive Design 24 2018]Better Performance === Greater Accessibility [Inclusive Design 24 2018]
Better Performance === Greater Accessibility [Inclusive Design 24 2018]Aaron Gustafson
 
PWA: Where Do I Begin? [Microsoft Ignite 2018]
PWA: Where Do I Begin? [Microsoft Ignite 2018]PWA: Where Do I Begin? [Microsoft Ignite 2018]
PWA: Where Do I Begin? [Microsoft Ignite 2018]Aaron Gustafson
 
Designing the Conversation [Concatenate 2018]
Designing the Conversation [Concatenate 2018]Designing the Conversation [Concatenate 2018]
Designing the Conversation [Concatenate 2018]Aaron Gustafson
 
Designing the Conversation [Accessibility DC 2018]
Designing the Conversation [Accessibility DC 2018]Designing the Conversation [Accessibility DC 2018]
Designing the Conversation [Accessibility DC 2018]Aaron Gustafson
 
Performance as User Experience [AEADC 2018]
Performance as User Experience [AEADC 2018]Performance as User Experience [AEADC 2018]
Performance as User Experience [AEADC 2018]Aaron Gustafson
 
The Web Should Just Work for Everyone
The Web Should Just Work for EveryoneThe Web Should Just Work for Everyone
The Web Should Just Work for EveryoneAaron Gustafson
 
Performance as User Experience [AEA SEA 2018]
Performance as User Experience [AEA SEA 2018]Performance as User Experience [AEA SEA 2018]
Performance as User Experience [AEA SEA 2018]Aaron Gustafson
 
Performance as User Experience [An Event Apart Denver 2017]
Performance as User Experience [An Event Apart Denver 2017]Performance as User Experience [An Event Apart Denver 2017]
Performance as User Experience [An Event Apart Denver 2017]Aaron Gustafson
 
Advanced Design Methods 1, Day 2
Advanced Design Methods 1, Day 2Advanced Design Methods 1, Day 2
Advanced Design Methods 1, Day 2Aaron Gustafson
 
Advanced Design Methods 1, Day 1
Advanced Design Methods 1, Day 1Advanced Design Methods 1, Day 1
Advanced Design Methods 1, Day 1Aaron Gustafson
 
Designing the Conversation [Paris Web 2017]
Designing the Conversation [Paris Web 2017]Designing the Conversation [Paris Web 2017]
Designing the Conversation [Paris Web 2017]Aaron Gustafson
 
Exploring Adaptive Interfaces [Generate 2017]
Exploring Adaptive Interfaces [Generate 2017]Exploring Adaptive Interfaces [Generate 2017]
Exploring Adaptive Interfaces [Generate 2017]Aaron Gustafson
 
Progressive Web Apps and the Windows Ecosystem [Build 2017]
Progressive Web Apps and the Windows Ecosystem [Build 2017]Progressive Web Apps and the Windows Ecosystem [Build 2017]
Progressive Web Apps and the Windows Ecosystem [Build 2017]Aaron Gustafson
 
Writing for Engagement [TechReady 22]
Writing for Engagement [TechReady 22]Writing for Engagement [TechReady 22]
Writing for Engagement [TechReady 22]Aaron Gustafson
 

More from Aaron Gustafson (20)

Delivering Critical Information and Services [JavaScript & Friends 2021]
Delivering Critical Information and Services [JavaScript & Friends 2021]Delivering Critical Information and Services [JavaScript & Friends 2021]
Delivering Critical Information and Services [JavaScript & Friends 2021]
 
Adapting to Reality [Guest Lecture, March 2021]
Adapting to Reality [Guest Lecture, March 2021]Adapting to Reality [Guest Lecture, March 2021]
Adapting to Reality [Guest Lecture, March 2021]
 
Designing the Conversation [Beyond Tellerrand 2019]
Designing the Conversation [Beyond Tellerrand 2019]Designing the Conversation [Beyond Tellerrand 2019]
Designing the Conversation [Beyond Tellerrand 2019]
 
Progressive Web Apps: Where Do I Begin?
Progressive Web Apps: Where Do I Begin?Progressive Web Apps: Where Do I Begin?
Progressive Web Apps: Where Do I Begin?
 
Adapting to Reality [Starbucks Lunch & Learn]
Adapting to Reality [Starbucks Lunch & Learn]Adapting to Reality [Starbucks Lunch & Learn]
Adapting to Reality [Starbucks Lunch & Learn]
 
Conversational Semantics for the Web [CascadiaJS 2018]
Conversational Semantics for the Web [CascadiaJS 2018]Conversational Semantics for the Web [CascadiaJS 2018]
Conversational Semantics for the Web [CascadiaJS 2018]
 
Better Performance === Greater Accessibility [Inclusive Design 24 2018]
Better Performance === Greater Accessibility [Inclusive Design 24 2018]Better Performance === Greater Accessibility [Inclusive Design 24 2018]
Better Performance === Greater Accessibility [Inclusive Design 24 2018]
 
PWA: Where Do I Begin? [Microsoft Ignite 2018]
PWA: Where Do I Begin? [Microsoft Ignite 2018]PWA: Where Do I Begin? [Microsoft Ignite 2018]
PWA: Where Do I Begin? [Microsoft Ignite 2018]
 
Designing the Conversation [Concatenate 2018]
Designing the Conversation [Concatenate 2018]Designing the Conversation [Concatenate 2018]
Designing the Conversation [Concatenate 2018]
 
Designing the Conversation [Accessibility DC 2018]
Designing the Conversation [Accessibility DC 2018]Designing the Conversation [Accessibility DC 2018]
Designing the Conversation [Accessibility DC 2018]
 
Performance as User Experience [AEADC 2018]
Performance as User Experience [AEADC 2018]Performance as User Experience [AEADC 2018]
Performance as User Experience [AEADC 2018]
 
The Web Should Just Work for Everyone
The Web Should Just Work for EveryoneThe Web Should Just Work for Everyone
The Web Should Just Work for Everyone
 
Performance as User Experience [AEA SEA 2018]
Performance as User Experience [AEA SEA 2018]Performance as User Experience [AEA SEA 2018]
Performance as User Experience [AEA SEA 2018]
 
Performance as User Experience [An Event Apart Denver 2017]
Performance as User Experience [An Event Apart Denver 2017]Performance as User Experience [An Event Apart Denver 2017]
Performance as User Experience [An Event Apart Denver 2017]
 
Advanced Design Methods 1, Day 2
Advanced Design Methods 1, Day 2Advanced Design Methods 1, Day 2
Advanced Design Methods 1, Day 2
 
Advanced Design Methods 1, Day 1
Advanced Design Methods 1, Day 1Advanced Design Methods 1, Day 1
Advanced Design Methods 1, Day 1
 
Designing the Conversation [Paris Web 2017]
Designing the Conversation [Paris Web 2017]Designing the Conversation [Paris Web 2017]
Designing the Conversation [Paris Web 2017]
 
Exploring Adaptive Interfaces [Generate 2017]
Exploring Adaptive Interfaces [Generate 2017]Exploring Adaptive Interfaces [Generate 2017]
Exploring Adaptive Interfaces [Generate 2017]
 
Progressive Web Apps and the Windows Ecosystem [Build 2017]
Progressive Web Apps and the Windows Ecosystem [Build 2017]Progressive Web Apps and the Windows Ecosystem [Build 2017]
Progressive Web Apps and the Windows Ecosystem [Build 2017]
 
Writing for Engagement [TechReady 22]
Writing for Engagement [TechReady 22]Writing for Engagement [TechReady 22]
Writing for Engagement [TechReady 22]
 

Recently uploaded

Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoffsammart93
 
Stronger Together: Developing an Organizational Strategy for Accessible Desig...
Stronger Together: Developing an Organizational Strategy for Accessible Desig...Stronger Together: Developing an Organizational Strategy for Accessible Desig...
Stronger Together: Developing an Organizational Strategy for Accessible Desig...caitlingebhard1
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Victor Rentea
 
Navigating Identity and Access Management in the Modern Enterprise
Navigating Identity and Access Management in the Modern EnterpriseNavigating Identity and Access Management in the Modern Enterprise
Navigating Identity and Access Management in the Modern EnterpriseWSO2
 
Vector Search -An Introduction in Oracle Database 23ai.pptx
Vector Search -An Introduction in Oracle Database 23ai.pptxVector Search -An Introduction in Oracle Database 23ai.pptx
Vector Search -An Introduction in Oracle Database 23ai.pptxRemote DBA Services
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Jeffrey Haguewood
 
WSO2's API Vision: Unifying Control, Empowering Developers
WSO2's API Vision: Unifying Control, Empowering DevelopersWSO2's API Vision: Unifying Control, Empowering Developers
WSO2's API Vision: Unifying Control, Empowering DevelopersWSO2
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...Zilliz
 
Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)Zilliz
 
Less Is More: Utilizing Ballerina to Architect a Cloud Data Platform
Less Is More: Utilizing Ballerina to Architect a Cloud Data PlatformLess Is More: Utilizing Ballerina to Architect a Cloud Data Platform
Less Is More: Utilizing Ballerina to Architect a Cloud Data PlatformWSO2
 
CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistandanishmna97
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FMESafe Software
 
Choreo: Empowering the Future of Enterprise Software Engineering
Choreo: Empowering the Future of Enterprise Software EngineeringChoreo: Empowering the Future of Enterprise Software Engineering
Choreo: Empowering the Future of Enterprise Software EngineeringWSO2
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodJuan lago vázquez
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businesspanagenda
 
Exploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusExploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusZilliz
 
Quantum Leap in Next-Generation Computing
Quantum Leap in Next-Generation ComputingQuantum Leap in Next-Generation Computing
Quantum Leap in Next-Generation ComputingWSO2
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native ApplicationsWSO2
 

Recently uploaded (20)

+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
Stronger Together: Developing an Organizational Strategy for Accessible Desig...
Stronger Together: Developing an Organizational Strategy for Accessible Desig...Stronger Together: Developing an Organizational Strategy for Accessible Desig...
Stronger Together: Developing an Organizational Strategy for Accessible Desig...
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
 
Navigating Identity and Access Management in the Modern Enterprise
Navigating Identity and Access Management in the Modern EnterpriseNavigating Identity and Access Management in the Modern Enterprise
Navigating Identity and Access Management in the Modern Enterprise
 
Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..
 
Vector Search -An Introduction in Oracle Database 23ai.pptx
Vector Search -An Introduction in Oracle Database 23ai.pptxVector Search -An Introduction in Oracle Database 23ai.pptx
Vector Search -An Introduction in Oracle Database 23ai.pptx
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
 
WSO2's API Vision: Unifying Control, Empowering Developers
WSO2's API Vision: Unifying Control, Empowering DevelopersWSO2's API Vision: Unifying Control, Empowering Developers
WSO2's API Vision: Unifying Control, Empowering Developers
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
 
Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)
 
Less Is More: Utilizing Ballerina to Architect a Cloud Data Platform
Less Is More: Utilizing Ballerina to Architect a Cloud Data PlatformLess Is More: Utilizing Ballerina to Architect a Cloud Data Platform
Less Is More: Utilizing Ballerina to Architect a Cloud Data Platform
 
CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistan
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Choreo: Empowering the Future of Enterprise Software Engineering
Choreo: Empowering the Future of Enterprise Software EngineeringChoreo: Empowering the Future of Enterprise Software Engineering
Choreo: Empowering the Future of Enterprise Software Engineering
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
 
Exploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusExploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with Milvus
 
Quantum Leap in Next-Generation Computing
Quantum Leap in Next-Generation ComputingQuantum Leap in Next-Generation Computing
Quantum Leap in Next-Generation Computing
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 

Media in the Age of PWAs [ImageCon 2019]

  • 1. Media in the Age of PWAs Aaron Gustafson @AaronGustafson slideshare.net/AaronGustafson
  • 3. Progressive Web App? What exactly is a

  • 4. What exactly is a Progressive Web App?
  • 5. What exactly is a Progressive Web App?
  • 6. What exactly is a Progressive Web App?
  • 7. “Progressive Web App”
 is a marketing term Progressive Web App

  • 13. @AaronGustafson A Minimum Viable PWA HTTPS Web App
 Manifest
  • 15. @AaronGustafson A Minimum Viable PWA HTTPS Web App
 Manifest Service Worker
  • 18. Carnival:
 24% opt-in rate and 42% open rate for push notifications Katarzyna Ostrowska aka.ms/carnival-pwa
  • 19. Starbucks:
 2x increase in daily active users aka.ms/google-io-2018
  • 20. Tinder:
 Core experience
 with 90% less code aka.ms/tinder-pwa-2017
  • 21. Trivago:
 97% increase in
 click-outs to 
 hotel offers aka.ms/trivago-pwa-2017
  • 22. West Elm:
 15% increase in
 time on site
 9% increase in revenue per visit aka.ms/west-elm-pwa-2017
  • 23.
  • 24. @AaronGustafson A Minimum Viable PWA HTTPS Web App
 Manifest Service Worker
  • 26. @AaronGustafson Registering a Service Worker if ( "serviceWorker" in navigator ) {
 
 navigator.serviceWorker.register( "/serviceworker.min.js" );
 
 }
  • 27. @AaronGustafson Registering a Service Worker if ( "serviceWorker" in navigator ) {
 
 navigator.serviceWorker.register( "/serviceworker.min.js" );
 
 }
  • 28. @AaronGustafson Registering a Service Worker if ( "serviceWorker" in navigator ) {
 
 navigator.serviceWorker.register( "/serviceworker.min.js" );
 
 } Path is important!
  • 29. @AaronGustafson The Service Worker Lifecycle Browser Install Activation Ready aka.ms/pwa-lifecycle
  • 30. @AaronGustafson How connections are made Browser Internet
  • 31. @AaronGustafson Along comes Service Worker Browser Internet Cache
  • 32. @AaronGustafson Along comes Service Worker Browser Internet Cache !
  • 33. @AaronGustafson Along comes Service Worker Browser Internet Cache
  • 34. @AaronGustafson Know your (storage) limits Temporary Persistent Browser purges User purges
  • 35. @AaronGustafson Know your (storage) limits Volume Size Domain Limit Overall Limit ≤ 8 GB 20%
 of
 overall 50 MB 8–32 GB 500 MB 32–128 GB 4% of volume > 128 GB 4% or 20 GB
  • 36. Except on iOS.
 Safari gives you 50 MB.
  • 38. Storage is a privilege,
 don’t abuse it.
  • 39. How?
  • 41. @AaronGustafson #2: Use responsive images 41 <img src="medium.jpg"
 srcset="small.jpg 256w,
 medium.jpg 512w,
 large.jpg 1024w"
 sizes="(max-width: 30em) 30em, 100vw"
 alt="It’s responsive!"> aka.ms/cloudinary-images
  • 42. @AaronGustafson #3: Lazy load images 42 <img src="medium.jpg"
 srcset="small.jpg 256w,
 medium.jpg 512w,
 large.jpg 1024w"
 sizes="(max-width: 30em) 30em, 100vw"
 loading="lazy"
 alt="It’s responsive and lazy loads!"> aka.ms/img-lazy-loading
  • 43. @AaronGustafson #4: Provide alternate formats 43 <picture>
 <source type="image/webp" srcset="my.webp">
 <img src="my.jpg" alt="Alt text goes here">
 </picture>
  • 44. @AaronGustafson #4: Provide alternate formats via Cloudinary URLs: 44 https://res.cloudinary.com/demo/image/upload/w_300,f_auto/my.jpg aka.ms/cloudinary-webp
  • 45. @AaronGustafson #5: Have fallback images 45 self.addEventListener( "install", function( event ){
 event.waitUntil(
 caches.open( "static" ).then( cache => {
 return cache.addAll( ["/i/fallbacks/offline.svg"] );
 })
 );
 });
 
 function respondWithOfflineImage() {
 return caches.match( "/i/fallbacks/offline.svg" );
 } aka.ms/ag-sw
  • 46. @AaronGustafson #5: Have fallback images 46 self.addEventListener( "install", function( event ){
 event.waitUntil(
 caches.open( "static" ).then( cache => {
 return cache.addAll( ["/i/fallbacks/offline.svg"] );
 })
 );
 });
 
 function respondWithOfflineImage() {
 return caches.match( "/i/fallbacks/offline.svg" );
 } aka.ms/ag-sw
  • 47. @AaronGustafson #5: Have fallback images 47 self.addEventListener( "install", function( event ){
 event.waitUntil(
 caches.open( "static" ).then( cache => {
 return cache.addAll( ["/i/fallbacks/offline.svg"] );
 })
 );
 });
 
 function respondWithOfflineImage() {
 return caches.match( "/i/fallbacks/offline.svg" );
 } aka.ms/ag-sw
  • 48. @AaronGustafson #5: Have fallback images 48 self.addEventListener( "install", function( event ){
 event.waitUntil(
 caches.open( "static" ).then( cache => {
 return cache.addAll( ["/i/fallbacks/offline.svg"] );
 })
 );
 });
 
 function respondWithOfflineImage() {
 return caches.match( "/i/fallbacks/offline.svg" );
 } aka.ms/ag-sw
  • 49. @AaronGustafson #5: Have fallback images 49 self.addEventListener( "install", function( event ){
 event.waitUntil(
 caches.open( "static" ).then( cache => {
 return cache.addAll( ["/i/fallbacks/offline.svg"] );
 })
 );
 });
 
 function respondWithOfflineImage() {
 return caches.match( "/i/fallbacks/offline.svg" );
 } aka.ms/ag-sw
  • 50. @AaronGustafson #5: Have fallback images 50 self.addEventListener( "install", function( event ){
 event.waitUntil(
 caches.open( "static" ).then( cache => {
 return cache.addAll( ["/i/fallbacks/offline.svg"] );
 })
 );
 });
 
 function respondWithOfflineImage() {
 return caches.match( "/i/fallbacks/offline.svg" );
 } aka.ms/ag-sw
  • 51. @AaronGustafson #5: Have fallback images 51 self.addEventListener( "fetch", event => {
 const request = event.request,
 url = request.url;
 if ( request.headers.get("Accept").includes("image") ) {
 event.respondWith(
 return fetch( request, fetch_config.images )
 .then( response => {
 return response;
 })
 .catch(
 respondWithOfflineImage
 );
 );
 }
 });
 
 aka.ms/ag-sw
  • 52. @AaronGustafson #5: Have fallback images 52 self.addEventListener( "fetch", event => {
 const request = event.request,
 url = request.url;
 if ( request.headers.get("Accept").includes("image") ) {
 event.respondWith(
 return fetch( request, fetch_config.images )
 .then( response => {
 return response;
 })
 .catch(
 respondWithOfflineImage
 );
 );
 }
 });
 
 aka.ms/ag-sw
  • 53. @AaronGustafson #5: Have fallback images 53 self.addEventListener( "fetch", event => {
 const request = event.request,
 url = request.url;
 if ( request.headers.get("Accept").includes("image") ) {
 event.respondWith(
 return fetch( request, fetch_config.images )
 .then( response => {
 return response;
 })
 .catch(
 respondWithOfflineImage
 );
 );
 }
 });
 
 aka.ms/ag-sw
  • 54. @AaronGustafson #5: Have fallback images 54 self.addEventListener( "fetch", event => {
 const request = event.request,
 url = request.url;
 if ( request.headers.get("Accept").includes("image") ) {
 event.respondWith(
 return fetch( request, fetch_config.images )
 .then( response => {
 return response;
 })
 .catch(
 respondWithOfflineImage
 );
 );
 }
 });
 
 aka.ms/ag-sw
  • 55. @AaronGustafson #5: Have fallback images 55 self.addEventListener( "fetch", event => {
 const request = event.request,
 url = request.url;
 if ( request.headers.get("Accept").includes("image") ) {
 event.respondWith(
 return fetch( request, fetch_config.images )
 .then( response => {
 return response;
 })
 .catch(
 respondWithOfflineImage
 );
 );
 }
 });
 
 aka.ms/ag-sw
  • 56. @AaronGustafson #5: Have fallback images 56 self.addEventListener( "fetch", event => {
 const request = event.request,
 url = request.url;
 if ( request.headers.get("Accept").includes("image") ) {
 event.respondWith(
 return fetch( request, fetch_config.images )
 .then( response => {
 return response;
 })
 .catch(
 respondWithOfflineImage
 );
 );
 }
 });
 
 aka.ms/ag-sw
  • 59. @AaronGustafson #6: Respect Save Data 58 let save_data = false;
 if ( 'connection' in navigator ) {
 save_data = navigator.connection.saveData;
 } aka.ms/ag-sw
  • 60. @AaronGustafson #6: Respect Save Data 59 self.addEventListener( "fetch", event => {
 const request = event.request,
 url = request.url;
 if ( request.headers.get("Accept").includes("image") ) {
 event.respondWith(
 if ( save_data ) {
 return respondWithFallbackImage( url );
 }
 // …
 );
 }
 
 aka.ms/ag-sw
  • 61. @AaronGustafson #6: Respect Save Data 60 const fallback_avatar = "/i/fallbacks/avatar.svg",
 fallback_image = "/i/fallbacks/image.svg",
 avatars = /webmention.io/;
 
 // …
 
 function respondWithFallbackImage( url ) {
 const image = avatars.test( url ) ? fallback_avatar
 : fallback_image;
 return caches.match( image );
 } aka.ms/ag-sw
  • 62. @AaronGustafson #6: Respect Save Data 61 const fallback_avatar = "/i/fallbacks/avatar.svg",
 fallback_image = "/i/fallbacks/image.svg",
 avatars = /webmention.io/;
 
 // …
 
 function respondWithFallbackImage( url ) {
 const image = avatars.test( url ) ? fallback_avatar
 : fallback_image;
 return caches.match( image );
 } aka.ms/ag-sw
  • 63. @AaronGustafson #6: Respect Save Data 62 const fallback_avatar = "/i/fallbacks/avatar.svg",
 fallback_image = "/i/fallbacks/image.svg",
 avatars = /webmention.io/;
 
 // …
 
 function respondWithFallbackImage( url ) {
 const image = avatars.test( url ) ? fallback_avatar
 : fallback_image;
 return caches.match( image );
 } aka.ms/ag-sw
  • 65. @AaronGustafson #7: Prioritize certain images 64 const high_priority = [
 /aaron-gustafson.com/,
 /adaptivewebdesign.info/
 ];
 
 function isHighPriority( url ) {
 let i = high_priority.length;
 while ( i-- ) {
 if ( high_priority[i].test( url ) ) {
 return true;
 }
 }
 return false;
 } 
 aka.ms/ag-sw
  • 66. @AaronGustafson #7: Prioritize certain images 65 const high_priority = [
 /aaron-gustafson.com/,
 /adaptivewebdesign.info/
 ];
 
 function isHighPriority( url ) {
 let i = high_priority.length;
 while ( i-- ) {
 if ( high_priority[i].test( url ) ) {
 return true;
 }
 }
 return false;
 } 
 aka.ms/ag-sw
  • 67. @AaronGustafson #7: Prioritize certain images 66 const high_priority = [
 /aaron-gustafson.com/,
 /adaptivewebdesign.info/
 ];
 
 function isHighPriority( url ) {
 let i = high_priority.length;
 while ( i-- ) {
 if ( high_priority[i].test( url ) ) {
 return true;
 }
 }
 return false;
 } 
 aka.ms/ag-sw
  • 68. @AaronGustafson #8: Clean up after yourself 67 const version = "v2:",
 sw_caches = {
 static: {
 name: `${version}static`
 },
 images: {
 name: `${version}images`,
 limit: 75
 },
 pages: {
 name: `${version}pages`,
 limit: 5
 },
 posts: {
 name: `${version}posts`,
 limit: 10
 },
 other: {
 name: `${version}other`,
 limit: 50
 }
 }; aka.ms/ag-sw
  • 69. @AaronGustafson #8: Clean up after yourself 68 function trimCache( cache_name, limit ) {
 caches.open( cache_name ).then( cache => {
 cache.keys().then( items => {
 if ( items.length > limit ) {
 cache.delete( items[0] ).then(
 trimCache( cache_name, limit )
 );
 }
 });
 });
 } aka.ms/ag-sw
  • 70. @AaronGustafson #8: Clean up after yourself 69 function trimCache( cache_name, limit ) {
 caches.open( cache_name ).then( cache => {
 cache.keys().then( items => {
 if ( items.length > limit ) {
 cache.delete( items[0] ).then(
 trimCache( cache_name, limit )
 );
 }
 });
 });
 } aka.ms/ag-sw
  • 71. @AaronGustafson #8: Clean up after yourself 70 function trimCache( cache_name, limit ) {
 caches.open( cache_name ).then( cache => {
 cache.keys().then( items => {
 if ( items.length > limit ) {
 cache.delete( items[0] ).then(
 trimCache( cache_name, limit )
 );
 }
 });
 });
 } aka.ms/ag-sw
  • 72. @AaronGustafson #8: Clean up after yourself 71 if ( "serviceWorker" in navigator ) {
 navigator.serviceWorker.register( "/serviceworker.min.js" );
 
 if ( navigator.serviceWorker.controller ) {
 window.addEventListener( "load", function(){
 navigator.serviceWorker.controller.postMessage( "clean up" );
 });
 }
 } aka.ms/ag-sw
  • 73. @AaronGustafson #8: Clean up after yourself 72 addEventListener("message", messageEvent => {
 if (messageEvent.data == "clean up") {
 for ( let key in sw_caches ) {
 if ( sw_caches[key].limit != undefined ) {
 trimCache( sw_caches[key].name, sw_caches[key].limit );
 }
 }
 }
 }); aka.ms/ag-sw
  • 74. @AaronGustafson #8: Clean up after yourself 73 addEventListener("message", messageEvent => {
 if (messageEvent.data == "clean up") {
 for ( let key in sw_caches ) {
 if ( sw_caches[key].limit != undefined ) {
 trimCache( sw_caches[key].name, sw_caches[key].limit );
 }
 }
 }
 }); aka.ms/ag-sw
  • 75. @AaronGustafson Make good choices 1. No animated GIFs (especially as backgrounds) 2. Use responsive images 3. Lazy load images 4. Provide alternate image formats 5. Provide fallback images via Service Worker 6. Pay attention to the Save Data header 7. Prioritize certain images 8. Clean up after yourself 74