goldhand / sw-precache-webpack-plugin

Webpack plugin that generates a service worker using sw-precache that will cache webpack's bundles' emitted assets. You can optionally pass sw-precache configuration options to webpack through this plugin.
MIT License
1.44k stars 104 forks source link

Browser alerts that Manifest start_url is not cached #125

Open matheusgrieger opened 6 years ago

matheusgrieger commented 6 years ago

webpack version: 3.8.1

sw-precache-webpack-plugin version: 0.11.4

Please tell us about your environment: Linux Ubuntu 17.10 and Windows 10

Browser: Chrome 62.0.3202.94 (64 bits)

Current behavior: The sw.js gets generated correctly and all static assets are being cached as expected but other requests (such as specified start_url in manifest.json) are not, so after looking at a Lighthouse report within Chrome, it points to that error.

Expected/desired behavior: Other requests should be cached as well as the static assets.

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem along with your: Not sure if it is a bug or I'm doing something wrong.

var precacheConfig = [["public/static/css/app.cc24ceb5a97c2797a814e6ce8ff8a114.css","3a013b79477bece4a6cc4b7df6e71396"],["public/static/js/app.8d33aed6ca8472d71c3b.js","58c1216858cce7389f6a6548f4747508"],["public/static/js/manifest.c21466ca933b31a4f4e8.js","0b0855da24e74d63e9d55b2d75ab9f38"],["public/static/js/vendor.49c73aaccaf22bf7a987.js","a63c7b3b582032741955aeed76f14a39"]]; var cacheName = 'sw-precache-v3-projectName-' + (self.registration ? self.registration.scope : ''); // cache name changed for this example

var ignoreUrlParametersMatching = [/^utm_/];

var addDirectoryIndex = function (originalUrl, index) { var url = new URL(originalUrl); if (url.pathname.slice(-1) === '/') { url.pathname += index; } return url.toString(); };

var cleanResponse = function (originalResponse) { // If this is not a redirected response, then we don't have to do anything. if (!originalResponse.redirected) { return Promise.resolve(originalResponse); }

// Firefox 50 and below doesn't support the Response.body stream, so we may
// need to read the entire body to memory as a Blob.
var bodyPromise = 'body' in originalResponse ?
  Promise.resolve(originalResponse.body) :
  originalResponse.blob();

return bodyPromise.then(function(body) {
  // new Response() is happy when passed either a stream or a Blob.
  return new Response(body, {
    headers: originalResponse.headers,
    status: originalResponse.status,
    statusText: originalResponse.statusText
  });
});

};

var createCacheKey = function (originalUrl, paramName, paramValue, dontCacheBustUrlsMatching) { // Create a new URL object to avoid modifying originalUrl. var url = new URL(originalUrl);

// If dontCacheBustUrlsMatching is not set, or if we don't have a match,
// then add in the extra cache-busting URL parameter.
if (!dontCacheBustUrlsMatching ||
    !(url.pathname.match(dontCacheBustUrlsMatching))) {
  url.search += (url.search ? '&' : '') +
    encodeURIComponent(paramName) + '=' + encodeURIComponent(paramValue);
}

return url.toString();

};

var 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);
});

};

var stripIgnoredUrlParameters = function (originalUrl, ignoreUrlParametersMatching) { var url = new URL(originalUrl); // Remove the hash; see https://github.com/GoogleChrome/sw-precache/issues/290 url.hash = '';

url.search = url.search.slice(1) // Exclude initial '?'
  .split('&') // Split into an array of 'key=value' strings
  .map(function(kv) {
    return kv.split('='); // Split each 'key=value' string into a [key, value] array
  })
  .filter(function(kv) {
    return ignoreUrlParametersMatching.every(function(ignoredRegex) {
      return !ignoredRegex.test(kv[0]); // Return true iff the key doesn't match any of the regexes.
    });
  })
  .map(function(kv) {
    return kv.join('='); // Join each [key, value] array into a 'key=value' string
  })
  .join('&'); // Join the array of 'key=value' strings into a string with '&' in between each

return url.toString();

};

