GoogleChromeLabs / sw-precache

[Deprecated] A node module to generate service worker code that will precache specific resources so they work offline.

Home Page:https://developers.google.com/web/tools/workbox/guides/migrations/migrate-from-sw

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

MIME type ('text/html') is not executable, and strict MIME type checking is enabled.

brotzky opened this issue · comments

This is the issue I'm getting

  • Deploy files to to server
  • Service Worker caches all the files
  • Deploy updated files to server
  • Get this error on refresh after deploying updated files: Refused to execute script from 'https:/www.example.com/app.jf813asdfd3t.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.

Doing a hard refresh (CMD + SHIFT + R) skips the service worker cache and loads the newly deployed files without any issues. And then doing a regular refresh causes the error to come back.

My sw.js is not being cached and have nginx setup.

Deploying an update and refreshing pulls files from service worker, but they have the incorrect content-type.
screen shot 2016-09-13 at 9 02 54 am

Doing a hard refresh solves the issue
screen shot 2016-09-13 at 9 03 13 am

As you can see form the images, the files are being pulled from the same location, except that the service worker is pulling the incorrect content. When I go into the Chrome Develop Tool network tab and "preview" the content the service worker JS file contains only HTML and the hard reloaded one is actual JS.

I am using React with React Router.
This is my sw-precache webpack config:

new SWPrecacheWebpackPlugin({
  cacheId: 'someSecretString',
  filename: 'sw.js',
  dontCacheBustUrlsMatching: [/./],
  staticFileGlobs: [path.join(__dirname, '../../build/') + '/**/*.{js,html,css,svg}'],
  stripPrefix: path.join(__dirname, '../../build/'),
  maximumFileSizeToCacheInBytes: 4194304,
  navigateFallback: 'index.html',
})

My initial guess is that it's an issue with nginx, but asking if you guys have any other guesses.

The code that's responsible for populating the cache on service worker installation is about as basic as it gets:

cache.add(new Request(cacheKey, {credentials: 'same-origin'}));

That uses the Cache Storage API to make a network request and use the response of that request to populate our cache.

Similarly, the code that's responsible for reading from the cache is just:

cache.match(urlsToCacheKeys.get(url));

I don't think there's much room there for sw-precache to mangle the responses in any way. That points to a configuration web server as being the source of the issue. It sounds like, for whatever reason, it's responding to the precaching requests with a text/html response, instead of the appropriate MIME type.

When I go into the Chrome Develop Tool network tab and "preview" the content the service worker JS file contains only HTML and the hard reloaded one is actual JS.

Can you explain this in more detail? What's the HTML that you see? It might give a clue as to what's going on. You can also examine the contents of a cached resource by doing the following in the DevTools console:

var url = '/path/to/resource.js';
fetch(url).then(r => r.text()).then(console.log);

Try running that with url set to one of the resources that should be JavaScript, and see if you're actually getting HTML back from the server and stored in the cache.

Can you explain this in more detail? What's the HTML that you see? It might give a clue as to what's going on. You can also examine the contents of a cached resource by doing the following in the DevTools console

The content is the root index.html that we serve to start our React app.

Try running that with url set to one of the resources that should be JavaScript, and see if you're actually getting HTML back from the server and stored in the cache.

Returns the correct JS content if the file exists, otherwise it returns the index.html content if the file (aka the path) doesn't exist.

It looks like what is happening is that the /login (index.html) is getting served from the service worker and doesn't get updated with the newest version. So when the user refreshes they get served the old index.html that is pointing to the old (deleted off the server) .js files. It then tries to find the deleted .js files and nginx is configured to serve index.html for any routes, including non-existent ones.

Serving /login from service worker contains deleted paths to old JS files, that get replaced with index.html resulting in incorrect MIME type.
screen shot 2016-09-13 at 11 12 40 am

Hard reload with a fresh (correct version of) /login has updated paths with no issues with MIME type since the relative paths within the index.html file exist on the server.
screen shot 2016-09-13 at 11 14 09 am

I'm going to review how nginx is setup and get back to you. Thanks for the help!

edit: I just checked the url fetch you suggested on the Cache Storage contents and it returns the correct content (JavaScript when fetching /vendor.e4cc7429b66a348613e7.js), for example).

I just checked /index.html to see if it contained the correct content, and it does. But for some reason service worker is serving the old version if /index.html when I hit /login

In the image, you can see /login coming from the Service Worker (incorrect version that is pointing to the wrong JS files) and at the bottom you can see I ran a fetch for /index.html which contains the correct version of the index.html... yet on refresh still serves the old one.
screen shot 2016-09-13 at 11 27 03 am

Be sure that the sw-precache build step is re-run each time you deploy your site, and that it's run after all your static file resource versioning takes place in your build. That will ensure that sw-precache picks up the versioned resource URL that it needs to fetch.

How I fixed it:

In the Webpack config I had to remove the navigateFallback as it was serving the old index.html file from the service worker:

new SWPrecacheWebpackPlugin({
  cacheId: 'someSecretString',
  filename: 'sw.js',
  dontCacheBustUrlsMatching: [/./],
  staticFileGlobs: [path.join(__dirname, '../../build/') + '/**/*.{js,html,css,svg}'],
  stripPrefix: path.join(__dirname, '../../build/'),
  maximumFileSizeToCacheInBytes: 4194304,
  navigateFallback: 'index.html', // <-- DELETED THIS LINE
})

Could it be that the index.html is one of the only files we have without a hash? it's not index.jf0913j1903.html, for example.

Now on every deploy there are no issues of serving the incorrect index.html file from the Service Worker since it's coming from the network now.

