PWA - Progressive Web Apps Wiki
What are Progressive Web Apps?
Why should you care?
PWA let you write awesome mobile web experiences for your users. They are:
- Awesome like the Web
- Awesome distribution model - the web - you don’t need to install anything in your device
- Great reach (as compared to 80% of native apps usage being limited to 3 top apps)
- Awesome like native
- Fast initial load and great performance
- Offline mode
- Add to home screen
- Push notifications
In summary bring together the awesome capabilities of the web in terms of distribution and reach, and the awesome capabilities of native in terms of UX, performance and engagement (Reliable, Fast and Engaging).
Why is this important? Because mobile web traffic is growing and mobile users visit in average 100 mobile web pages a month, about 2.5x the number of apps they use during the same time. PWA aim to bring the engagement of the native apps into web, while keeping the low cost of user acquisition inherent to it. The cool thing with PWA is that you can take an incrementally approach and add on new PWA features as you go taking advantage of Lighthouse to show you the way.
Which technology stands behind PWA?
- App shell that let’s you provide a shell to your app that will load instantly
- Service workers that act as a local proxy between your app and the server
- Add to home screen capabilities
- Push notifications API
- Web Payments API
- Credentials Management API
- And not included in PWA but very related to improving your mobile web experience is AMP - Accelerated Mobile Pages - that focus particularly in providing an awesome first load experience
Service Workers
A service worker is a client side proxy that sits between your application and the server. Service workers give you a lot of control as to how to treat http requests and caching for any type of resources be it static assets like HTML, CSS, JavaScript and images, or HTTP requests to web services.
Service workers are a special type of web workers that just like web workers run in a separate thread from the browser but they have the special ability to be able to receive messages when the web application is not active, and even when the browser is closed. Some great use cases of web workers are caching, enabling offline workflows and, because they can receive messages when the application is inactive, push notifications and background syncing. They also have access to the new Cache API and IndexDB to store information.
Because of the asynchronous nature of requests and responses the Service workers API is asynchronous and based on Promises.
Service workers have three stages in their lifecycle:
- Registration: You register the service worker in your web application by pointing the browser to the service worker source code. You can also setup the service worker configuration.
- Installation: After registering the service worker is installed in the background. The
install
event is triggered immediately after registration and you can setup stuff like pre-catching of the app shell, anything that makes the app ready to be used. (The shell of your app that allows you to show something to the user as soon as the app is loaded). Note that to transition from one version of a service worker to another all windows with a given version need to be closed before we can start running the new version. - Activation: Once successfully installed the service worker becomes active. From the on it will handle all network requests in its scope (by default all requests in the web app domain if the service worker is located in the root of the app). You can use the
activate
event to clean-up old caches from previous service worker versions.
Service workers are event based and can react to the following events:
install
activate
message
to communicate with other scriptsfetch
triggered any time a resource is requested within the service worker scopesync
used for background syncpush
are initiated by the backend service through a browsers push service. They can be used for push notifications or other functions.
Registering a Service Worker
In order to register a service worker you use the navigator.serviceWorker.register
method as follows:
// check service worker is supported
if ('serviceWorker' in navigator) {
// register service worker
// point to where the code for the service worker is located
navigator.serviceWorker
.register('/service-worker.js')
// the register method returns a promise
.then(function(registration) {
// registration succeeds
console.log('Registration succeeded! Yey!');
})
.catch(function(error) {
// registration fails
console.log('Registration failed:', error);
});
}
In addition to defining where the service worker code is located you can also use service worker registration to configure the scope of the service worker.
What is The Scope of a Service Worker?
The scope of a service worker determines which network requests the service worker is capable of intercepting. By default, the scope of a service worker is determined by where the service worker file is located. A service worker with a file located in the root folder of a website will be able to intercept all requests for that domain. For instance, if you have a service worker in:
https://www.barbarianmeetscoding.com/service-worker.js
It’d be able to intercept any calls in the barbarianmeetscoding.com
domain including those in barbarianmeetscoding.com/wiki
, barbarianmeetscoding.com/blog
, barbarianmeetscoding.com/categories
, etc.
Configuring the Scope of a Service Worker
The register
method allows you to explicitly configure the scope of a service worker:
navigator.serviceWorker.register('/service-worker.js', {
scope: '/app/'
});
// HERE
The Cache API
The new cache API can be accessed within a service worker (or a webpage) and lets you cache resources that are tied to a URL.
Common patterns for caching resources are:
- On service worker install, create cache and add initial resources (the app shell)
- On activate you can update your cache
- On fetch you can retrieve the data from the cache, the network or the local database (IndexedDB)
- On user interaction with an “add for online viewing” feature
Having cached your data there are different strategies for retrieving cached data:
- Cache falling back to network. When attempting a request go first to the cache and fall back to network if you can’t find a resource. This is the most common approach for offline first web apps.
- Network falling back to cache. When attempting a request go first to the network and fall back to the cache if there’s an error and we can’t retrieve the data from the network. This is ideal for dynamic content that refreshes often. The user experience of this approach results in online users getting the latest version of the site while offline users get a cached version of the site and can still interact with it. This can be an issue for users with slow connections because they’d need to wait for the request to fail until they see any content.
- Cache then network. Makes requests simultaneously to the cache and the network. The cache will likely return the data faster than the network and the user will be able to see some data as soon as possible. When the network returns with fresher data we update the UI and update the cache.
- Generic fallback. For those cases where there’s no cached content and the network fails it is useful to have an offline fallback to show to the user. This can be used for pages or more specific resources like images. This cached fallback resources are probably cached when you install the service worker.
Example of Creating Initial Cache and Adding App Shell to Cache
Example of Clearing Outdated Caches
Example of Retrieving Resources with Network Fallback
Example of Caching Network Responses
Example of Cache Falling Back to Network
What to Learn More? Check this references!
- PWA at Google Developers
- PWA checklist
- PWA codelabs at Google Developers
- PWA Instructor Led Training at Google Developers
- AliExpress case study
- The Offline Cookbook
The Fetch API
The fetch API is a improved version of the XHR API with a promise based API.
CORS - Cross Origin Resource Sharing
By default the browser enforces a single origin policy for your web service requests, that is, you can only consume web services hosted in your same origin (scheme, hostname and port). With CORS you can allow a service to be consumed from a different origin than service itself. When making a cross-origin request the browser will append the origin
header in the request while the server will respond with a access-allow-control-origin
header containing the allowed origins. If the origin of the request is any of the origins allowed by the server then the request will succeed.
Written by Jaime González García , dad, husband, software engineer, ux designer, amateur pixel artist, tinkerer and master of the arcane arts. You can also find him on Twitter jabbering about random stuff.