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⟨RegExp⟩]](#navigatefallbackwhitelist-arrayregexp) - [replacePrefix [String]](#replaceprefix-string) - [runtimeCaching [Array⟨Object⟩]](#runtimecaching-arrayobject) - [staticFileGlobs [Array⟨String⟩]](#staticfileglobs-arraystring) @@ -325,6 +326,30 @@ the request is a navigation. _Default_: `''` +#### navigateFallbackWhitelist [Array⟨RegExp⟩] +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()]; } 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(); + }); +});