Dynamic Pre-Cache Strategy0:00
Let's cache some stuff, again. What I want to focus on are the pre-cache URLs. Now, I know that we have hard-coded this, but think in terms of our applications. Our applications are anything but static. They are dynamic, and especially with Laravel 12, every starter kit includes VEET. Now, you don't have to use VEET, but I imagine most of us are, which means that the assets that VEET is going to build
but I imagine most of us are, which means that the assets that VEET is going to build is also dynamic. So it doesn't make a whole lot of sense for us to come in here and change these pre-cache URLs based upon whatever VEET decided to output as far as the names. We want our pre-cache URLs to be dynamic. So there are many ways that we can approach this. There is a plugin for VEET for service workers,
So there are many ways that we can approach this. There is a plugin for VEET for service workers, and we could use that. And where would the fun be in that? Now, in production, yes, absolutely. We would probably opt for something like that because that just makes our lives so much easier. But I'm also interested in looking at other options, other strategies. Like for example, one thing that we could do
other strategies. Like for example, one thing that we could do is essentially lift this URL list out of our service worker and put it somewhere within our application so that like an invocable controller could provide that so that our service worker could fetch that list of URLs and then pre-cache them. Or we could use Blade to output JavaScript, which would essentially be our service worker. I mean, that's kind of interesting,
which would essentially be our service worker. I mean, that's kind of interesting, but we lose some tooling in that way. So what we are going to do is we're gonna build a backend list of URLs that we want to pre-cache because our application has what I'm going to refer to as the shell. What is the shell? Well, it's anything that our application needs in order to look and feel the way that it looks and feels.
Well, it's anything that our application needs in order to look and feel the way that it looks and feels. So that means, you know, images, CSS, JavaScript, things that our application needs. All of those things could be considered the shell of our application. So I want to load the shell so that it doesn't matter if we're offline or online, those things are in there. If we're offline, those things are available to us. I also want to adjust this
If we're offline, those things are available to us. I also want to adjust this so that we don't necessarily cache everything because there are schools of thought out there that say, you know, you shouldn't cache everything because then your cache could be stale. If you are off out somewhere without any connectivity and you're out there for days, you know, whatever you have cached on your device is definitely going to be out of date.
whatever you have cached on your device is definitely going to be out of date. So what we are going to do is cache the shell of our application, but we're going to do it in a way that it's going to be dynamic. So first of all, and we're going to do this in two parts too. In this episode, we're going to focus more on the service worker. In the next episode, we're going to focus more on the server side, generating our dynamic list of URLs.
Building Pre-Cache Manifest3:28
In the next episode, we're going to focus more on the server side, generating our dynamic list of URLs. But for right now, we are going to create a controller called PreCacheManifestController. Now, don't confuse this with our manifest that's needed for the service worker. If you think in terms of just generalized manifests, a manifest is just a list of things. So this is just a list of things that we want to pre-cache, basically.
So this is just a list of things that we want to pre-cache, basically. So let's open up our PreCacheManifestController and we are going to build an array of the URLs that we want to pre-cache. So we already know that we want to the root of our application, we want offline. And what else do we want to pre-cache? You know, we do have our icons. So we could say field logger 192.png.
You know, we do have our icons. So we could say field logger 192.png. I think that's the name of that icon. The other one would be 512. And there are other things that we could do. Eventually, we want to build in the ability to read our Vite manifest, you know, and pull that information. But for right now, I want to include the assets that the Vite dev server is going to provide,
But for right now, I want to include the assets that the Vite dev server is going to provide, such as resources slash js and then app.js. Then we would have the same thing for the CSS. But the URL itself is going to be a little bit different because the Vite developer server is actually a different origin. HTTPS, and that was field logger.test. The port is 5173. So we need to include that there.
The port is 5173. So we need to include that there. And that's going to be that as far as what we're going to do. The next episode, we'll make this dynamic so that we will return a JSON response. But we're going to do it like this so that our JSON structure is going to have a version, which for right now, we'll just hard code as one. And then we will have our URLs, which is going to be provided by our shell.
And then we will have our URLs, which is going to be provided by our shell. So we're going to have that. Our return status is going to be 200. And then we want our headers, which in our case, I want the cache control to be no cache, no store, and must validate. Or it's not validate, it's revalidate. So those are our headers. And then I want to control a little bit
So those are our headers. And then I want to control a little bit about the output of JSON. So we're going to have the unescaped slashes and JSON unescaped Unicode. So that's going to be our end point. And we need a route for that. So let's take what we have for our actual manifest and let's call this pre-cache manifest. In which case we'll use the pre-cache manifest controller,
Fixing Offline Page6:28
and let's call this pre-cache manifest. In which case we'll use the pre-cache manifest controller, the name of which needs to be a little bit different. We'll just call this pre-cache manifest. Okay, so that's good. One other thing I want to address, and that is our offline view. Because I wasn't thinking about this. You know, it made sense to use the layout because it's a layout.
You know, it made sense to use the layout because it's a layout. It's a layout. But the layout includes the service worker registration. And we don't need to do that on the offline page. So I want the offline to be kind of standalone so that we'll just take the content from our offline. And I just pasted in the layout here so that we can change the slot to be our offline content. And we need to get rid of the form here
so that we can change the slot to be our offline content. And we need to get rid of the form here because if we're offline, we can't log out. So let's just get rid of the logout button. Everything else can be there. Let's get rid of the script to register the service worker. And that's going to be good. We don't need the manifest list, I don't think here. So let's get rid of that as well. Everything else should be fine.
Refactoring Service Worker7:41
So let's get rid of that as well. Everything else should be fine. Let's test that to make sure offline works, okay? Let's also check the pre-cache manifest.json. And we have our JSON structure there. So we're good to go to focus on our service worker. So we're going to drastically change this because all we are going to do is pre-cache the shell. We're not going to worry about caching things as we browse to them,
We're not going to worry about caching things as we browse to them, unless if we think that they are static assets, in which case we will cache that. So the first thing I want is the pre-cache end point because we're going to refer to this several times here. And that was pre-cache manifest.json. The next thing is not a cache name. We're going to call it cache prefix. I'm going to change this so that it's not field logger view.
We're going to call it cache prefix. I'm going to change this so that it's not field logger view. Well, we can do field locker and then we will have a dash after that so that we can append the version to it as we need. And then we won't have our pre-cache URLs, but we're going to have another cache called meta-cache. The reason being because we are going to cache our pre-cache manifest. Because if we try to fetch it
our pre-cache manifest. Because if we try to fetch it and for whatever reason that doesn't work, hopefully we have a cache. And if we do, then we can use that. Otherwise, you know, we're offline. So our name for this cache, we'll just call it meta-precache. And that's going to be fine there. So let's start with the install
And that's going to be fine there. So let's start with the install because that's, you know, the beginning of everything, but this is going to be a little more involved here. So we're going to have our async callback function here. We will need to call that. And we're going to use our self skip waiting inside of this callback, but it's going to be the last thing that we do. So that's the first thing is we're going to fetch
but it's going to be the last thing that we do. So that's the first thing is we're going to fetch our pre-cache manifest. So we're going to have our version and the URLs. And we're going to have to do this several times. So let's say that we will have a function called get current version and URLs. That's a long name, but it's at least telling as to what it is. And that's going to be asynchronous.
as to what it is. And that's going to be asynchronous. So we want to await to that. So that then we will attempt to open up, you know, our cache. So we will await caches.open And we want the cache prefix to be combined with the version so that we open up that cache because then we are going to add all of the URLs. And then we will skip waiting, but we do need to await that.
And then we will skip waiting, but we do need to await that. Did we await that before? I think we need to await that. Okay, so that means that we need to write this get current version and URLs function. So we will have async function, and then that function name. And really we want a response by awaiting the pre-cache endpoint.
And really we want a response by awaiting the pre-cache endpoint. And we are going to catch in case if there's a need to catch, and we will just return null. So that we will check if we have a response or rather if we don't have a response, then we want to open up our meta cache because hopefully we have this cached. So we will await caches open meta cache.
because hopefully we have this cached. So we will await caches open meta cache. And then hopefully we have it cached so that we will await meta match the pre-cache endpoint. Then we will check if we have that cached response, then we'll just return cached.json. Otherwise we have an issue. We don't have a response. We don't have a cache. So really we're offline,
We don't have a cache. So really we're offline, in which case we're not going to throw anything. We aren't going to even try to see if we can get the offline because we are installing, or at least we could be installing at this particular time. So we're just gonna throw a new error that says no pre-cache manifest available. So next we're gonna check if the response hasn't been modified.
So next we're gonna check if the response hasn't been modified. So that means it's a 304, in which case we're just gonna pull it from the cache and return it. So essentially the same thing, except that we aren't going to throw an error if we don't have something cached, because if we don't, then we're just gonna fall down
because if we don't, then we're just gonna fall down to actually get that response, cache it, and then go from there. So first thing we are going to do is clone the response so that then we will get the JSON, but we need to await res.json. Then we're gonna open up our meta cache. So that is the meta cache so that we can put pre-cache endpoint and the clone.
So that is the meta cache so that we can put pre-cache endpoint and the clone. Why did it wrap? Oh, because I need to await that. So we await opening up our meta cache, then we put in the clone for our pre-cache endpoint, and then we just return the JSON. So that's, yeah. So we try to fetch our pre-cache. If that doesn't work, we try to get it from cache.
So we try to fetch our pre-cache. If that doesn't work, we try to get it from cache. If that doesn't work, then we throw an error. If it hasn't changed, we just grab it from our cache, use that. Otherwise we're going to get what we have from the response, store that in our meta cache, and then return the JSON. So that's our install. So that the next thing that we need to do is activate, in which case we essentially want to do the same thing,
Managing Cache Versions13:46
So that the next thing that we need to do is activate, in which case we essentially want to do the same thing, but it's gonna be a little bit more involved since we are working with two different caches. So we want to clean up our caches, but we're gonna have to do this in a slightly smarter way, in that we're gonna have a keep name. We need to know the name of the cache that we want to save. And, you know, I'm thinking about names here. Let's call this app shell,
And, you know, I'm thinking about names here. Let's call this app shell, because that's descriptive as to what this cache is, because we have this other cache of the meta pre-cache. Okay, so we have our name. We don't have our name. We want to know what name of the latest shell cache that we want to keep, in which case we want to get the version from our get current version and URLs.
in which case we want to get the version from our get current version and URLs. But we need to await that, because this is going to give us the ability to set our keep name. The name of the cache that we want to keep is our cache prefix with the provided version. However, if we run into an error trying to get to that latest name, then we need to look at the caches that we currently have
trying to get to that latest name, then we need to look at the caches that we currently have and try to figure out what the latest one is, in which case we could have a function called get newest shell name for the lack of a better name for a function. Although that's pretty descriptive as to what it is. We're getting the latest shell name. So nevermind, that's going to be fine. And then from there,
So nevermind, that's going to be fine. And then from there, we want to delete old shell caches that is not with the keep name. And then we want to await self clients claim. Okay, so that seems pretty sound. We want to get the latest version cache name. If that fails, then we read the caches that we currently have, and then we delete anything
then we read the caches that we currently have, and then we delete anything that doesn't match our keep name. So let's define this get newest name here. So this needs to be asynchronous and we aren't going to need anything as far as parameters are concerned, but we do need the names of all of our caches. So we can await caches keys, but we only want to deal with the caches
So we can await caches keys, but we only want to deal with the caches that have our shell because we have that meta cache. So really we want to filter the names so that the key starts with the cache prefix. This way we get to only the caches that are for our shell and we ignore everything else. And then we'll just sort those so that then let's return. We'll check if we have any names. And if so, we will return the last one,
We'll check if we have any names. And if so, we will return the last one, names.length minus one. Otherwise we will return null because if we don't have any names, then we've got nothing. So that will be fine. So that will get our newest shell name so that then we need to delete all of the old shell caches. So let's put this.
so that then we need to delete all of the old shell caches. So let's put this. I like to keep my functions or my files in alphabetical order. That's just how I'm weird. And we have this keep name that we want to keep. And the first thing that we want to do is the same thing that we did before. We want to get all of the names of our caches, but let's call this keys.
We want to get all of the names of our caches, but let's call this keys. We will await caches keys. And then we want to iterate over each one of these keys. We want to see if it's going to match our keep name. And if it's not, and if it's a shell cache, then we want to delete it. So we're going to use promise all because we're going to do keys map and we're going to work with each key.
because we're going to do keys map and we're going to work with each key. Now we need to know two things. We need to know if it's a shell cache is shell. And this will be easy. If it starts with our cache prefix, then it's a shell cache. But we also need to know if it's old and we need to check if we have a keep name because keep name could be null.
and we need to check if we have a keep name because keep name could be null. So if we have a keep name, then we want to see if the key that we are currently working with is not the keep name, in which case that would be true and we would want to delete it. Otherwise it would be false. And then we'll have a default value of true there. So that then if it's a shell cache and if it's old,
And then we'll have a default value of true there. So that then if it's a shell cache and if it's old, then we will return caches, delete, passing in the key. That's going to clear up all of our old shell caches and that's going to finish our activate. So when it comes time to fetching, well, things are going to be a little bit different because now, you know, before we cached everything as we browsed, we don't want to do that. We only want to cache things that we think are static.
as we browsed, we don't want to do that. We only want to cache things that we think are static. And really we want to cache things that we think are ours and we want to static and they're static. So let's do this. We're going to start by saying if the request mode is equal to navigate, then there's really not a whole lot that we want to do here. We want to essentially fetch that response
that we want to do here. We want to essentially fetch that response or fetch that request and then return the response and then be done with that. So we're going to have async and we need to call that so that here we will try to return await fetch request because if that fails, then we want to do one of two things. We want to get the offline if it's currently cached and hopefully it is so that we show that offline page
We want to get the offline if it's currently cached and hopefully it is so that we show that offline page but it could be possible that it's not cached in which case we want a new response of offline where the status is 503 and that needs to be a function. There we go. Okay, so if the user is navigating, so they are clicking on the link, they, well, you know what?
so they are clicking on the link, they, well, you know what? They could be submitting a form because, you know, let's think about this. They're out in the field. They have connectivity to where they can pull up the page for whatever meter they've typed in or tapped in whatever they want. But when, by the time they tap on send, they've lost connectivity.
But when, by the time they tap on send, they've lost connectivity. In which case, you know, we want to show you're offline. Sorry. So we want to get rid of this check because we want to handle every kind of request if it is navigate. Well, for any request really. Yeah, so if it's a navigation request, then we are going to attempt to just do or go wherever.
Yeah, so if it's a navigation request, then we are going to attempt to just do or go wherever. If that fails, then we try to get the offline from the cache. If that fails, then we just return an offline response and we're good to go. So that's if the user is navigating and if they are, we're done. We return. But then, you know, we want to check if it's a static asset and not just check if it's a static asset,
But then, you know, we want to check if it's a static asset and not just check if it's a static asset, but check if it is a static asset from our side. In which case, we're going to need to do this. We're going to create a URL variable. We're going to new up the URL constructor, passing in requests.url. That way, we can come down here and we can check if the URL origin is equal to the service worker's location origin.
and we can check if the URL origin is equal to the service worker's location origin. And I can find it's number seven. Okay, if it is the same origin and if it is a static asset, then we want to treat this a little bit differently. You know what? So let's just take everything that we have in here and get rid of that. So that we can take this and put this here.
and get rid of that. So that we can take this and put this here. And the first thing we are going to do is attempt to get the shell name. So we're going to await get newest shell name. And the reason why we want to do this is because we could be between the install and the activate, in which case our cache isn't fully built. So we want to check. If we don't have a shell name, then we don't have cache.
So we want to check. If we don't have a shell name, then we don't have cache. We want to try the network and hopefully that's going to work. So we're going to return await fetch request. However, if that fails, then, you know, remember we are in between the install and the activate. So if that fails, we don't have cache. The only thing that we could do is return this new response of offline 503.
The only thing that we could do is return this new response of offline 503. So we'll do that. However, if we do have a shell name, then we want to open up that cache. So we'll call caches open with that shell name. And we're going to see if we have something cached for this request, because here's essentially what we want to do. We want, if we have this cache,
because here's essentially what we want to do. We want, if we have this cache, we want to return the cache. If we don't have a cache, we want to fetch it from the network and return that. And of course, cache it if it's not cached. But if that fails, then we also want to return the offline. So our return statement is going to look like this to where we will return our cache
So our return statement is going to look like this to where we will return our cache or we will return the response from the network or do I still, yeah, I had that still in my clipboard or we return the new response of offline. And that's our return. But of course we need to try to get this response. So we will await fetch request and then we will work with the response so that if the response is okay
and then we will work with the response so that if the response is okay and the response type is basic or if the type is equal to cores. Now this response type is important. If it is an asset that's just on our server, then the response type is going to be basic. In the case of our VEET development server, the response type will be cores, but that's still something
the response type will be cores, but that's still something that we would be interested in caching. So anything like basic or cores, we typically want to cache or at least in this particular case. So that's what we will do. We will put into our cache the clone of the response. And if for whatever reason there's an issue, we'll just catch it, return an object.
And if for whatever reason there's an issue, we'll just catch it, return an object. And yeah, so that then we will return res and that should do it. Although let's do this. We'll also catch here so that if there's an error, we'll just have null. And then we will return cached response or offline. And that should be it. As far as our service worker is concerned.
And that should be it. As far as our service worker is concerned. Oh, we do need to define, what was it? Is static asset. So that starts with an I, which means it's gonna come after get newest. So this shouldn't be asynchronous. So we'll just have function is static asset URL. And then let's do this. We'll have URL or we'll say path equals URL dot path name.
And then let's do this. We'll have URL or we'll say path equals URL dot path name. Because we're gonna use that a lot. Because we are going to return. And the first thing we are going to do is check if the path name starts with build slash. Because that's typically gonna be from Vite. So if it's in our build folder, it's a static assets. We want to keep that or we want to cache that. Then we want to check for actual static files,
We want to keep that or we want to cache that. Then we want to check for actual static files, images, CSS, JavaScript, things like that. So if it ends with things like dot CSS or dot JS, dot PNG, SVG, JPG. There's others that we can do. That's gonna be fine in this particular case. So it's a static asset. If it starts with slash build, if it ends with dot CSS dot JS.
If it starts with slash build, if it ends with dot CSS dot JS. Okay, so that's our service worker. You know, adding flexibility adds complexity. Is shell is there? I'm glad I was scrolling up because that's something that we would have had to have caught then. Okay, so I think we're ready to actually test this out. And while I have that up in the browser, I'm gonna pull up a private window
Testing Cache Behavior27:22
And while I have that up in the browser, I'm gonna pull up a private window because I like testing in a private window because it's a fresh start. There's no cookies, there's no cache, there's nothing. So if we ever just need to start over again, we can just close out the window, open up another one and start. It's kind of nice in that sense. So we need to sign in as Tim at example.com.
It's kind of nice in that sense. So we need to sign in as Tim at example.com. Password is the password. We will go to our meters. Let's pull up the developer tools and we have a couple of errors. So at the path, start with. Start with is not a function. It's right in that sense. So we need to find starts with and that is ends with.
It's right in that sense. So we need to find starts with and that is ends with. Okay, I said that there were two of them. Start with, well, that's starts with two. Okay, start. Okay. So we'll go back to our private. Let's unregister, let's clear the errors and let's do a hard refresh. Okay, so our service worker is activated and running.
and let's do a hard refresh. Okay, so our service worker is activated and running. That looks good. If we take a look at storage, we of course have some storage there, but the telling thing is to look at our cache storage. So we have our MetaPRE cache and there it is. Now, notice our response type is basic. We briefly talked about that, but notice that the content length here is zero.
We briefly talked about that, but notice that the content length here is zero. That makes you think that there's nothing there and that's not the case because remember that we are essentially storing the response there. So the response, the content length is part of the response. That's usually a header. And this could be a quirk of the developer tools. This could be that the content length wasn't included
And this could be a quirk of the developer tools. This could be that the content length wasn't included with that request or with the response. So the content length here is going to be zero. And we're going to see that too when we look at the shell cache. And we can see here, we have cached everything that we wanted to cache as far as our pre-cache is concerned. But once again, we can see things that don't have a content length here.
But once again, we can see things that don't have a content length here. So just because you might see zero for content length doesn't mean that there's nothing there. So there we have the service worker. We're halfway done as far as our dynamic, I'm going to call it dynamic service worker because that's almost exactly what it is. So in the next episode, we will implement the PHP side of that
So in the next episode, we will implement the PHP side of that to where we will read our Vite manifest and dynamically build our list of URLs to pre-cache.
