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

Precaching bug with adapter-static and load() functions #98

Closed
WhyAsh5114 opened this issue Dec 31, 2024 · 54 comments · Fixed by #97
Closed

Precaching bug with adapter-static and load() functions #98

WhyAsh5114 opened this issue Dec 31, 2024 · 54 comments · Fixed by #97
Labels
bug Something isn't working

Comments

@WhyAsh5114
Copy link

The main problem

There's a specific bug when using load() functions along with prerendering and the static-adapter that I spent
hours trying to fix, it occurs primarily due to @vite-pwa/sveltekit not being able to figure out how to cache
the returned data from the load(), typically stored in:

.svelte-kit/output/prerendered/dependencies/{page-route}/__data.json

This causes the app to break when offline and navigating to a page that depends on that data.

The hacky fix

  1. To fix this, you need to manipulate the PWA config in vite.config.ts and the custom service-worker file.
    After setting up the basic PWA, I added the following to your vite.config.ts:
export default defineConfig({
  plugins: [
    sveltekit(),
    SvelteKitPWA({
      /* other config */
      injectManifest: {
        globPatterns: [
          'client/**/*.{js,css,ico,png,svg,webp,webmanifest,ttf,woff,woff2}',
          'prerendered/**/*.{html,json}'
        ],
      }
    })
  ],
});
  1. Notice the prerendered/**/*.{html,json} in the globPatterns array, this tells vite-plugin-pwa to cache the
    prerendered data (typically in the form of __data.json files) so that the app can work offline. This should have
    been enough, but for some reason you need this weird hack in your service-worker.ts:
/// <reference lib="WebWorker" />
/// <reference types="@sveltejs/kit" />

import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
declare let self: ServiceWorkerGlobalScope;

const manifest = self.__WB_MANIFEST;

