{"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

\n

Article Series:<\/h4>\n
    \n
  1. The Setup (you are here!)<\/li>\n
  2. The Implementation<\/a><\/li>\n<\/ol>\n<\/div>\n

    Basic approach<\/h3>\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 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

    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

    Starting with a service worker<\/h4>\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

    Basic caching<\/h4>\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

    Abstractions to the rescue<\/h4>\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 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

    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

    \/\/ 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

    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 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

    Adding the runtimeCaching<\/code><\/a> section to our SWPrecacheWebpackPlugin<\/code> pulls in sw-toolbox<\/code> and lets us cache urls matching a certain pattern, dynamically, as needed\u2014with getCache<\/code> helping keep the boilerplate to a minimum.<\/p>\n

    Hello World, sw-toolbox<\/h4>\n

    The entire service worker file that\u2019s generated is pretty big, but let\u2019s just look at a small piece, namely one of the dynamic caches from above:<\/p>\n

    toolbox.router.get(\/^https:\\\/\\\/images-na.ssl-images-amazon.com\/, toolbox.cacheFirst, {\r\n  cache: { maxEntries: 500, name: \"amazon-images1\", maxAgeSeconds: 63072000 },\r\n  successResponses: \/0|[123].*\/\r\n});<\/code><\/pre>\n

    sw-toolbox<\/code> has provided us with a nice, high-level router object we can use to hook into various URL requests, MVC-style. We\u2019ll use this to setup offline shortly.<\/p>\n

    Don\u2019t forget to register the service worker<\/h4>\n

    And, of course, the existence of the service worker file that\u2019s generated above is of no use by itself; it needs to be registered. The code looks like this, but be sure to either have it inside an onload<\/code> listener, or some other place that\u2019ll be guaranteed to run after the page has loaded.<\/p>\n

    if (\"serviceWorker\" in navigator) {\r\n  navigator.serviceWorker.register(\"\/service-worker.js\");\r\n}<\/code><\/pre>\n

    There we have it! We got a basic service worker running, which caches our application resources. Tune in tomorrow when we extend it to support offline.<\/p>\n

    \n

    Article Series:<\/h4>\n
      \n
    1. The Setup (you are here!)<\/li>\n
    2. The Implementation<\/a><\/li>\n<\/ol>\n<\/div>\n","protected":false},"excerpt":{"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, […]<\/p>\n","protected":false},"author":250838,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"c2c_always_allow_admin_comments":false,"footnotes":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":[]},"categories":[4,19,20],"tags":[1322,1359,649,949],"jetpack_publicize_connections":[],"acf":[],"jetpack_featured_media_url":"","jetpack-related-posts":[],"featured_media_src_url":null,"_links":{"self":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/263322"}],"collection":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/users\/250838"}],"replies":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/comments?post=263322"}],"version-history":[{"count":20,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/263322\/revisions"}],"predecessor-version":[{"id":263385,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/263322\/revisions\/263385"}],"wp:attachment":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/media?parent=263322"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/categories?post=263322"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/tags?post=263322"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}