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

2024 April Patch Tuesday
2024 April Patch Tuesday2024 April Patch Tuesday
2024 April Patch TuesdayIvanti
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxLoriGlavin3
 
Generative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfGenerative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfIngrid Airi González
 
Data governance with Unity Catalog Presentation
Data governance with Unity Catalog PresentationData governance with Unity Catalog Presentation
Data governance with Unity Catalog PresentationKnoldus Inc.
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsSergiu Bodiu
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.Curtis Poe
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxLoriGlavin3
 
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24Mark Goldstein
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc
 
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfSo einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfpanagenda
 
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxDigital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxLoriGlavin3
 
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Alkin Tezuysal
 
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...Scott Andery
 
Sample pptx for embedding into website for demo
Sample pptx for embedding into website for demoSample pptx for embedding into website for demo
Sample pptx for embedding into website for demoHarshalMandlekar2
 
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality AssuranceInflectra
 
Scale your database traffic with Read & Write split using MySQL Router
Scale your database traffic with Read & Write split using MySQL RouterScale your database traffic with Read & Write split using MySQL Router
Scale your database traffic with Read & Write split using MySQL RouterMydbops
 
Generative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersGenerative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersRaghuram Pandurangan
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteDianaGray10
 
Modern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better StrongerModern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better Strongerpanagenda
 
Emixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native developmentEmixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native developmentPim van der Noll
 

Recently uploaded (20)

2024 April Patch Tuesday
2024 April Patch Tuesday2024 April Patch Tuesday
2024 April Patch Tuesday
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
 
Generative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfGenerative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdf
 
Data governance with Unity Catalog Presentation
Data governance with Unity Catalog PresentationData governance with Unity Catalog Presentation
Data governance with Unity Catalog Presentation
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platforms
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
 
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
 
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfSo einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
 
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxDigital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
 
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
 
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
 
Sample pptx for embedding into website for demo
Sample pptx for embedding into website for demoSample pptx for embedding into website for demo
Sample pptx for embedding into website for demo
 
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
 
Scale your database traffic with Read & Write split using MySQL Router
Scale your database traffic with Read & Write split using MySQL RouterScale your database traffic with Read & Write split using MySQL Router
Scale your database traffic with Read & Write split using MySQL Router
 
Generative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersGenerative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information Developers
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test Suite
 
Modern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better StrongerModern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
 
Emixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native developmentEmixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native development
 

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