manifest.forEach((entry) => {
	if (typeof entry === 'object' && entry.url.endsWith('__data.json')) {
		entry.url = entry.url.replace(/^prerendered\/dependencies\//, '');
	}
});

cleanupOutdatedCaches();
precacheAndRoute(manifest, { ignoreURLParametersMatching: [/.*/] });

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

registerRoute(/.*/, new StaleWhileRevalidate());

This hack is necessary because the __data.json files are stored in the prerendered/dependencies/ folder, and the
service worker's default behaviour incorrectly configures their URLs. This hack manually alters the URLs so that the
service worker can cache them correctly.

@userquin userquin added the bug Something isn't working label Dec 31, 2024
@userquin
Copy link
Member

can you provide a minimal reproduction and so I can check the kit folder? Right now I'm fixing similar problem for Kit SPA mode...

Or just updload an screenshot like this one (this is for SPA using static adatper):
imagen

@userquin
Copy link
Member

I'm going to update the buildGlobPatterns function to include json and js files and removing also 'prerendered/dependencies/' prefix in the manifest transform:

function buildGlobPatterns(globPatterns?: string[]) {
  if (globPatterns) {
    if (!globPatterns.some(g => g.startsWith('prerendered/')))
      globPatterns.push('prerendered/**/*.{html,json,js}')

    if (!globPatterns.some(g => g.startsWith('client/')))
      globPatterns.push('client/**/*.{js,css,ico,png,svg,webp,webmanifest}')

    if (!globPatterns.some(g => g.includes('webmanifest')))
      globPatterns.push('client/*.webmanifest')

    return globPatterns
  }

  return ['client/**/*.{js,css,ico,png,svg,webp,webmanifest}', 'prerendered/**/*.{html,json,js}']
}

@WhyAsh5114
Copy link
Author

Yep, that should fix it

@WhyAsh5114
Copy link
Author

Also, I had another doubt. When using this strategy, the $offlineReady store returned from the useRegisterSW() function is never true.

Am I misunderstanding something? How would I know if all the assets have been precached on the frontend?

@userquin
Copy link
Member

$offlineReady will be called/activated only once, when the service worker is installed and activated first time.

Uhmmm, looks like the .svelte-kit/output/prerendered/dependencies/_app/env.js file is added after PWA plugin builds the service worker, and so there is no way to fix this: I'm going to update #97 with previous fix, can you test it in your local with this dependency https://pkg.pr.new/@vite-pwa/sveltekit@488aa8c ?

@WhyAsh5114
Copy link
Author

sure, I'll let you know

@userquin
Copy link
Member

@WhyAsh5114
Copy link
Author

not really, I'm using adapter-static, with export const prerender = true in root layout.ts

@userquin
Copy link
Member

Am I misunderstanding something? How would I know if all the assets have been precached on the frontend?

You need to check it in the browser devtools: Application > Cache Storage

image

not really, I'm using adapter-static, with export const prerender = true in root layout.ts

Can you provide a minimal reproduction?

@WhyAsh5114
Copy link
Author

WhyAsh5114 commented Dec 31, 2024

Uhmmm, looks like the .svelte-kit/output/prerendered/dependencies/_app/env.js file

Also, the env.js file isn't really of interest, the __data.json files are though IMO, since when I navigate using button clicks on the app, the SW tries to fetch that page's __data.json if it has a load() (or maybe $props()/export let data).

Ohkk, I'll make a minimal repro and link it here.

@userquin
Copy link
Member

userquin commented Dec 31, 2024

Since you're using static adapter (SPA requires it), maybe moving the buildEnd hook from pre to post works, I need to do some tests.

I have no idea why kit not applying adapter logic like normal build, looks like prerendered/dependencies files built without awaiting... (maybe a bug or missing await somewhere).

@WhyAsh5114
Copy link
Author

WhyAsh5114 commented Dec 31, 2024

Minimal repro: https://github.com/WhyAsh5114/prerendered-pwa

See the service-worker.ts file, if you uncomment the hacky fix, only then the SW registers properly.

@userquin
Copy link
Member

Thx, looks like we need to move the kit pwa build plugin from 'pre' to 'post' (register it after kit), this way we've now prerendered/dependencies/_app/env.js in the sw precache manifest.

@userquin
Copy link
Member

There is a problem, now the service worker being generated in the kit output folder, but the adapter will not copy the service worker since it is being generated after the adapter runs (the adapter will copy the client folder without the sw).

@WhyAsh5114
Copy link
Author

hmm, but again, why would we need the env.js file? does it work if we just get __data.json?

Or are those files also generated after the PWA plugin builds?

@WhyAsh5114
Copy link
Author

I'm not even sure it contains anything useful, in my minimal repro it's just this:

export const env={}

@userquin
Copy link
Member

userquin commented Dec 31, 2024

I'm going to check your reproduction, looks like the $service-worker not including the _app/env.js file in the build/files:

image

@WhyAsh5114
Copy link
Author

Yes, my globPattern doesn't include JS from the prerendered/client dir, only html,json

                                globPatterns: [
					'client/**/*.{js,css,ico,png,svg,webp,webmanifest,ttf,woff,woff2}',
					'prerendered/**/*.{html,json}'
				]

@userquin
Copy link
Member

ok, the fix will work for __data.json files, kit is generating them since we've the entries in the sw precache manifest.

@userquin
Copy link
Member

And working offline:

image

@WhyAsh5114
Copy link
Author

Yep, perfect. I expected this by default, maybe we should also update the docs for this, let me know if I can help!

@userquin
Copy link
Member

I'm going to release a patch version, in the meantime you can check it in your local with this dependency: https://pkg.pr.new/@vite-pwa/sveltekit@a7c0605 (I guess once PR merged the pkg.pr.new is removed)

@userquin
Copy link
Member

Yep, perfect. I expected this by default, maybe we should also update the docs for this, let me know if I can help!

Checking the docs...

@WhyAsh5114
Copy link
Author

I'm going to release a patch version, in the meantime you can check it in your local with this dependency: https://pkg.pr.new/@vite-pwa/sveltekit@a7c0605 (I guess once PR merged the pkg.pr.new is removed)

I will do that once I get back, out rn

@userquin
Copy link
Member

vite-pwa/docs#172

@WhyAsh5114
Copy link
Author

I'm going to release a patch version, in the meantime you can check it in your local with this dependency: https://pkg.pr.new/@vite-pwa/sveltekit@a7c0605 (I guess once PR merged the pkg.pr.new is removed)

Umm, I installed this and remove my custom URL manipulation. It doesn't seem to work, did you push the patch?
image

And the docs change looks good.

@userquin
Copy link
Member

userquin commented Dec 31, 2024

Remove the storage including the service worker and reload the page, you may have old sw (you should use a virtual, vanillajs or sveltekit for page reload): check if the old service worker is in skip waiting state (yellow dot in devtools)

@WhyAsh5114
Copy link
Author

WhyAsh5114 commented Dec 31, 2024

Yes, I did try that, clear storage, empty cache and hard reload. I was still getting this error. Same minimal repro with following changes:

  1. New package
  2. vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { SvelteKitPWA } from '@vite-pwa/sveltekit';

export default defineConfig({
	plugins: [
		sveltekit(),
		SvelteKitPWA({
			manifestFilename: 'manifest.webmanifest',
			strategies: 'injectManifest',
			filename: 'service-worker.ts',
			injectManifest: {
				globPatterns: [
					'client/**/*.{js,css,ico,png,svg,webp,webmanifest,ttf,woff,woff2}',
					'prerendered/**/*.{html,json}'
				]
			}
		})
	]
});
  1. service-worker.ts
/// <reference lib="WebWorker" />
/// <reference types="@sveltejs/kit" />

import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching';
declare let self: ServiceWorkerGlobalScope;

cleanupOutdatedCaches();
precacheAndRoute(self.__WB_MANIFEST, { ignoreURLParametersMatching: [/.*/] });

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

@userquin
Copy link
Member

userquin commented Dec 31, 2024

Try adding includeVersionFile: true to kit pwa options: you can also remove injectManifest options, looks like your options are the default ones

@WhyAsh5114
Copy link
Author

WhyAsh5114 commented Dec 31, 2024

Still the same, it only happens when I go offline. The cache entry is there though, so is the version.json
image

@userquin
Copy link
Member

userquin commented Dec 31, 2024

Check if you have checked Disable cache in network tab, uncheck it (disabling cache will bypass the sw):

imagen

@WhyAsh5114
Copy link
Author

And working offline:

image

Try doing it in this way:

  1. npm run bulid && npm run preview
  2. go to localhost:4173
  3. now go offline and try to click on the link

@WhyAsh5114
Copy link
Author

Check if you have checked Disable cache in network tab, uncheck it:

imagen

it is unchecked

@userquin
Copy link
Member

imagen

@WhyAsh5114
Copy link
Author

You are getting that after removing the url manipulation from the service worker?
If so, I'm messing something up on my side, this package was the one I was supposed to install right?

		"@vite-pwa/sveltekit": "https://pkg.pr.new/@vite-pwa/sveltekit@a7c0605",

@userquin
Copy link
Member

wait, I'm using local tgz file, checking changes...

@userquin
Copy link
Member

In the meantime add the pwa icons...

@userquin
Copy link
Member

Use this link https://pkg.pr.new/@vite-pwa/sveltekit@97

@WhyAsh5114
Copy link
Author

Ohk, need to head out for a bit, will get back in an hour or two and add the icons

@userquin
Copy link
Member

imagen

imagen

@userquin
Copy link
Member

Oh, we need font files (ttf,woff*), don't remove first globPattern.

@userquin
Copy link
Member

@WhyAsh5114 kit pwa v0.6.7 released

@WhyAsh5114
Copy link
Author

Hey, it works!

I forgot to use registerRoute() on the paths so the sw wasn't participating in the fetch at all. My bad.

Once I added that, with v0.6.7, it works properly. Thank you very much!

@WhyAsh5114
Copy link
Author

Okay, maybe its not fully done yet. There's still some weird behavior. I've update the repro to include it. Steps to create an error:

  1. npm run build && npm run preview
  2. In a new private tab, cleared everything, empty cache and hard reload
  3. Go offline
  4. Stay on the root page, don't go to /dependent at all (important)
  5. Hover on the link to go to /dependent, there'll be errors in the console
    image
  6. Even though the __data.json is cached, you'll still get this. But for some reason, manually changing the URL to localhost:4173/dependent will work without errors, and you can then go back and forth.
  7. But starting from root (localhost:4173), and immediately going offline, and then trying to go to /dependent with the URL breaks it

@WhyAsh5114
Copy link
Author

For some reason, hovering (and clicking) on the link element, the request doesn't go through the service-worker.

@userquin userquin reopened this Dec 31, 2024
@userquin
Copy link
Member

userquin commented Dec 31, 2024

Check https://github.com/WhyAsh5114/prerendered-pwa/pull/1 (about prefetching when hovering the links no idea what's happening, I get some errors but now it should be fixed in the PR => read my comment in the PR).

The PR including pwa icons (from kit pwa example) and removed manifest.webamanifest from static folder addding the content to pwa manifest option).

