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
{
"build-app": {
"pwa": {
"manifest": {
"name": "Todo MVC",
"description": "A simple to-do application created with Dojo",
"icons": [
{ "src": "./favicon-16x16.png", "sizes": "16x16", "type": "image/png" },
{ "src": "./favicon-32x32.png", "sizes": "32x32", "type": "image/png" },
{ "src": "./favicon-48x48.png", "sizes": "48x48", "type": "image/png" },
{ "src": "./favicon-256x256.png", "sizes": "256x256", "type": "image/png" }
]
}
}
}
}
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
{
"build-app": {
"pwa": {
"serviceWorker": {
"cachePrefix": "my-app",
"excludeBundles": ["admin"],
"routes": [
{
"urlPattern": ".*\\.(png|jpg|gif|svg)",
"strategy": "cacheFirst",
"cacheName": "my-app-images",
"expiration": { "maxEntries": 10, "maxAgeSeconds": 604800 }
},
{
"urlPattern": "http://my-app-url.com/api/articles",
"strategy": "cacheFirst",
"expiration": { "maxEntries": 25, "maxAgeSeconds": 86400 }
}
]
}
}
}
}
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.
Property | Type | Optional | Description |
---|---|---|---|
bundles | string[] |
Yes | An array of bundles to include in the precache. Defaults to all bundles. |
cachePrefix | string |
Yes | The prefix to use for the runtime precache cache. |
clientsClaim | boolean |
Yes | Whether the service worker should start controlling clients on activation. Defaults to false . |
excludeBundles | string[] |
Yes | An array of bundles to include in the precache. Defaults to [] . |
importScripts | string[] |
Yes | An array of script paths that should be loaded within the service worker |
precache | object |
Yes | An object of precache configuration options (see below) |
routes | object[] |
Yes | An array of runtime caching config objects (see below) |
skipWaiting | boolean |
Yes | Whether the service worker should skip the waiting lifecycle |
Precaching
The precache
option can take the following options to control precaching behavior:
Property | Type | Optional | Description |
---|---|---|---|
baseDir | string |
Yes | The base directory to match include against. |
ignore | string[] |
Yes | An array of glob pattern string matching files that should be ignored when generating the precache. Defaults to [ 'node_modules/**/*' ] . |
include | string or string[] |
Yes | A 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. |
index | string |
Yes | The index filename that should be checked if a request fails for a URL ending in / . Defaults to 'index.html' . |
maxCacheSize | number |
Yes | The maximum size in bytes a file must not exceed to be added to the precache. Defaults to 2097152 (2 MB). |
strict | boolean |
Yes | If true , then the build will fail if an include pattern matches a non-existent directory. Defaults to true . |
symlinks | boolean |
Yes | Whether 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:
Property | Type | Optional | Description |
---|---|---|---|
urlPattern | string |
No | A pattern string (which will be converted a regular expression) that matches a specific route. |
strategy | string |
No | The caching strategy (see below). |
options | object |
Yes | An object of additional options, each detailed below. |
cacheName | string |
Yes | The 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} ). |
cacheableResponse | object |
Yes | Uses 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 . |
expiration | object |
Yes | Controls 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. |
networkTimeoutSeconds | number |
Yes | Used 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, thenetworkFirst
strategy may be a better fit.