var hashParamName = '_sw-precache'; var urlsToCacheKeys = new Map( precacheConfig.map(function(item) { var relativeUrl = item[0]; var hash = item[1]; var absoluteUrl = new URL(relativeUrl, self.location); var cacheKey = createCacheKey(absoluteUrl, hashParamName, hash, false); return [absoluteUrl.toString(), cacheKey]; }) );

function setOfCachedUrls(cache) { return cache.keys().then(function(requests) { return requests.map(function(request) { return request.url; }); }).then(function(urls) { return new Set(urls); }); }

self.addEventListener('install', function(event) { event.waitUntil( caches.open(cacheName).then(function(cache) { return setOfCachedUrls(cache).then(function(cachedUrls) { return Promise.all( Array.from(urlsToCacheKeys.values()).map(function(cacheKey) { // If we don't have a key matching url in the cache already, add it. if (!cachedUrls.has(cacheKey)) { var request = new Request(cacheKey, {credentials: 'same-origin'}); return fetch(request).then(function(response) { // Bail out of installation unless we get back a 200 OK for // every request. if (!response.ok) { throw new Error('Request for ' + cacheKey + ' returned a ' + 'response with status ' + response.status); }

            return cleanResponse(response).then(function(responseToCache) {
              return cache.put(cacheKey, responseToCache);
            });
          });
        }
      })
    );
  });
}).then(function() {

  // Force the SW to transition from installing -> active state
  return self.skipWaiting();

})

); });

self.addEventListener('activate', function(event) { var setOfExpectedUrls = new Set(urlsToCacheKeys.values());

event.waitUntil( caches.open(cacheName).then(function(cache) { return cache.keys().then(function(existingRequests) { return Promise.all( existingRequests.map(function(existingRequest) { if (!setOfExpectedUrls.has(existingRequest.url)) { return cache.delete(existingRequest); } }) ); }); }).then(function() {

  return self.clients.claim();

})

); });

self.addEventListener('fetch', function(event) { if (event.request.method === 'GET') { // Should we call event.respondWith() inside this fetch event handler? // This needs to be determined synchronously, which will give other fetch // handlers a chance to handle the request if need be. var shouldRespond;

// First, remove all the ignored parameters and hash fragment, and see if we
// have that URL in our cache. If so, great! shouldRespond will be true.
var url = stripIgnoredUrlParameters(event.request.url, ignoreUrlParametersMatching);
shouldRespond = urlsToCacheKeys.has(url);

// If shouldRespond is false, check again, this time with 'index.html'
// (or whatever the directoryIndex option is set to) at the end.
var directoryIndex = 'index.html';
if (!shouldRespond && directoryIndex) {
  url = addDirectoryIndex(url, directoryIndex);
  shouldRespond = urlsToCacheKeys.has(url);
}

// If shouldRespond is still false, check to see if this is a navigation
// request, and if so, whether the URL matches navigateFallbackWhitelist.
var navigateFallback = '/';
if (!shouldRespond &&
    navigateFallback &&
    (event.request.mode === 'navigate') &&
    isPathWhitelisted([], event.request.url)) {
  url = new URL(navigateFallback, self.location).toString();
  shouldRespond = urlsToCacheKeys.has(url);
}

// If shouldRespond was set to true at any point, then call
// event.respondWith(), using the appropriate cache key.
if (shouldRespond) {
  event.respondWith(
    caches.open(cacheName).then(function(cache) {
      return cache.match(urlsToCacheKeys.get(url)).then(function(response) {
        if (response) {
          return response;
        }
        throw Error('The cached response that was expected is missing.');
      });
    }).catch(function(e) {
      // Fall back to just fetch()ing the request if some unexpected error
      // prevented the cached response from being valid.
      console.warn('Couldn\'t serve response for "%s" from cache: %O', event.request.url, e);
      return fetch(event.request);
    })
  );
}

} });



Probably same problem as #114 but it had no code samples or descriptions, so I figured I'd make a new one.
achelimed commented 6 years ago

:+1: I have the same problem with : webpack version: 3.7.1

sw-precache-webpack-plugin version: 0.11.4