@WhyAsh5114
Copy link
Author

@userquin, I did merge it and tried to run it again. It still errors out when doing the steps mentioned above. But yeah, this issue has been fixed, its something I need to figure out on my own with workbox docs and all.

@userquin
Copy link
Member

userquin commented Dec 31, 2024

@WhyAsh5114
Copy link
Author

yep, tried that, still issues

I'll look into the mentioned issue tomorrow, a bit tired rn

@WhyAsh5114
Copy link
Author

@userquin Okay, I finally fixed it. All we just needed to do was:

self.addEventListener('activate', (event) => {
	event.waitUntil(self.clients.claim());
	console.log('Service worker activated');
});

The service worker wasn't taking control on the initial page load, and therefore unable to serve cache resulting in errors as the requests were not being passed to it anyways.

There was no need to modify the registration logic in the root +layout.svelte or even include the version file in the kit config. Updated the repro to reflect all this with default config and still working.

@WhyAsh5114
Copy link
Author

In brief:
The install event is the first event a service worker gets, and it only happens once.
A promise passed to installEvent.waitUntil() signals the duration and success or failure of your install.
A service worker won't receive events like fetch and push until it successfully finishes installing and becomes "active".
By default, a page's fetches won't go through a service worker unless the page request itself went through a service worker. So you'll need to refresh the page to see the effects of the service worker.
clients.claim() can override this default, and take control of non-controlled pages.

https://web.dev/articles/service-worker-lifecycle

@userquin
Copy link
Member

userquin commented Jan 1, 2025

Happy new year 🎉

You're using auto update, the code should be:

self.skipWaiting()
self.addEventListener('activate', (event) => {
	event.waitUntil(self.clients.claim());
	console.log('Service worker activated');
});

/* THIS IS USED FOR PROMPT FOR UPDATE: CLIENTS WILL NOT EMIT THIS EVENT
self.addEventListener('message', (event) => {
	if (event.data && event.data.type === 'SKIP_WAITING') self.skipWaiting();
});
*/

@userquin
Copy link
Member

userquin commented Jan 1, 2025

About the logic in the layout, you should avoid calling r.update() when offline, the virtual callback you're using is deprecated, use the new one with the sw url: when your code base grows, if you call update while updating you may have some problems, increase the timeout in production, 30 minutes or even more should be fine.

@WhyAsh5114
Copy link
Author

Happy new year to you too! Thank you for the fixes :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants