Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unexpected token < error after redeploy (InjectManifest Mode) #2053

Closed
sourybunny opened this issue May 7, 2019 · 15 comments
Closed

Unexpected token < error after redeploy (InjectManifest Mode) #2053

sourybunny opened this issue May 7, 2019 · 15 comments
Labels
Needs More Info Waiting on additional information from the community.

Comments

@sourybunny
Copy link

sourybunny commented May 7, 2019

Library Affected:
"@vue/cli-plugin-pwa": "^3.6.0",

Browser & Platform:
Google Chrome for Android

Issue or Feature Request Description:
I bundle my website/PWA, where I use a couple of Web Components, using the workbox-webpack-plugin.

Everything works smooth and well till I redeploy the project (hosted on Netlify).
On my phone/desktop, if I clean my cache and access the website/pwa, all good, worker works correctly.
Later on, once I release a new version, where I did several changes, and deploy it to the hosting and if I try to access the website/pwa , then I'll face the following error:
Uncaught SyntaxError: Unexpected token <

On checking network requests, I see that a request is made to an older app.[hash].js file which belonged to previous deploy. It is unable to find this file as it is getting deleted when new sw is activated.

const VuetifyLoaderPlugin = require('vuetify-loader/lib/plugin') module.exports = { configureWebpack: { plugins: [ new VuetifyLoaderPlugin() ] }, devServer: { disableHostCheck: true, }, lintOnSave: false, pwa: { workboxPluginMode: 'InjectManifest', workboxOptions: { swSrc: 'src/service-worker.js' } } }

A similar issue is raised #1528 , #1783

Please help.

@sourybunny sourybunny changed the title Unexpected token < after redeploy Unexpected token < error after redeploy (InjectManifest Mode) May 8, 2019
@sourybunny
Copy link
Author

sourybunny commented May 8, 2019

First deploy-

previous deploy

latest deploy- after service worker activation (on reloading the page)

current deploy

Why is it requesting older files while the hash for app.[hash].js has changed after latest deploy. This is breaking the app.

network request

sw.js file

Service-worker.js

index.html referencing older app.js file
index.html

@jeffposnick
Copy link
Contributor

It sounds like you're running into described in this section of the docs:

Note: If your web app lazy-loads resources that are uniquely versioned with, e.g., hashes in their URLs, it's recommended that you avoid using skip waiting. Enabling it could lead to failures when lazily-loading URLs that were previously precached and were purged during an updated service worker's activation.

Can you make sure that you're not unconditionally calling skipWaiting()? The best way to handle this is to follow a recipe like this, and call skipWaiting() when a user opt-ins, immediately followed by a page refresh once the new service worker activates.

@jeffposnick jeffposnick added the Needs More Info Waiting on additional information from the community. label May 14, 2019
@sourybunny
Copy link
Author

sourybunny commented May 15, 2019

Hey @jeffposnick , thanks for the resources, I've gone through them and looks like "refreshing" is a complicated thing to do.

import { register } from 'register-service-worker'

if (process.env.NODE_ENV === 'production') {
  register(`${process.env.BASE_URL}service-worker.js`, {
    ready () {
      console.log(
        'App is being served from cache by a service worker.\n' +
        'For more details, visit https://goo.gl/AFskqB'
      )
    },
    registered () {
      console.log('Service worker has been registered.')
    },
    cached () {
      console.log('Content has been cached for offline use.')
    },
    updatefound () {
      console.log('New content is downloading.[update found]')
    },
    updated (registration) {
      console.log('New content is available; please refresh.');
      let worker = registration.waiting
      Events.$emit("showStatus", {text: "New Content is available. Please Reload the page.",timeout: 0})
      
      worker.postMessage({action: 'skipWaiting'})
    },
    offline () {
      console.log('No internet connection found. App is running in offline mode.')
      Events.$emit("showStatus", {text: "You are viewing content offline."})
    },
    error (error) {
      console.error('Error during service worker registration:', error)
    }
  })
  }

We are using vue-cli3-pwa-plugin and this is the boilerplate kind of thing for registration code.
As you can see, I'm sending the postMessage after the update( checking if registration.waiting) which then does skipWaiting

self.addEventListener("message", (e)=>{
  if (e.data.action=='skipWaiting') {
    self.skipWaiting()
  }
})

This is an approach that is suggested by workbox too. But it just breaks my app when I reload the page after a new service worker is activated, since it tries to find an old cached html file that is not updated well in sw activation phase. The app breaks because it still uses an old html file from the cache that has links to old js files that can't be found anymore.
What am I missing here? Is there a way to use workbox advanced recipe for refreshing using this vue-pwa boilerplate?

