Repository: GoogleChrome/sw-precache (js)

Issue:
sw-precache and implicit index.html rewrites
Many servers are configured to handle a request for a bare directory URL as being equivalent to the request for `index.html` within that directory.

It's possible, but not intuitive, to handle this using the [`dynamicUrlToDependencies`](https://github.com/GoogleChrome/sw-precache/blob/master/demo/gulpfile.js#L39) parameter. This approach also forces you to list entries for every directory that has an implicit `index.html` rewrite.

At the very least, the docs should clarify this behavior. Better, `sw-precache` should add a new option to configure what bare directories get rewritten to (e.g. `index.html, `index.php`, `index`) and should automatically handle serving the appropriate file without the need for [`dynamicUrlToDependencies`] mappings.

Pull request description:
Directory index option
Closes #20

Patch:
diff --git a/README.md b/README.md
index 29f7130..ed49844 100644
--- a/README.md
+++ b/README.md
@@ -106,6 +106,17 @@ property from your `package.json`.
 
 Default: `''`
 
+### directoryIndex [String]
+Many web servers automatically treat a URL corresponding to a directory (i.e. ending in `'/'`) as
+if it were a request for a specific index file in that directory, traditionally `'index.html'`.
+`sw-precache` will take that translation into account and serve the contents a relative
+`directoryIndex` file when there's no other match for a URL ending in `'/'`.
+To turn off this behavior, set `directoryIndex` to `false` or `null`.
+To override this behavior for one or more URLs, use the `dynamicUrlToDependencies` option to
+explicitly set up mappings between a directory URL and the corresponding file to use.
+
+Default: `'index.html'`
+
 ### dynamicUrlToDependencies [Object&#x27e8;String,Array&#x27e8;String&#x27e9;&#x27e9;]
 Maps a dynamic URL string to an array of all the files that URL's contents depend on.
 E.g., if the contents of `/pages/home` are generated server-side via the templates `layout.jade` and
diff --git a/demo/gulpfile.js b/demo/gulpfile.js
index b888273..18b6a6a 100644
--- a/demo/gulpfile.js
+++ b/demo/gulpfile.js
@@ -36,7 +36,6 @@ function generateServiceWorkerFileContents(rootDir, handleFetch, callback) {
   var config = {
     cacheId: packageJson.name,
     dynamicUrlToDependencies: {
-      './': [path.join(rootDir, 'index.html')],
       'dynamic/page1': [
         path.join(rootDir, 'views', 'layout.jade'),
         path.join(rootDir, 'views', 'page1.jade')
diff --git a/lib/functions.js b/lib/functions.js
index 3891258..3a4ee62 100644
--- a/lib/functions.js
+++ b/lib/functions.js
@@ -55,5 +55,13 @@ module.exports = {
       absoluteUrlToCacheName: absoluteUrlToCacheName,
       currentCacheNamesToAbsoluteUrl: currentCacheNamesToAbsoluteUrl
     };
+  },
+
+  addDirectoryIndex: function(originalUrl, index) {
+    var url = new URL(originalUrl);
+    if (url.pathname.slice(-1) === '/') {
+      url.pathname += index;
+    }
+    return url.toString();
   }
 };
diff --git a/lib/sw-precache.js b/lib/sw-precache.js
index 3f61ad7..35146cd 100644
--- a/lib/sw-precache.js
+++ b/lib/sw-precache.js
@@ -62,6 +62,7 @@ function getHash(data) {
 module.exports = function(params, callback) {
   params = defaults(params || {}, {
     cacheId: '',
+    directoryIndex: 'index.html',
     dynamicUrlToDependencies: {},
     handleFetch: true,
     ignoreUrlParametersMatching: [/^utm_/],
@@ -145,6 +146,8 @@ module.exports = function(params, callback) {
 
     var populatedTemplate = template(data)({
       cacheId: params.cacheId,
+      // Ensure that anything false is translated into '', since this will be treated as a string.
+      directoryIndex: params.directoryIndex || '',
       externalFunctions: externalFunctions,
       handleFetch: params.handleFetch,
       ignoreUrlParametersMatching: params.ignoreUrlParametersMatching,
diff --git a/package.json b/package.json
index 99e7468..5222181 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "sw-precache",
-  "version": "1.2.7",
+  "version": "1.3.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 3e5b504..2078475 100644
--- a/service-worker.tmpl
+++ b/service-worker.tmpl
@@ -133,6 +133,12 @@ self.addEventListener('fetch', function(event) {
       IgnoreUrlParametersMatching);
 
     var cacheName = AbsoluteUrlToCacheName[urlWithoutIgnoredParameters];
+    var directoryIndex = '<%= directoryIndex %>';
+    if (!cacheName && directoryIndex) {
+      urlWithoutIgnoredParameters = addDirectoryIndex(urlWithoutIgnoredParameters, directoryIndex);
+      cacheName = AbsoluteUrlToCacheName[urlWithoutIgnoredParameters];
+    }
+
     if (cacheName) {
       event.respondWith(
         // We can't call cache.match(event.request) since the entry in the cache will contain the


Test patch:
diff --git a/test/test.js b/test/test.js
index d0335be..0a853fe 100644
--- a/test/test.js
+++ b/test/test.js
@@ -211,3 +211,34 @@ describe('populateCurrentCacheNames', function() {
   });
 });
 
+describe('addDirectoryIndex', function() {
+  var directoryIndex = 'index.html';
+
+  it('should append the directory index when the URL ends with /', function(done) {
+    var url = 'http://example.com/';
+    var urlWithIndex = externalFunctions.addDirectoryIndex(url, directoryIndex);
+    assert.strictEqual(urlWithIndex, 'http://example.com/index.html');
+    done();
+  });
+
+  it('should append the directory index when the URL has an implicit /', function(done) {
+    var url = 'http://example.com';
+    var urlWithIndex = externalFunctions.addDirectoryIndex(url, directoryIndex);
+    assert.strictEqual(urlWithIndex, 'http://example.com/index.html');
+    done();
+  });
+
+  it('should not do anything when the URL does not end in /', function(done) {
+    var url = 'http://example.com/path/file.txt';
+    var urlWithIndex = externalFunctions.addDirectoryIndex(url, directoryIndex);
+    assert.strictEqual(urlWithIndex, url);
+    done();
+  });
+
+  it('should append the directory index without modifying URL parameters', function(done) {
+    var url = 'http://example.com?test=param';
+    var urlWithIndex = externalFunctions.addDirectoryIndex(url, directoryIndex);
+    assert.strictEqual(urlWithIndex, 'http://example.com/index.html?test=param');
+    done();
+  });
+});