{"id":263322,"date":"2017-12-06T07:57:27","date_gmt":"2017-12-06T14:57:27","guid":{"rendered":"http:\/\/css-tricks.com\/?p=263322"},"modified":"2017-12-07T07:34:55","modified_gmt":"2017-12-07T14:34:55","slug":"making-your-web-app-work-offline-part-1","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/making-your-web-app-work-offline-part-1\/","title":{"rendered":"Making your web app work offline, Part 1: The Setup"},"content":{"rendered":"
This two-part series is a gentle introduction to offline web development. Getting a web application to do something while offline is surprisingly tricky, requiring a lot of things to be in place and functioning correctly. We’re going to cover all of these pieces from a high level, with working examples. This post is an overview, but there are plenty of more-detailed resources listed throughout.<\/p>\n
<\/p>\n
I\u2019ll be making heavy use of JavaScript\u2019s async\/await syntax<\/a>. It\u2019s supported in all major browsers and Node, and greatly simplifies Promise-based code. The link above explains async well, but in a nutshell they allow you to resolve a promise, and access its value directly in code with We\u2019ll be extending an existing booklist<\/a> project to sync the current user\u2019s books to IndexedDB, and create a simplified offline page that\u2019ll show even when the user has no network connectivity.<\/p>\n The one non-negotiable thing you need for offline development is a service worker. A service worker is a background process that can, among other things, intercept network requests; redirect them; short circuit them by returning cached responses; or execute them as normal and do custom things with the response, like caching.<\/p>\n Probably the first, most basic, yet high impact thing you\u2019ll do with a service worker is have it cache your application\u2019s resources. Service worker and the cache it uses are extremely low-level primitives; everything is manual. In order to properly cache your resources you\u2019ll need to fetch and add them to a cache, but then you\u2019ll also need to track changes to these resources. You’ll track when they change, remove the prior version, and fetch and update the new one. <\/p>\n In practice, this means your service worker code will need to be generated as part of a build step, which hashes your files, and generates a file that\u2019s smart enough to record these changes between versions, and update caches as needed. <\/p>\n This is extremely tedious and error-prone code that you\u2019d likely never want to write yourself. Luckily some smart people have written abstractions to help, namely sw-precache<\/a>, and sw-toolbox<\/a> by the great people at Google. Note, Google has since deprecated these tools in favor of the newer Workbox<\/a>. I\u2019ve yet to move my code over since Let\u2019s jump right in. We\u2019re using webpack, and as webpack goes, there\u2019s a plugin, so let\u2019s check that out first.<\/p>\n By default ALL of the bundles webpack makes will be precached. We\u2019re also manually providing some paths to static resources I want cached in the await<\/code>, rather than calling
.then<\/code> and accessing the value in the callback, which often leads to the dreaded “rightward drift.”<\/p>\n
What are we building?<\/h4>\n
Starting with a service worker<\/h4>\n
Basic caching<\/h4>\n
Abstractions to the rescue<\/h4>\n
sw-*<\/code> works so well, but in any event the ideas are the same, and I\u2019m told the conversion is easy. And it\u2019s worth mentioning that sw-precache currently has about 30,000 downloads per day<\/strong>, so it\u2019s still widely used.<\/p>\n
Hello World, sw-precache<\/h4>\n
\/\/ inside your webpack config\r\nnew SWPrecacheWebpackPlugin({\r\n mergeStaticsConfig: true,\r\n filename: \"service-worker.js\",\r\n staticFileGlobs: [ \/\/static resources to cache\r\n \"static\/bootstrap\/css\/bootstrap-booklist-build.css\",\r\n ...\r\n ],\r\n ignoreUrlParametersMatching: \/.\/,\r\n stripPrefixMulti: { \/\/any paths that need adjusting\r\n \"static\/\": \"react-redux\/static\/\", \r\n ...\r\n },\r\n ...\r\n})<\/code><\/pre>\n
staticFileGlobs<\/code><\/a> property, and I\u2019m adjusting some paths in
stripPrefixMulti<\/code><\/a>.<\/p>\n
\/\/ inside your webpack config\r\nconst getCache = ({ name, pattern, expires, maxEntries }) => ({\r\n urlPattern: pattern,\r\n handler: \"cacheFirst\",\r\n options: {\r\n cache: {\r\n maxEntries: maxEntries || 500,\r\n name: name,\r\n maxAgeSeconds: expires || 60 * 60 * 24 * 365 * 2 \/\/2 years\r\n },\r\n successResponses: \/0|[123].*\/\r\n }\r\n});\r\n\r\nnew SWPrecacheWebpackPlugin({\r\n ...\r\n runtimeCaching: [ \/\/pulls in sw-toolbox and caches dynamically based on a pattern\r\n getCache({ pattern: \/^https:\\\/\\\/images-na.ssl-images-amazon.com\/, name: \"amazon-images1\" }),\r\n getCache({ pattern: \/book\\\/searchBooks\/, name: \"book-search\", expires: 60 * 7 }), \/\/7 minutes\r\n ...\r\n ]\r\n})<\/code><\/pre>\n