Your users deserve a fast and responsive web app and PWAs help you step that up a notch through notifications, offline support and more.
There’s a lot that goes into that from understanding how the DOM tree works and how that plays with CSS and JavaScript to how to leverage the ServiceWorker for cashing and push notifications.
In this session, we’ll build a PWA that show cases many of the things you need to keep in mind when building a great and fast progressive web app.
3. @joshholmes
Microsoft Edge and Chromium Open
Source: Our Intent
1. We will adopt Chromium as the web platform for Microsoft Edge
desktop.
2. We will evolve the Microsoft Edge app architecture, enabling
distribution to all supported versions of Windows including
Windows 7 and Windows 8, as well as Windows 10. We will also
bring Microsoft Edge to other desktop platforms, such as macOS.
3. We will offer our Windows platform expertise to improve the
experience of all Chromium-based browsers on Windows.
https://github.com/MicrosoftEdge/MSEdge
23. @joshholmes
//Install stage sets up the offline page in the cache and opens a new cache
self.addEventListener('install', function(event) {
var offlinePage = new Request('offline.html');
event.waitUntil(
fetch(offlinePage).then(function(response) {
return caches.open('pwabuilder-offline').then(function(cache) {
console.log('[PWA Builder] Cached offline page during Install'+ response.url);
return cache.put(offlinePage, response);
});
}));
});
//If any fetch fails, it will show the offline page.
self.addEventListener('fetch', function(event) {
event.respondWith(
fetch(event.request).catch(function(error) {
console.error( '[PWA Builder] Network request Failed. Serving offline page ' + error );
return caches.open('pwabuilder-offline').then(function(cache) {
return cache.match('offline.html');
});
}
));
});
//This is a event that can be fired from your page to tell the SW to update the offline page
self.addEventListener('refreshOffline', function(response) {
return caches.open('pwabuilder-offline').then(function(cache) {
console.log('[PWA Builder] Offline page updated from refreshOffline event: '+ response.url);
return cache.put(offlinePage, response);
});
});
Service Worker
24. @joshholmes
//Add this below content to your HTML page, or add the js file to your page at the very top to register service
worker
if (navigator.serviceWorker.controller) {
console.log('[PWA Builder] active service worker found, no need to register')
} else {
//Register the ServiceWorker
navigator.serviceWorker.register('pwabuider-sw.js', {
scope: './'
}).then(function(reg) {
console.log('Service worker has been registered for scope:'+ reg.scope);
});
}
Register the SW
74. @joshholmes
Steps for better performance
1. Use native features whenever possible
2. Only include assets you actually need
3. Optimize everything
4. Think about when you load assets
5. Consider how you load assets
6. Only load assets when they add value
75
95. @joshholmes
We used some hints though
<link rel="preconnect"
href="//10kapart.blob.core.windows.net">
<link rel="preconnect"
href="//cdnjs.cloudflare.com">
<link rel="preconnect"
href="//www.google-analytics.com">
105. @joshholmes
We had 10 JS files
๏ Global
‣ main.js - the site’s library
‣ serviceworker.js - The site’s service worker
๏ Browser-specific
‣ html5shiv.js - local copy of the HTML5 Shiv for < IE9
111
106. @joshholmes
We had 10 JS files
๏ Page-specific
‣ enter.js - Entry form-related code
‣ form-saver.js - Used to save form entries locally until submitted
‣ hero.js - Runs the SVG animation on the homepage
‣ home.js - Handles homepage-specific tasks
‣ project.js - Used on project pages during voting
‣ update.js - Handles the winner update form
112
133. @joshholmes
How do we get there?
JS?
No
No imageLoad
Yes
<>
SVG support?
Yes
SVG
Ajax request SVG
Yank out script & add to document
No
picture
Save the markup for
next page load
NoYes
Verify browser
width condition
137. @joshholmes
Evaluate images case-by-case
๏ Does the image reiterate information found in the surrounding text?
๏ Is the image necessary to understand the surrounding content?
๏ Does the image contain text?
๏ Is the image a graph, chart, or table?
๏ Could the content of the image be presented in a different format
that would not require an image?
๏ Is the image purely presentational?
143
143. @joshholmes
Quick format recap
๏ GIF
‣ for images with large swaths of solid colors
‣ Binary transparency
๏ JPG
‣ For photographs and images with gradations of color
‣ Can be compressed (introduces artifacts)
149
144. @joshholmes
Quick format recap
๏ PNG (8-Bit)
‣ Alternative to GIF
‣ Can support alpha transparency (with the right creation software)
๏ PNG (24-bit)
‣ Alternative to JPG
‣ Usually larger than JPGs
‣ Supports alpha tranparency
150
145. @joshholmes
Quick format recap
๏ WebP
‣ Newer format, not universally supported
‣ Smaller than JPGs and 24-bit PNGs
‣ Support alpha transparency
‣ and so much more…
151
158. @joshholmes
Steps for better performance
1. Use native features whenever possible
2. Only include assets you actually need
3. Optimize everything
4. Think about when you load assets
5. Consider how you load assets
6. Only load assets when they add value
179
Hi there, my name is Aaron Gustafson
WaSP
Microsoft - Web Standards, PWAs & a11y
You’ll hear me use the terms Progressive Web App and PWA interchangeably throughout this talk
Let’s ignore the first part of this term for a moment, but I promise I will circle back to it shortly.
The term “web app” may sound like something you can put your finger on, right?
It’s software, on the Web, you use to complete a task.
Like the expense manager, but it can be any web site or property, really.
And so it is with progressive web apps too.
“Web apps” in this context can be any website…
newspapers, games, books, shopping sites…
it really doesn’t matter what the content or purpose of the website is, the “web app” moniker is applicable to all of them.
The term could just have easily been progressive web site and it may be helpful to think of it as such
It doesn’t need to be a single page app
You don’t need to be running everything client side
There are no particular requirements for the type of PWA you are developing
Essentially a PWA is a website that is capable of being promoted to being native-ish.
It gets many of the benefits of being a native app (some of which I will cover in this talk),
but is also has all of the benefits of being a website too
Google defined 10 characteristics they believe define this new breed of web application
Progressive: Works for every user, regardless of browser choice because it's built with progressive enhancement as a core tenet
We’ll circle back to discuss progressive enhancement a bit more in a moment
Responsive: Fits any form factor
Network independent: Works offline & on low-quality networks (Service Worker)
App-like: Feels like an app in terms of responsiveness and UX
Fresh: Always up to date (Service Worker)
Safe: Served via HTTPS to prevent snooping and to ensure content hasn't been tampered with
Discoverable: Search spiders can identify these websites as apps through their use of the Web Application Manifest and Service Worker
Re-engageable: Makes re-engagement easy through features like push notifications
Installable: Allows users to install apps they find useful, independent of (but not exclusive of) an app store.
Linkable: Easily shared via a URL. Can also respond to URLs (deep linking).
In terms of designing user experiences, the problem we are often charged with solving is how to help users accomplish a task. In most cases, we want to do as much as we can to remove any obstacles that can get in their way. You know, friction.
This is probably the 48th time you’ve heard that word at this conference and there’s a reason for that: Our job is to reduce friction in order to help people do what they need to do with as little pain or annoyance as possible.
We need to be concerned about the performance of our web pages because poor performance is friction.
I’m sure you’ve seen countless studies that have shown time & time again that people want speedy access to websites.
It matters.
The Harris Poll found that speed was the second most important attribute of a website, ranked right behind easy navigation, and just ahead of reliability. In other words, they’d rather it be fast than work all the time :-)
I’m sure you’ve seen countless studies that have shown time & time again that people want speedy access to websites.
It matters.
The Harris Poll found that speed was the second most important attribute of a website, ranked right behind easy navigation, and just ahead of reliability. In other words, they’d rather it be fast than work all the time :-)
Going on Akamai and Gomez data, Kissmetrics determined that…
Translated…
Of course this is somewhat hypothetical. Let’s look at practical.
Amazon knows how much poor performance costs.
Mobiquity found that slow mobile load times was the greatest source of frustration for mobile shoppers.
Google has found that…
Switching gears from negative to positive…
It should be obvious by now that performance really matters.
In building the site, I decided to challenge the team to make the contest site abide by the same rules. After all, if we were going to ask it of others, we didn’t want to be hypocrites.
In a way, we decided to eat our own dogfood.
Before I get into the ins and outs of some of the performance optimizations we made to the site, I want to give you a quick overview of how pages load. Just so we are all on the same page.
This isn’t going to be exhaustive, but it will give you a sense of what we are dealing with when we deliver things on the web.
First step when you enter an address in the URL bar
This may be cached locally or somewhere in your local or provider’s network
Then the browser & server greet each other
Then the browser requests a file
Server processing can be as simple as finding a static file or retrieving a ton of data from a database and crunching a bunch of numbers. This piece is widely variable, but can be tuned as well.
That discussion falls outside of the boundaries of this talk though.
Once we have the response, the browser can get to work doing something with it.
This is a really high-level overview of how parsing occurs. Generally this is how it goes, but there are some optimizations individual browsers make that are unique to them.
This is a really contrived example, but I wanted to use it to inform you of how all of this comes together so you have a better sense of how markup and source order affects page load performance.
As the DOM parser moves through the HTML document…
Additionally, each request may also require building up a full DNS & TCP handshake request
With all of this in mind, here are my recommended first steps for performance tuning your projects…
For more on the autocomplete property, Jason Grigsby wrote a very exhaustive walkthrough of what it is and how to use it on the Cloud Four blog.
Here’s another example with an email field
Modern approaches to layout are far more code-efficient.
Modern JavaScript can also allow you to work without libraries.
Make use of system fonts. You can even create elaborate font stacks offering a bunch of options that should produce a desirable match.
This was our setup in the contest site.
And the fallbacks were relatively close or at least close enough to not present weird layout issues when our top picks weren’t available.
Subsetting means removing unused characters from the font file.
There are numerous tools for this: command line, Font Squirrel and it’s supported by services like Typekit, Google Fonts
But be cautious in how you do this as you might discover you’ve removed a character you actually needed.
There are a lot of great tools out there… many may be more than you need.
If you use Foundation all-up, that’s about 266 kB or a relatively large image worth of code and you’re only likely to be using a portion of it.
Some of these libraries and frameworks do enable you to customize a “build” to your needs, only including what you are actually using.
A colleague of mine was hired to consult on the performance of a high profile retail website. When he tucked into the code, he saw they were using jQuery, Prototype, Scriptaculous, MooTools, and a handful of other libraries. In total, they were loading 3.5 MB of JavaScript. All because their developers didn’t standardize on a single library and just added them willy nilly, as if they were free.
Some folks rely on a Content Delivery Network (or CDN) to deliver the libraries they use. That can be great (as long as there’s not a network issue).
But it’s important to remember, as I mentioned before, that these can incur some additional overhead in the network connection realm. And that can cost precious time, especially on high-latency mobile networks.
For each CDN domain, we might have to go through the whole connection process to request a file. The more CDN shards, the more connection overhead you have.
Thankfully we can optimize some of these steps a little bit
Resource hints
Browsers use the “as” attribute to help them slot the request into the right part of the loading process.
Of course download isn’t all we need to worry about…
A few years ago Filament Group ran a test, building the same “app” using several frameworks commonly employed at the time and tested how long it took them to become usable.
Ember 1.9 was the worst offender, taking nearly 5 seconds to become usable on a mobile device on a 3G network. The reason is a combination of poor network performance with a slower mobile chipset.
And even once you have the library downloaded, they usually pale in comparison to JavaScript code when it comes to execution speed.
Just as a quick example, an ID lookup in jQuery runs about 98% slower than the native DOM method.
Every layer of abstraction reduce performance.
And so, we opted to skip the libraries and frameworks and write pretty much everything from scratch.
We did use some hints though.
And we did borrow the SVG support test from Modernizr.
(Credited in the source, of course)
We used Gulp to help out with a lot of the optimization work.
Gulp is great, but it’s not the only game in town for this sort of thing. There are other task runners like Grunt or even Make, and bundling systems like Webpack or Parcel. I’m not here to tell you what to use, you should figure out what’s right for you & your team.
It’s important to point out that we pre-compressed our static assets because that meant there was one less thing for the server to have to do.
You can create a map to load files in a particular way, but you can also use filenames in many systems (because they will bundle them alphabetically).
When you need certain bits of code to be placed early or late in a file. _ & omega can enable that.
Even though we were using a Node backend, we pre-compressed our HTML files so we had one less task for the server to do dynamically (further reducing it’s processing time).
It’s worth noting we also used Varnish to cache dynamic pages generated out of the Node backend.
2 global
1 browser specific (which I’ll get to)
7 page or feature-specific
As you’d expect…
And since JS was not necessary, but rather enhanced the page functionality, we were able to defer loading page-specific scripts.
Defer is one option, another is async, which means…
It’s tempting to defer or asynchronously load every bit of JS, but that can be problematic.
What if one file loads before the other. With async we don’t control when things load.
We only set async and defer on files other than our main.js file.
Parallel connections per host and overall are limited
Under HTTP/1.1, we also had to deal with head of line blocking where resource requests need to be resolved before a new one can be made.
HTTP/2 is quite an advancement and, among its features, offers…
Request as much as you want - 1 connection
To throw that into the waterfall chart you might be used to seeing in devtools…
Demo Akamai put together that shows HTTP1 vs HTTP2
379 individual tiles loading in
Recommended reading…
the default CSS file is tiny
In most cases, the older browsers we’re talking about are mobile anyway. Or IE8 and below. And I’m ok giving them a mobile layout to be honest.
Conditional comments, while no longer supported in modern versions of IE and Edge can still be useful.
This one delivers the HTML5 shim to IE8 and below so our HTML5 elements render properly.
You can also use them to hide specific content from older browsers. We used them to hide scripts from IE8 and below
And since the site works without JS, we didn’t have to worry about it.
You can also load images conditionally. We did that in the footer with the Microsoft Edge and AEA logos.
Uses the hero.jsI want to use an Interface Experience Map to show how we enhance for different experiences
Images can be useful to draw users’ eyes when dealing with a page where competition is high.
They become less useful when competition is low
Some images are nice to have, but not necessary.
We need to justify each and every one we use.
The truth is…
I highly recommend reading this article on the Outline. It’s interesting, especially as it comes from a non-tech perspective.
Distillation: If you’re writing an article about cyber security, I really don’t need to see some stock photo of a phony hacker. Unless it adds real value, leave it out.
We need to justify each and every image we use.
I’m just going to run through some high-level bits about image formats.
Una (You-nah) Kravets has a great talk on image formats she gave at AEA in Denver last year that goes into way more depth…
I’m ok just showing their names as the default experience.
We had it both ways. No image as the default and an image if certain conditions were met
To do it, I used a data attribute and wrote up a little shorthand for defining what to do with the images.
Don’t worry about totally following this JS code, but here’s how I got CSS & JS in sync.
Created a div to enable me to watch for specific breakpoints and injected it dynamically via JavaScript (invisibly of course).
In CSS I used font-family to store the breakpoint name for each of the major breakpoints in the design
Adam Bradley came up with this approach.
Then I gave myself a method to access that information from JavaScript.
This is a simplification of the actual code.
Then I could use it.
Let’s look at how we got those file sizes so low!
Here’s the process we used for each image.
First we removed the color information, which saved us about 7% off of the original file size.
Then we cropped and resized the images, reducing the file size by 68%
Save For Web in Photoshop + ImageOptim
Instead of lazy loading an img element, we lazy load a picture element.
You can add as many sources as you want, just put the smallest one first as the first match wins.
So instead of lazy loading this…
We lazy load this
To summarize what I just covered.
It’s important to recognize that…
We must…
Think about what we load & how we load it
Be realistic about what we ”need”
Make every resource have to fight for its place in our sites
Realize that there are trade-offs between design and performance
We should always aim to make decisions in our users’ favor over our own desires
After all…
Hi there, my name is Aaron Gustafson
WaSP
Microsoft - Web Standards, PWAs & a11y