PWA (Progressive Web App) is a powerful solution to multiple problems. Prior to PWA, there were some browser-specific implementations to some of the problems solved by PWA. But, PWA provided a much more elegant solution while also solving even more problems at the same time.
In this post, we’ll cover the problems PWA was created to solve, some of the solutions PWA replaced, tools that make PWA development faster and more flexible, and tips to get the most out of PWA development.
The Problems
Website development is fraught with issues. Let’s list a few of the perils:
- How to cache content
- How to bust that cache when new data is available
- How to enable a website to still function when no Internet connection is available
- How to be confident in the performance of a website
- How to be confident in the security of a website
- How to make sure a website is accessible
- How to make sure a website is following best practices
- How to know a website is using SEO correctly
Performance, SEO, Best Practices, Accessibility, Security
The problems listed above and many more can easily be tackled and solved using tools like Lighthouse to analyze a PWA. Multiple vendors also have documentation, tutorials, and examples available to help get started developing PWAs. For instance, https://developers.google.com/web/progressive-web-apps/ is a great place to start, while https://developers.google.com/web/tools/lighthouse/ is a super-rich PWA website analyzer that will help make sure a website is developed correctly.
Google has a wealth of information available on Lighthouse. My suggestion is to use this tool as much as possible. If all of the suggestions are followed, the website will perform well, follow standards, be secure, accessible, and ready for SEO.
Earlier Solutions
Previous attempts at solving these problems came in the form of browser extensions like Adobe Flash and Microsoft Silverlight. Google had its own app development framework that only worked with Chrome. All of these solutions had problems – whether locked into just one browser or security issues, they just weren’t viable in the long run.
Alternatively, PWA works at a lower level, integrating with HTML5 itself to provide the cross-browser, secure experience that was desperately needed.
Tips For Developing PWAs
PWAs are great, but they can be a challenge to deal with during development. PWAs only work on HTTPS. PWAs need to be assembled into a deployable package. The default cache size is too small to deploy a debug application. Plus, figuring out how the cache scheme works can be tough.
Let’s look at some of the solutions to these challenges.
HTTPS
Each browser vendor solves this in a different way. For Chrome, if you use http://127.0.0.1, it will allow serving up the PWA without using HTTPS. In Firefox, http://127.0.0.1 also works, but there is a checkbox in the inspector’s advanced settings for “Enable Service Workers over HTTP (when toolbox is open)”. This allows using a normal domain name with HTTP to debug locally. Since MS Edge just switched to being based on Chrome, I would image it supports the same thing Chrome does.
How to tell the online status of the website
PWA has built-in capabilities to let the website know when the Internet is working. This allows the website to dynamically adapt. For example, one site I work on caches critical database information for the site to work in an IndexedDB that populates when the user logs in and refers to that when the Internet is offline. For some types of applications where work needs to get done in an offline environment, this is extremely useful.
function isOnline() { // tell the user they are online here / show an indicator // set an attribute somewhere letting the application know it is online } function isOffline() { // tell the user they are offline here / show an indicator // set an attribute somewhere letting the application know it is offline } $(document).ready(function() { if (window.addEventListener) { /* Works well in Chrome, new Edge, Firefox and Opera with the Work Offline option in the File menu. */ window.addEventListener("online", isOnline, false); window.addEventListener("offline", isOffline, false); } else { // IE (old Edge) document.body.ononline = isOnline; document.body.onoffline = isOffline; } // normal initialization code });
Packaging
A npm package that has helped in the past with packaging is sw-precache
. Unfortunately, in typical Google fashion, it has been deprecated in favor of a new tool named Workbox. I haven’t migrated to Workbox yet (since I just saw this), but I will add my tips for sw-precache in case anyone else, besides me, is still using it.
Parameters to swPrecache.write():
maximumFileSizeToCacheInBytes
:
This parameter to sw-precache
increases the default limit for apps. It is most useful when a debug build is needed locally. I set mine at 50000000 just to give some headroom above my currently-built debug package size. Without this, the PWA won’t cache everything, and it won’t work correctly (especially in offline mode).
staticFileGlobs
:
This parameter tells sw-precache about the static parts of the website so it can cache them. Be sure to include the service worker in the list of globs! Example:
staticFileGlobs: [ 'webapp/css/**/*', 'webapp/**/*.html', 'webapp/images/**/*', 'webapp/js/*.js', 'webapp/service-worker-registration.js', 'webapp/**/*.json', 'webapp/**/*.txt', 'webapp/**/*.ico' ],
ignoreUrlParametersMatching
:
This parameter is a regular expression for matching URL request parameters. Anything matching this expression won’t be considered part of the URL when deciding if a URL matches a cached pattern.
stripPrefix
:
In the staticFileGlobs
example above, note that the site is deployed with a prefix of webapp/
. When storing the files in the cache, that prefix should be removed. This parameter provides the means to exclude it.
runtimeCaching
:
This parameter is for listing the remote calls the website makes and how to deal with them regarding caching. Each entry in this array has an attribute for method (get
/put
/post
/delete
), urlPattern
(the relative URL being called), and a handler (networkOnly
/networkFirst
/fastest
/etc). networkOnly
means there is no offline caching of results. networkFirst
means call the network unless it is unavailable, in which case fallback to the cached results.
How to Cache
Here is the code I use for my default service-worker-registration
. It detects when new a new release is available and installs it. The example started with code from Google, which I added to. Since this replaces the entire application, cache-busting techniques of yesteryear are no longer necessary.
/** * Copyright 2015 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* eslint-env browser */ 'use strict'; if ('serviceWorker' in navigator) { // Delay registration until after the page has loaded, to ensure that our // precaching requests don't degrade the first visit experience. // See https://developers.google.com/web/fundamentals/instant-and-offline/service-worker/registration window.addEventListener('load', function() { // Your service-worker.js *must* be located at the top-level directory relative to your site. // It won't be able to control pages unless it's located at the same level or higher than them. // *Don't* register service worker file in, e.g., a scripts/ sub-directory! // See https://github.com/slightlyoff/ServiceWorker/issues/468 navigator.serviceWorker.register('service-worker.js').then(function(reg) { // updatefound is fired if service-worker.js changes. reg.onupdatefound = function() { // The updatefound event implies that reg.installing is set; see // https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#service-worker-container-updatefound-event var installingWorker = reg.installing; installingWorker.onstatechange = function() { switch (installingWorker.state) { case 'installed': if (navigator.serviceWorker.controller) { // At this point, the old content will have been purged and the fresh content will // have been added to the cache. // It's the perfect time to display a "New content is available; please refresh." // message in the page's interface. console.log('New or updated content is available'); alert('New or updated content is available. Please refresh the browser window.'); } else { // At this point, everything has been precached. // It's the perfect time to display a "Content is cached for offline use." message. console.log('Content is now available offline'); alert('Content is now available offline.', 'Offline'); } break; case 'redundant': console.error('The installing service worker became redundant.'); break; } }; }; }).catch(function(e) { console.error('Error during service worker registration:', e); }); }); }
Conclusion
PWA is an amazing technology. We covered the problems PWA was created to solve, some of the solutions PWA replaced, tools that make PWA development faster and more flexible, and useful tips to get the most out of PWA development.
Hopefully, these few tricks will help you harness the power for your own sites.