In another case, I just deleted the code for skipWaiting and tried to activate new sw after closing all the tabs and opening in new tab. While activation has been successful, after a page reload it's still fetching the old html file and js files that do not exist anymore.

Can you explain me the order of these events for a bug free pwa while using workbox webpack plugin:

  1. A new code change happens, we redeploy.
    1.Registration and Installation happen first.
    2. Cache cleanup and new precache manifest are generated during activation which should remove
    my old html and js and replace with newer ones.
    3. If we call skipWaiting it activates immediately so chances are that it breaks the existing clients
    using old version
    4. If I don't deal with skipWaiting, then I need to close all tabs and reopen in new tab to see it
    activated.
    5. We refresh the page to see the new content.

This is what I understood about the life cycle, correct me if I'm wrong I would love to understand it better. But I would be glad if you could help me know why my app is using old html file after a page refresh while sw has been activated correctly.

Also, my issue is more related to #1528 and looks like it is solved in workboxV4. But I'm using vue-cli-pwa-plugin which also has workbox-webpack-plugin as dependency. While I'm trying to update the pwa-plugin to latest 3.7.0 which possibly has updated workboxv4, it is still showing me workbox-3.6.3 in package-lock.json.
Any idea how this works?

Thanks

@philipwalton
Copy link
Member

It's possible that your service worker is still waiting to activate after the update is found and even after you refresh. Sometimes refreshing the page won't skip the waiting phase if there are other tasks pending or other clients still open.

You could try using workbox-window to register your service worker, as that library adds some nice logging beyond what register-service-worker has. It'll log a warning to the console if the installing service worker gets stuck in the waiting phase.

@sourybunny
Copy link
Author

sourybunny commented May 16, 2019

Hey @philipwalton && @jeffposnick I have tested it thoroughly and I don't have any issues with service worker activation, It works very fine, I send a refresh prompt while new sw is in waiting phase and on accepting it, it skips waiting and gets activated, I even tested it by closing all tabs and reopening to get it activated and I can see all that happening correctly in dev tools.

There is no issue I see with how my service worker is activating, it works great. but the problem could have been during the cache cleanup phase, something is wrong with it.

Also I see that there is precache-manifest-temp along with pecache-manifest cache in cache API which could be messing up things while I'm still unsure about it.
I've documented the whole flow from first deploy onwards by un-registering existing sw's and cleaning up all caches.

  1. First fresh deploy:

first deploy

  1. First deploy precache-temp:

deploy-1-emptytemp

  1. First deploy precache:

app 9d909086 js-cached

  1. I did some code changes to my app and redeloyed now

redeploy-installing

  1. Service worker in waiting state

waitingsw

  1. precache-temp while still waiting:

waiting-temp

  1. precache while still waiting:

redeploy-precache-waiting

  1. Right after I click Refresh button which does skipWaiting and activates new sw.

right-after-skipwaiting

  1. I do a window.location.reload() after clicking refresh.

breaks-after-reload

  1. The app breaks trying to fetch old app file which already got deleted. But I see my new app.hash.js file cached in precache and precahe-temp empty again now. My index.html still points to old app.hash.js file. Why is this happening?

app 7f29243e js-cached

If I add code to delete caches in registration phase like this:

 registered (registration) {
      console.log('Service worker has been registered.')
      caches.keys().then(cacheNames => {
        cacheNames.forEach(cacheName => {
          caches.delete(cacheName);
        });
      });
      registration.update();
    },

only then does it fetch my new html and app.js files otherwise if it's normal like this

registered (registration) {
      console.log('Service worker has been registered.')
      registration.update();
    },

it always tries to fetch old files. But I don't want to delete caches everytime during registration, this just means my caching is useless. Our app is in production already and redeploying has become a nightmare for us. Deleting the caches in registration is the only hack I found so far to not break the app while redeploying. May be you can help me understand why it works fine after I delete previous caches, I have no idea about it. Please help me know what I'm doing wrong here.

I would like you to take a look at #1803 #1528 #1783 I find them very much similar to this but I don't see any solution to them as well. Please help

A few other similar to this:

  1. completely-forcing-a-spa-refresh-ignoring-cache-service-workers-and-so-on
  2. problem with cache-headers max-age, follow the discussion
    Thanks

@jeffposnick
Copy link
Contributor

jeffposnick commented May 16, 2019

If you're specifically seeing issues that are tied to the -temp cache and cleanup, then the changes introduced in Workbox v4, detailed in #1793, might be relevant to you.

Migrating to the latest Workbox release is definitely worth trying here.

@sharanan-sarvsutra
Copy link

I am on v4 and facing similar issue (there are no temp caches). Have tried following (as suggested remedies), but to no avail:-

  1. Avoid using skipWaiting()
  2. If using skipWaiting() then use this recipe

