Progressive web applications

Progressive web apps (PWAs) are made up of a collection of technologies and patterns that improve the user experience and help create a more reliable and usable application. Mobile users in particular will see the application as more integrated into their device similar to an installed app.

The core of a progressive web app is made up of two technologies: Service workers and a manifest. Dojo’s build command supports both of these through .dojorc with the pwa object.

Manifest

The manifest describes an application in a JSON file and provides details so it may be installed on a device’s homescreen directly from the web.

.dojorc

  1. {
  2. "build-app": {
  3. "pwa": {
  4. "manifest": {
  5. "name": "Todo MVC",
  6. "description": "A simple to-do application created with Dojo",
  7. "icons": [
  8. { "src": "./favicon-16x16.png", "sizes": "16x16", "type": "image/png" },
  9. { "src": "./favicon-32x32.png", "sizes": "32x32", "type": "image/png" },
  10. { "src": "./favicon-48x48.png", "sizes": "48x48", "type": "image/png" },
  11. { "src": "./favicon-256x256.png", "sizes": "256x256", "type": "image/png" }
  12. ]
  13. }
  14. }
  15. }
  16. }

When a manifest is provided dojo build will inject the necessary <meta> tags in the applications index.html.

  • mobile-web-app-capable="yes": indicates to Chrome on Android that the application can be added to the user’s homescreen.
  • apple-mobile-web-app-capable="yes": indicates to iOS devices that the application can be added to the user’s homescreen.
  • apple-mobile-web-app-status-bar-style="default": indicates to iOS devices that the status bar should use the default appearance.
  • apple-touch-icon="{{icon}}": the equivalent of the manifests’ icons since iOS does not currently read icons from the manifest. A separate meta tag is injected for each entry in the icons array.

Service worker

A service worker is a type of web worker that is able to intercept network requests, cache, and provide resources. Dojo’s build command can automatically build fully-functional service worker that is activated on startup and complete with precaching and custom route handling from a configuration file.

For instance, we could write a configuration to create a simple service worker that cached all of the application bundles except the admin bundle and cached recent application images and articles.

.dojorc

  1. {
  2. "build-app": {
  3. "pwa": {
  4. "serviceWorker": {
  5. "cachePrefix": "my-app",
  6. "excludeBundles": ["admin"],
  7. "routes": [
  8. {
  9. "urlPattern": ".*\\.(png|jpg|gif|svg)",
  10. "strategy": "cacheFirst",
  11. "cacheName": "my-app-images",
  12. "expiration": { "maxEntries": 10, "maxAgeSeconds": 604800 }
  13. },
  14. {
  15. "urlPattern": "http://my-app-url.com/api/articles",
  16. "strategy": "cacheFirst",
  17. "expiration": { "maxEntries": 25, "maxAgeSeconds": 86400 }
  18. }
  19. ]
  20. }
  21. }
  22. }
  23. }

ServiceWorker configuration

Under the hood, the ServicerWorkerPlugin from @dojo/webpack-contrib is used to generate the service worker, and all of its options are valid pwa.serviceWorker properties.

PropertyTypeOptionalDescription
bundlesstring[]YesAn array of bundles to include in the precache. Defaults to all bundles.
cachePrefixstringYesThe prefix to use for the runtime precache cache.
clientsClaimbooleanYesWhether the service worker should start controlling clients on activation. Defaults to false.
excludeBundlesstring[]YesAn array of bundles to include in the precache. Defaults to [].
importScriptsstring[]YesAn array of script paths that should be loaded within the service worker
precacheobjectYesAn object of precache configuration options (see below)
routesobject[]YesAn array of runtime caching config objects (see below)
skipWaitingbooleanYesWhether the service worker should skip the waiting lifecycle

Precaching

The precache option can take the following options to control precaching behavior:

PropertyTypeOptionalDescription
baseDirstringYesThe base directory to match include against.
ignorestring[]YesAn array of glob pattern string matching files that should be ignored when generating the precache. Defaults to [ ‘node_modules/*/‘ ].
includestring or string[]YesA glob pattern string or an array of glob pattern strings matching files that should be included in the precache. Defaults to all files in the build pipeline.
indexstringYesThe index filename that should be checked if a request fails for a URL ending in /. Defaults to ‘index.html’.
maxCacheSizenumberYesThe maximum size in bytes a file must not exceed to be added to the precache. Defaults to 2097152 (2 MB).
strictbooleanYesIf true, then the build will fail if an include pattern matches a non-existent directory. Defaults to true.
symlinksbooleanYesWhether to follow symlinks when generating the precache. Defaults to true.

Runtime caching

In addition to precaching, strategies can be provided for specific routes to determine whether and how they can be cached. This routes option is an array of objects with the following properties:

PropertyTypeOptionalDescription
urlPatternstringNoA pattern string (which will be converted a regular expression) that matches a specific route.
strategystringNoThe caching strategy (see below).
optionsobjectYesAn object of additional options, each detailed below.
cacheNamestringYesThe name of the cache to use for the route. Note that the cachePrefix is not prepended to the cache name. Defaults to the main runtime cache (${cachePrefix}-runtime-${domain}).
cacheableResponseobjectYesUses HTTP status codes and or headers to determine whether a response can be cached. This object has two optional properties: statuses and headers. statuses is an array of HTTP status codes that should be considered valid for the cache. headers is an object of HTTP header and value pairs; at least one header must match for the response to be considered valid. Defaults to { statuses: [ 200 ] } when the strategy is ‘cacheFirst’, and { statuses: [0, 200] } when the strategy is either networkFirst or staleWhileRevalidate.
expirationobjectYesControls how the cache is invalidated. This object has two optional properties. maxEntries is the number of responses that can be cached at any given time. Once this max is exceeded, the oldest entry is removed. maxAgeSeconds is the oldest a cached response can be in seconds before it gets removed.
networkTimeoutSecondsnumberYesUsed with the networkFirst strategy to specify how long in seconds to wait for a resource to load before falling back on the cache.

Four routing strategies are currently supported:

  • networkFirst attempts to load a resource over the network, falling back on the cache if the request fails or times out. This is a useful strategy for assets that either change frequently or may change frequently (i.e., are not versioned).
  • cacheFirst loads a resource from the cache unless it does not exist, in which case it is fetched over the network. This is best for resources that change infrequently or can be cached for a long time (e.g., versioned assets).
  • networkOnly forces the resource to always be retrieved over the network, and is useful for requests that have no offline equivalent.
  • staleWhileRevalidate requests resources from both the cache and the network simulaneously. The cache is updated with each successful network response. This strategy is best for resources that do not need to be continuously up-to-date, like user avatars. However, when fetching third-party resources that do not send CORS headers, it is not possible to read the contents of the response or verify the status code. As such, it is possible that a bad response could be cached. In such cases, the networkFirst strategy may be a better fit.