But now index.html isn't being served when offline and it has to be from the network every time. Not really a win. I added navigateFallback: 'index.html' to deal with React Router.

Be sure that the sw-precache build step is re-run each time you deploy your site, and that it's run after all your static file resource versioning takes place in your build. That will ensure that sw-precache picks up the versioned resource URL that it needs to fetch.

Yes, it is being re-run each time :)

Here's an example of what gets built:
screen shot 2016-09-13 at 11 42 18 am

If you have a web app that uses the History API to modify the effective page's URL, you're going to need to use navigateFallback. It handles navigation requests to any of those "fake" URLs by serving up your real index.html instead. So don't remove that.

I think what's happening is along the lines of what you said; you're using

dontCacheBustUrlsMatching: [/./]

and as a result, when the service worker tries to refetch index.html, it's not going to append any cache-busting parameters to the request. That's fine, if you're sure that index.html is served with HTTP caching disabled—which is a best practice when using versioned resource URLs, independent of service workers: https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=en#invalidating-and-updating-cached-responses

So my guess is that you may have HTTP caching enabled for index.html. You should disable it (ideally), or alternatively, configure dontCacheBustUrlsMatching so that it matches everything except index.html.

@jeffposnick your solution seems to work! I changed dontCacheBustUrlsMatching to match all files except html ones. Also, added navigateFallback: 'index.html' back in.

I'll keep testing and close this issue once I feel confident it's working correctly.
Thanks so much for the awesome support 🙏

React & React Router with dynamic route bundling (loading JS chunks based on route change)

Error:

  • Already have files on server
  • Deploy new files to server with new cache bust hash
  • sw.js adds all the new files to cache on first hit of page (while serving the page from Service Worker cached files)
  • Click to go to new route --> Error: Refused to execute script from 'https:/www.example.com/3.jf813asdfd3t.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.
  • CMD + R (refresh page) removes issue as correctly loading chunks from service worker

screen shot 2016-09-20 at 11 01 19 am copy

Because we're using React Router (Browser history API) the user's page doesn't refresh on route update. This means the files initially loaded and updated by SW are out of sync. This breaks any dynamic chunking ability without refreshing the page to load the correct JS.

Summary: Serving initial load from Service Worker, which has dynamic chunks of JS executed and saved in memory. But those dynamic routes don't exist anymore after Service Worker gets cache busted so updating route fails as it's pointing at files that don't exist anymore.

Really struggling to solve this one. 😩

This sounds like a case where skipWaiting() is getting in your way, and you'd be better off without out.

It should be easy to turn it off via an official option in the near future, but in the meantime, can you try making edits to your local node_modules/sw-precache/service-worker.tmpl file and commenting out the return self.skipWaiting() line? I just want to confirm that without skipWaiting(), your web app's on-demand loading pulls in what's expected.

The downside of removing skipWaiting() is that your newly deployed service worker code and caches won't become active until all existing tabs that have an older version of that service worker are closed, but that actually sounds like what you want in this case.

<!-- return self.skipWaiting(); -->

Solves the issue. Yes, that's the behaviour we'll have to adapt because of the nature of dynamic route chunking. Thanks again for the tip. Look forward to the next release now 👍

Did you actually do <!-- return self.skipWaiting(); -->? Because the file is JavaScript, and I would have thought that <!-- return self.skipWaiting(); --> would just lead to a syntax error, and you'd need // return self.skipWaiting();

I just want to confirm that you're not seeing, e.g., a failure to register the SW entirely due to a syntax error, which makes you think that the SW is behaving differently.

You're right, my bad. My editor marked the .tmpl file as HTML and auto generated that HTML comment style. When I ran build it must have silently failed because it didn't create a changed version of sw.js according to git and my build process didn't fail.

Side question, maybe for a seperate issue, but what's the best way to minify sw-precache-webpack output file? currently all sw.js content is not minified.

Okay, let me know if you see the behavior you expect when using a JavaScript comment.

I don't tend to minify the generated service worker file, because given identical input, I don't trust all minifiers to produce identical output each time they're run. Any changes to the output file is enough to trigger the service worker update flow. This isn't a big deal, since the install/activate handlers will be no-ops unless the list of cached files have actually changed, but it means there's the potential for extra code execution just as a side-effect of minifying.

If you do have a minifier that will always produce the same output given the same input, then there shouldn't be any concerns. I don't have a specific recommendation, though.

Working as expected, thanks again!

Okay, great. I'll prioritize getting #122 added as an official option.

FYI, release 4.1.0 of sw-precache includes official options for disabling skipWaiting() and clients.claim(): https://github.com/GoogleChrome/sw-precache/releases/tag/4.1.0

How to fix this error i am using ionic3 after run the ionic serve i got error?

Hi everybody!

I had the exact same problem but solved it differently. I'm sharing my solution to help everybody in the future.

So I'm using workbox precaching, but when updating the scripts and deploying to the server, I forgot to increase the revision number for index.html. Increasing it solved the problem instantly.

workbox.precaching.precacheAndRoute([
    { url: '/', revision: '2' },
    ...
]);

Hi @brotzky and @jeffposnick , i am facing same issue, but we are using Angular 5 + Jenkins with nginx server setup.

Can you tell me what all changes are required to solve issue in mine case?

Thanks and regards

I have faced with same this problem.
I think there are 2 options for that.
1 > I hope there is an option in workbox to config the mine-type, or callback or something to help me check the response before adding the cache
2 > In the file index.html add a script to check the cache in CacheStorge if there is any responses that has wrong mine-type then remove it and refresh your page.