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

Large Cache Storage size #131

Closed jabacchetta closed 6 years ago

jabacchetta commented 6 years ago

webpack version: 3.x

sw-precache-webpack-plugin version: 0.11.x

Please tell us about your environment: OSX 10.x

Browser: Chrome 63

Question:

Can someone help me understand why my Cache Storage is so large? I've got a bundle that is about 1 MB in size, along with a few images that are around 35 KB that appear to be getting cached. Yet, my initial Cache Storage is showing as 6.9 MB.

Even stranger, on subsequent visits to the same page, the Cache Storage grows multiple times what it originally was... at times going over 200 MB from one page visit.

I am using Next.js with prefetching, and as I understand it these prefetched pages will be cached on subsequent visits even when not navigating away from the homepage, but going from 7 MB to 42 MB (or higher) seems extreme.

screen shot 2018-01-05 at 6 43 19 pm screen shot 2018-01-05 at 6 46 12 pm

First Visit

This is right when the service worker is installed.

screen shot 2018-01-05 at 6 40 04 pm screen shot 2018-01-05 at 6 40 39 pm

Second Visit

This is after closing out the browser entirely and then going back to the same homepage.

screen shot 2018-01-05 at 7 07 41 pm screen shot 2018-01-05 at 7 07 46 pm

Production

Here's a production version, after many visits which is when it was brought to my attention.

another

jeffposnick commented 6 years ago

FYI, there's a decent chance that this is due to some of your cached responses being opaque (i.e. cross-origin, from an origin that doesn't support CORS).

See https://bugs.chromium.org/p/chromium/issues/detail?id=796060#c17 for details as to why they take more up more storage quota than you'd otherwise expect.

jabacchetta commented 6 years ago

I was able to narrow this down to the issue being with the runtimeCaching option, along with ANY request.

Whether it was a request for Google Fonts or a request from our own API. A 3 KB JSON file turned into 8 MB of cache

One other interesting thing to note. It's also cacheing my requests that have not been converted over to the Fetch API yet, where I'm using the axios package (XHR API). From my understanding the Service Worker doesn't support XHR, and therefore shouldn't cache it at all, right?

Regardless of the XHR issue, though, the 8MB cache was from a request made with the FETCH API... with all XHR turned off.

So I suppose I have two questions at this point.

  1. Why is a 3KB JSON file requested with the Fetch API being stored as 8 MB (at least according to DevTools).
  2. Why are XHR requests still working in the cache?
jeffposnick commented 6 years ago
  1. When opaque responses are cached it, the amount of quota usage goes up by around ~7-8mb, regardless of the size of the underlying resource. (The rationale for this behavior is explained here.)
  2. Any outgoing HTTP request made from a web page that is controlled by a service worker, regardless of whether that request is originated from fetch(), XHR, or by setting the src or href attributes on a DOM element, will trigger the fetch handler of the service worker. The idea that the "service worker doesn't support XHR" is true in the sense that you can't use XHR from within the service worker itself (i.e. from within the code that's generated by this plugin). But you're using XHR from your web page, and the service worker can and does intercept it.
jabacchetta commented 6 years ago

Thanks for the clarification, @jeffposnick. Our API is CORS-enabled, which would eliminate the possibility of it being caused by an opaque response, right?

jeffposnick commented 6 years ago

It's definitely possible to make outgoing HTTP requests that don't use CORS, and therefore result in an opaque response, even if the remote server does support CORS. I'm not sure how the axios library works, but everything you're describing sounds like the expected behavior that you'd get if you were caching opaque responses, so I'd have to assume that something is causing your requests/responses to not use CORS.

jabacchetta commented 6 years ago

Finally had some time to experiment some more with this today. I created a bare bones repo here.

I eliminated Next.js and axios from the equation. Using the native fetch api.

Turns out it's the images, but I still can't figure out why.

Here's the demo page with 330 KB worth of images being cached as 60 MB:

screen shot 2018-01-25 at 7 02 23 pm

Here's one of the requests, showing access-control-allow-origin:* in the response header.

screen shot 2018-01-25 at 7 13 16 pm
jeffposnick commented 6 years ago

An <img> tag won't trigger a CORS request for an image by default. So even though your server supports CORS, the browser is going to automatically make a request that leads to an opaque response.

You can use <img crossorigin="anonymous" src="..."> if you want to trigger a CORS request using an <img> tag.

There are more details at https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes

jabacchetta commented 6 years ago

@jeffposnick That solved it, thanks!

I'm kind of surprised that I haven't seen this mentioned anywhere yet while reading up on service workers. Not sure if this should have been obvious.

Would a PR to the readme be worthwhile here?