Repository: GoogleChrome/sw-precache
Pull request #85 (2016-02-17 19:08:36)

Adds a navigateFallbackWhitelist option
R: @ebidel 

This new option allows `navigateFallback` to only apply to certain whitelisted `RegExp`s, matching on the request URL path.


Patch:
diff --git a/README.md b/README.md
index bdeb801..29da8e5 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,7 @@ The full documentation is in this README, and the
     - [logger [function]](#logger-function)
     - [maximumFileSizeToCacheInBytes [Number]](#maximumfilesizetocacheinbytes-number)
     - [navigateFallback [String]](#navigatefallback-string)
+    - [navigateFallbackWhitelist [Array&#x27e8;RegExp&#x27e9;]](#navigatefallbackwhitelist-arrayregexp)
     - [replacePrefix [String]](#replaceprefix-string)
     - [runtimeCaching [Array&#x27e8;Object&#x27e9;]](#runtimecaching-arrayobject)
     - [staticFileGlobs [Array&#x27e8;String&#x27e9;]](#staticfileglobs-arraystring)
@@ -325,6 +326,30 @@ the request is a navigation.
 
 _Default_: `''`
 
+#### navigateFallbackWhitelist [Array&#x27e8;RegExp&#x27e9;] 
+Works to limit the effect of `navigateFallback`, so that the fallback only
+applies to requests for URLs with paths that match at least one
+[`RegExp`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp).
+
+This option is useful if you want to fallback to the cached App Shell for
+certain specific subsections of your site, but not have that behavior apply
+to all of your site's URLs.
+
+For example, if you would like to have `navigateFallback` only apply to
+navigation requests to URLs whose path begins with `/guide/`
+(e.g. `https://example.com/guide/1234`), the following configuration could be
+used:
+
+```js
+navigateFallback: '/shell',
+navigateFallbackWhitelist: [/^\/guide\//]
+```
+
+If set to `[]` (the default), the whitelist will be effectively bypassed, and
+`navigateFallback` will apply to all navigation requests, regardless of URL.
+
+_Default_: `[]`
+
 #### replacePrefix [String]
 Replaces a specified string at the beginning of path URL's at runtime. Use this
 option when you are serving static files from a different directory at runtime
diff --git a/app-shell-demo/gulpfile.babel.js b/app-shell-demo/gulpfile.babel.js
index b1469dc..d907e95 100644
--- a/app-shell-demo/gulpfile.babel.js
+++ b/app-shell-demo/gulpfile.babel.js
@@ -30,7 +30,7 @@ import rev from 'gulp-rev';
 import sass from 'gulp-sass';
 import sequence from 'run-sequence';
 import source from 'vinyl-source-stream';
-import swPrecache from 'sw-precache';
+import swPrecache from '../';
 import uglify from 'gulp-uglify';
 
 const SRC_DIR = 'src';
@@ -133,6 +133,7 @@ gulp.task('generate-service-worker', () => {
       importScripts: swScripts,
       logger: gutil.log,
       navigateFallback: '/shell',
+      navigateFallbackWhitelist: [/^\/guide\//],
       staticFileGlobs: [
         `${BUILD_DIR}/rev/js/**/*.js`,
         `${BUILD_DIR}/rev/styles/all*.css`,
diff --git a/lib/functions.js b/lib/functions.js
index b82eaae..84010fc 100644
--- a/lib/functions.js
+++ b/lib/functions.js
@@ -76,5 +76,18 @@ module.exports = {
       'sw-precache=' + now;
 
     return urlWithCacheBusting.toString();
+  },
+
+  isPathWhitelisted: function(whitelist, absoluteUrlString) {
+    // If the whitelist is empty, then consider all URLs to be whitelisted.
+    if (whitelist.length === 0) {
+      return true;
+    }
+
+    // Otherwise compare each path regex to the path of the URL passed in.
+    var path = (new URL(absoluteUrlString)).pathname;
+    return whitelist.some(function(whitelistedPathRegex) {
+      return path.match(whitelistedPathRegex);
+    });
   }
 };
diff --git a/lib/sw-precache.js b/lib/sw-precache.js
index f84e16a..a2b1d6a 100644
--- a/lib/sw-precache.js
+++ b/lib/sw-precache.js
@@ -78,6 +78,7 @@ function generate(params, callback) {
       logger: console.log,
       maximumFileSizeToCacheInBytes: 2 * 1024 * 1024, // 2MB
       navigateFallback: '',
+      navigateFallbackWhitelist: [],
       stripPrefix: '',
       replacePrefix: '',
       staticFileGlobs: [],
@@ -242,6 +243,10 @@ function generate(params, callback) {
           params.importScripts.map(JSON.stringify).join(',') : null,
         // Ensure that anything false is translated into '', since this will be treated as a string.
         navigateFallback: params.navigateFallback || '',
+        navigateFallbackWhitelist:
+          JSON.stringify(params.navigateFallbackWhitelist.map(function(regex) {
+            return regex.source;
+          })),
         precacheConfig: JSON.stringify(precacheConfig),
         runtimeCaching: runtimeCaching,
         swToolboxCode: swToolboxCode,
diff --git a/package.json b/package.json
index fe5ab44..c02185c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "sw-precache",
-  "version": "3.0.0",
+  "version": "3.1.0",
   "description": "Generate service worker code that will precache specific resources.",
   "author": {
     "name": "Jeff Posnick",
diff --git a/service-worker.tmpl b/service-worker.tmpl
index b9e008e..49e5d20 100644
--- a/service-worker.tmpl
+++ b/service-worker.tmpl
@@ -148,7 +148,10 @@ self.addEventListener('fetch', function(event) {
     // https://code.google.com/p/chromium/issues/detail?id=540967
     // https://bugzilla.mozilla.org/show_bug.cgi?id=1209081
     if (!cacheName && navigateFallback && event.request.headers.has('accept') &&
-        event.request.headers.get('accept').includes('text/html')) {
+        event.request.headers.get('accept').includes('text/html') &&
+        /* eslint-disable quotes, comma-spacing */
+        isPathWhitelisted(<%= navigateFallbackWhitelist %>, event.request.url)) {
+        /* eslint-enable quotes, comma-spacing */
       var navigateFallbackUrl = new URL(navigateFallback, self.location);
       cacheName = AbsoluteUrlToCacheName[navigateFallbackUrl.toString()];
     }


Test patch:
diff --git a/test/test.js b/test/test.js
index 30ea42d..c107ea5 100644
--- a/test/test.js
+++ b/test/test.js
@@ -429,3 +429,27 @@ describe('getCacheBustedUrl', function() {
     done();
   });
 });
+
+describe('isPathWhitelisted', function() {
+  var url = 'http://example.com/test/path?one=two';
+
+  it('should return true when passed an empty whitelist', function(done) {
+    assert(externalFunctions.isPathWhitelisted([], url));
+    done();
+  });
+
+  it('should return true when passed a whitelist matching the url', function(done) {
+    assert(externalFunctions.isPathWhitelisted([/^\/test\/path$/], url));
+    done();
+  });
+
+  it('should return false when passed a whitelist not matching the url', function(done) {
+    assert(!externalFunctions.isPathWhitelisted([/^oops$/], url));
+    done();
+  });
+
+  it('should return true when passed a whitelist whose second value matches the url', function(done) {
+    assert(externalFunctions.isPathWhitelisted([/^oops$/, /^\/test\/path$/], url));
+    done();
+  });
+});