All my hashed cache files use StaleWhileRevalidate strategy. Wondering if the cause of this issue could be a conflict between the strategy (staleWhileRevalidate) I am using and the way Workbox handles cache refresh? I could try NetworkFirst, but that'll go against my app's preferred behaviour. Seconding @sourybunny - deleting old caches on register is also not a solution.

Ensuring smooth redeployments is absolutely critical for a PWA to compete with Native apps. Arguably, native apps have improved a lot when it comes to managing user experience during app updates. I really think there has to be a better, more straightforward way to deal with this issue. The default doesn't seem to be working for me.

@sourybunny
Copy link
Author

@sharanan-sarvsutra
Glad that I found someone facing similar issue, but it's really disappointing that there is no solution to this issue so far. It works with networkFirst but that's not intended for caching resources. Deleting old caches just saved me from the redeploy bug. But I'm still looking for a solution to this. :(

@sharanan-sarvsutra
Copy link

sharanan-sarvsutra commented Jun 3, 2019

Just clarifying a gap in my understanding of workbox caching - I am using CRA (create react app)'s build tool that auto-hashes the build files. Unlike precached files, it seems files cached via a strategy (such as StaleWhileRevalidate) require manual refresh when modified. Workbox's waiting event callback seems like a good place to do this. Couldn't find a plugin to avoid doing this from scratch.

A few observations:-

  1. 'waiting' callback reliably fires each time service worker updates
  2. 'skipWaiting' handler is called immediately after
  3. 'controlling' handler should be next if service worker updates properly - this is not working in my case, still debugging

Interestingly, I am no longer getting the 'Unexpected token <' error because workbox is able to handle updates to the cached files. Although, workbox is also keeping the old files (which should be removed whenever sw update is available), but it has now stopped referring to old files (the cause of 'Unexpected token <' error).

I simply followed the advanced recipe as prescribed on this page, with the prompt removed. Apparently removing the prompt had an effect on workbox properly loading the updates.

@sharanan-sarvsutra
Copy link

Sharing an update - redeployments are not breaking any service worker logic now. Followed the advanced recipe as suggested in Workbox guides (minus the prompts) + manually handled cache refresh inside workbox's 'waiting' handler.

@sourybunny
Copy link
Author

Hey @sharanan-sarvsutra , can you share a gist/code of how you are doing the manual cache refresh and service worker updating logic.

Thanks.

@sharanan-sarvsutra
Copy link

workbox sw config:-
self.addEventListener('message', (event) => { if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting(); } });

to refresh cache:-
import { Workbox } from 'workbox-window'; export default function workboxRefresh() { if ('serviceWorker' in navigator) { const wb = new Workbox('/service-worker.js'); wb.addEventListener('waiting', async (event) => { await caches.delete('js-cache'); await caches.delete('css-cache'); window.location.reload(); wb.addEventListener('controlling', (event) => { window.location.reload(); }); wb.messageSW({ type: 'SKIP_WAITING' }); }); wb.register(); } }

@alexeigs
Copy link

alexeigs commented Nov 30, 2019

@jeffposnick @sharanan-sarvsutra
I managed to make it work, currently with the following process (first step two steps needed for iOS)

  1. Listening to a server version string
  2. Trigger a window.location.reload() if server version changes
  3. New SW is received and registered
  4. In the updated hook, I registration.waiting.postMessage({ type: 'SKIP_WAITING'}) and window.location.reload() again
  5. Everything works fine

But I wonder, Is there another way than reloading again after the SW updated in order to prevent the error occurring when precached resources mismatch due to hashes in their url.

  • First, what does the window.location.reload() after the SW update actually do in this case, does it update those precached urls?
  • Could we manually clean some cache that's relevant instead of that other reload?
  • What are the advantages/disadvantages of the config setting ignoreURLParametersMatching: [/./] instead in order to prevent the very same error without doing either of the two measures above?

Thanks for some clarification.

@rosslavery
Copy link

@sourybunny were you able to find a solution here? We're encountering identical behaviour here.

@vksco
Copy link

vksco commented Aug 10, 2023

This issue still exists,
after debugging for hours i find out that the largets file like app.js and chunk.vendor.js not caching in cache store. Thats why server is throwing 404 for this files because the default caching file is 2MB.

So the solution is to add maximumFileSizeToCacheInBytes: Bytes under workboxOptions .
this work for both GenerateSW and InjectManifest modes.

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  pwa: {
    workboxPluginMode: "GenerateSW",
    workboxOptions: {
      exclude: ['.htaccess'],
      maximumFileSizeToCacheInBytes: 5000000 // 5MB
    }
  }
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs More Info Waiting on additional information from the community.
Projects
None yet
Development

No branches or pull requests

7 participants