Environment: Linux 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt11-1+deb8u5 (2015-10-09) x86_64 GNU/Linux

Browser: Chrome Version 62.0.3202.89 (Official Build) (64-bit)

JustFly1984 commented 6 years ago

I got the same issue, with Chrome's Canary Lighthouse audit.

On other side with standard chrome I do not have this errors, and my app installing to Android desktop as usual, so I have pretty high degree of confidence that it is a Chrome's bug.

matheusgrieger commented 6 years ago

Testing on Lighthouse keeps pointing to the same error, "does not respond with 200 when offline" and "user will not be prompted to install web app". But after deployment and testing on Android, it does ask to install (only once, as stated in Chrome docs), but it indeed doesn't work offline. Not sure if it's a Chrome or ServiceWorker bug.

luaz commented 6 years ago

If "start_url": "/", I usually just need use following code in service-worker.js to pass the lighthouse requirement of Failures: Service worker does not successfully server the manifest's start_url

self.addEventListener('install', function(event) {
  const CACHE_NAME = 'startup-v2';
  console.log('install: ' + CACHE_NAME);
  const urls = [
    '/'
  ];
  event.waitUntil(
    caches.open(CACHE_NAME).then(function(cache) {
      return cache.addAll(urls);
    })
  );
});

How do I achieve the above with SW Precache Webpack Plugin? (since service-worker.js is generated by the plugin)

JustFly1984 commented 6 years ago

It is Lighthouse bug in Canary Chrome. If you test with standard chrome, you'll see that it is not failing there, and if you test it on real android device, you'll see that it behaves correct.

2018-02-04 17:45 GMT+08:00 luaz notifications@github.com:

If "start_url": "/", I usually just need use following code in service-worker.js to pass the lighthouse requirement of Failures: Service worker does not successfully server the manifest's start_url

self.addEventListener('install', function(event) { const CACHE_NAME = 'startup-v2'; console.log('install: ' + CACHE_NAME); const urls = [ '/' ]; event.waitUntil( caches.open(CACHE_NAME).then(function(cache) { return cache.addAll(urls); }) ); });

How do I achieve the above with SW Precache Webpack Plugin? (since service-worker.js is generated by the plugin)

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/goldhand/sw-precache-webpack-plugin/issues/125#issuecomment-362894408, or mute the thread https://github.com/notifications/unsubscribe-auth/ACJseW3o3dFqoMIp6egz2clH0OW_QbIWks5tRXw_gaJpZM4Qp0ri .

luaz commented 6 years ago

I test it on Chrome 64 on Linux and is still getting the warning (stable, not canary).

The warning aside, how to add external assets (non-webpack assets) or urls to be cached by sw-precache-webpack-plugin? I am pretty new to PWA, so I might get the concept wrong here.

I use the following configuration as it will cache all js, css and html generated in webpack. What should I do if I want the service worker to cache some external resources (e.g. "/", or "/app" or "/data.json") which is not part of this webpack?

    new SWPrecacheWebpackPlugin({
      cacheId: 'test',
      filename: 'service-worker.js',
      staticFileGlobs: ['dist/**/*.{js,css,html}', '/'],
      // minify: true,
      stripPrefix: 'dist/'
    })
matheusgrieger commented 6 years ago

@JustFly1984 no, not really. I never tested on Canary or Chromium, always on stable Chrome releases. All stated problems are originated from it. Also, as I said, it does prompt the user to install the PWA, but it doesn't work offline.

JustFly1984 commented 6 years ago

Oh, my stable chrome is 63 version. Can you confirm if you have new option SEO in your Audit LightHouse, if i you have SEO, so it mean this bug went unfixed from Canary to Stable.

matheusgrieger commented 6 years ago

My Chrome stable version is 64.0.3282.119, though I don't see the SEO option. Yet, here are the results I have when running the audits:

image

Remember, as I said, the user is prompted to install the web app, it just doesn't work offline. My start_url option is set to the full website URL instead of a slash ("/").

darkiron commented 6 years ago

Have same issue

a526672351 commented 5 years ago

Have same issue