Conditional Precaching in Workbox - service-worker

There are cases where an application might need a relatively large resource as a strict requirement, but only in certain cases that are not easily detectable from a service worker. For example:
Safari has a prefixed and non-conforming implementation of the Web Audio API. I found a great shim, but it's over 300kb. It's critical for the web app to function in Safari but unnecessary in other browsers.
Some media are available in multiple formats that may not always be supported. Video is typically too large to precache and has problems with range requests, but it could apply to WebP images or short audio files (e.g. Opus vs. AAC). If you include all formats in the precache manifest, by default it will download all of them.
One approach would be to manually exclude certain files from the precache manifest and then to conditionally load those files from the scripts on the main thread to be stored in the runtime cache. But then those files are not precached - they're only loaded after the new version activates, by which point you may no longer be online.
Is there a solution that allows the following?:
Have the service worker send a message to the main thread with the URL of a "test" script that checks the various conditions.
Load and run that script on the main thread and send the service worker the list of required conditional assets
Add those assets to the precache manifest to be diff'ed against the previous version and downloaded as necessary
The service worker should not switch over to the new version until all precached assets are loaded, including the conditional ones.

I ran into a similar issue with i18n files. I don't want my users to precache all strings for all available languages.
Some context for my setup. I'm using the injectManifest approach for vite. The webpack plugin should expose the same config options for this.
Language files in my case have this shape in the build dir:
assets/messages.XXXX.js
So the first step was to tell the injectManifest config to ignore these files:
injectManifest: {
globIgnores: ["**/node_modules/**/*", "assets/messages.*.js"],
}
The second step was going into the sw.js file and tell workbox that all scripts that aren't precached should be requested via the CacheFirst strategy. (i.e. first attempt to load it from the cache, if not present, load it from the network and put it into the cache).
So here's the adapted part of the sw.js:
/* ... */
import {registerRoute, NavigationRoute, Route} from "workbox-routing";
import {CacheFirst} from "workbox-strategies";
precacheAndRoute(self.__WB_MANIFEST);
// Handle non-pre-cached scripts
const scriptsRoute = new Route(
({request}) => {
return request.destination === "script";
},
new CacheFirst({
cacheName: "scripts",
})
);
cleanupOutdatedCaches();
registerRoute(new NavigationRoute(createHandlerBoundToURL("index.html")));
registerRoute(scriptsRoute);
/* ... */

Related

Pre cache folder and subfolder content

Is there a way to precache all files in folder and its subfolders?
What i want to accomplish is something like this:
...
event.waitUntil(
caches
.open(version + 'fundamentals')
.then(function (cache) {
return cache.addAll([
"/",
"/images/*"
...
The JavaScript executing in the context of your service worker won't have any knowledge of your filesystem.
If you'd like to precache all of the files matching a specific wildcard pattern, you'll need to add some build-time tooling, when the code that's being executed has access to your filesystem. The build-time tool can then feed output into a service worker's JavaScript file, ideally via some templating system.
The sw-precache tool can automate this process for you, including generating the service worker's JavaScript and keeping your caches up to date as static assets in your local filesystem change.
If you choose not to use a pre-packaged solution like sw-precache, make sure that you understand the service worker install/activate lifecycle events, and that you're properly versioning your static resources and caches.

Bundling and Minification of AWS CloundFront CDN files using ASP.NET MVC?

Bundling: It’s a simple logical group of files that could be referenced by unique name and being loaded with on HTTP requestor.
Minification: It’s process of removing unnecessary whitespace, line breaks, and comments from code to reduce its size thereby improving load times.
Here is my idea,
Basically I use multiple CCS, JS, and Image files for modularity, readability and maintainability of the code. Here multiple JS and CSS files require multiple HTTP requests from the browser leads to degrade the performance and load time of my web page, in some cases it leads to degradation of the overall performance of the website.
I would like to store my all static content into AWS S3 and serve them by CloudFront distribution links and use those CDN paths to my multiple projects with bundling & minification.
I have been trying to bundle all JS files from CDN into a single bundle (for Bundling & Minication) Like below code but this doesn’t work!
var myCDN = "http://cdn.myawsdomain.com/";
bundles.Add(new ScriptBundle("~/bundles/js", myCDN)
.Include(
"~/MyS3BucketName/Scripts/jquery.cookie.js",
"~/MyS3BucketName/Scripts/bootstrap.min.js",
"~/MyS3BucketName/Scripts/wow.min.js"
));
Also tried below code,but this doesn’t work!
bundles.Add(new ScriptBundle("~/bundles/js")
.Include(
"http://cdn.myawsdomain.com/MyS3BucketName/Scripts/jquery.cookie.js",
"http://cdn.myawsdomain.com/MyS3BucketName/Scripts/bootstrap.min.js",
"http://cdn.myawsdomain.com/MyS3BucketName/Scripts/wow.min.js"
));
Any help will be appreciated.
I am answering to my own question because it may be help full to someone.
I have generated compressed and minified version of the JS and CSS files using ASP.Net MVC Bundle Config. We can’t combine multiple CDN’s together (one script only) in bundle config.
I have performed the following steps to generate compressed and minified JS & CSS files,
a. Include necessary JS files in bundle config with the script bundle virtual path (“~/scripts/bundle”) and check web page is loaded with no errors in the browser.
BundleTable.EnableOptimizations = true;
bundles.UseCdn = true;
bundles.Add(new ScriptBundle("~/scripts/bundle")
.Include("~/Yourscriptfile1.js")
.Include("~/Yourscriptfile2.js")
.Include("~/Yourscriptfile3.js")
);
b. To compress and minify all those JS files into one file, Send HTTP request to virtual path (http://localhost:254/scripts/bundle) from your local machine browser and save response into “output.min.js” file.
c. Upload “output.min.js” file into S3 bucket and set this object public read-only property, add expire header with far-future expiration date, and configure S3 bucket as a CDN.
Key=”Cache-Control”, Value=”max-age=1814400” - [3 weeks]
Key=”Expires”, Value=”Thu, 30 Dec 2021 16:00:00 GMT” - [far-future expiration date only]
d. Now configure your CDN in bundle config file by changing the above code (Step-a) into below code,
BundleTable.EnableOptimizations = true;
bundles.UseCdn = true;
string CDN = "http://cdn.mydomain.io/Scripts/compress/output.min.js";
bundles.Add(new ScriptBundle("~/scripts/bundle", CDN)
.Include("~/Yourscriptfile1.js")
.Include("~/Yourscriptfile2.js")
.Include("~/Yourscriptfile3.js")
);
In the above code, Scripts will be requested from the CDN while in release mode. The debug version of the script will be drawn locally in debug mode.

Is there a way to update asp.net mvc bundle contents dynamically at run-time?

I'm ASP.NET MVC v4 for my application, and I'm using the web optimization features (bundling and minification of scripts and styles).
Now, what I understand is (please correct me if wrong), the optimization framework will look at the included files at the time of compilation and configure them. It'll create a version number (v=something) based on the contents. Every time the contents change, it'll recreate the version hash, and the client will get updated files.
Now, is there a way to get the following done
[1] Update something inside a js file in my server, and serve the updated one to the clients without re-building & re-starting the application (I'm not changing bundle configuration here, just updating file content inside a script) ?
[2] Update the script configuration itself (e.g. adding a new script to a bundle), and get that served to the clients without Re-compiling & Re-staring the application? Or, at least without re-compiling? (I know, generally we define the bundles inside cs files, but wondering if there is a way out!)
[3] Is there a way to use my own version number (say from a config file, v=myCustomScriptVersion) rather than the auto-generated version hash?
It's bit late, but I'm just sharing my experience on my own questions here.
As discussed in the comments of the question, bundles are defined as part of a cs file (generally BundleConfig.cs inside App_Start). So, the bundles are defined at compile time, and at application start they will get added to collection and become usable.
Now, the interesting bit. At run-time, the optimization framework looks into the included files and creates a hash of the contents, and appends that as a version query-string to the bundle request. So, when the bundle is called the generated uri is like the below one.
http://example.com/Bundles/MyBundledScripts?v=ILpm9GTTPShzteCf85dcR4x0msPpku-QRNlggE42QN81
This version number v=... is completely dynamic. If any file content within the bundle is changed, this version will be regenerated, and will remain same otherwise.
Now to answer the questions,
[1] This is done automatically by the framework, no need to do anything extra for this. Every time a file content is changed, new version number will be generated and the clients will get the updated scripts.
[2] Not possible. If files included in a bundle are changed, is has to be recompiled.
[3] Yes, it can be used. The custom version number can be added as below.
#Scripts.Render("~/Bundles/MyBundledScripts?v=" + ConfigurationManager.AppSettings["ScriptVersion"])
But Caution! This will remove the automatic versioning based on file contents.
And, additionally, if there are multiple versions of the same file available and we always want to include the latest version available, that can be achieved easily by including a {version} wildcard in bundle configuration like below.
bundles.Add(new ScriptBundle("~/Bundles/MyBundledScripts")
.Include(
"~/Scripts/Vendor/someScript-{version}.js"
));
So, if there are 2 scripts in the /Scripts/Vendor folder
someScript-2.3.js
someScript-3.4.js
Then the file someScript-3.4.js (higher version) will get included automatically. And when a new file someScript-4.0.js is added to the folder, that will be served to clients without any need for recompile/restart.

HTML5 Application Cache need explanation

Can someone show a complete example of application cache with html, css, js, appcache file including CACHE, NETWORK and FALLBACK section. Also updating the manifest. Where should the coding be written?
http://www.html5rocks.com/en/tutorials/appcache/beginner/#toc-updating-cache
As per updating cache from the above link, where should the coding been written?
The code for updating the manifest is written by your sever somewhere.
Either in PHP or Node.js you must write and serve this file with the correct mime type as specified in the link you posted.
You can auto generate this from the css and js files on your server. Don't include html files unless they are dynamic pages.
The first line in the file must be CACHE MANIFEST
Now it assumes your are putting things into the CACHE section, which is where you need to include all the paths to your css and js that you want the user to be able to use offline.
To create a NETWORK section, simply print out the word on it's own line.
Under this section you should include pages that should only be used online.
Under the FALLBACK section include a page to show if there is no offline version available.
This is a brief explanation but you should be able to easily find a tutorial that will help you auto generate this file.
For more details about the cache manifest itself:
http://diveintohtml5.info/offline.html
Offers the best explanation IMHO.

Using Rails 3 Sprockets at runtime

I have a Rails 3 application that needs to display images from another application. Those images change over time, so I have a task that runs hourly to check for changes. For performance reasons, I want to create a sprite with those images that I serve from my own application (so 1 image and 1 css file).
Ideally, I'd like Sprockets to handle these files in the same way it does any of my other images and stylesheets in my application so I don't have to roll my own minification, gzip, caching, etc solutions.
Is there a way to hook into Sprockets at runtime so that I don't have to stop my server, precompile, and start the server again?
I was unable to find any way to hook into sprockets.
For those who are curious, I solved this problem in the following way (but left out the minification/gzip piece, since the tiny performance boost doesn't justify the complexity):
Create the sprite files as usual.
Determine the md5 hash value for the image.
Copy the new files to the public folder using the md5 hash value as part of the file name.
Send the new files to all servers in the cluster.
Update a configuration variable on all servers to know which file to serve (e.g. MyApp::Application.config.tool_icon_md5_key).
Remove old files from all servers in the cluster.
The clustering is actually the most difficult piece. The key there is avoiding the case where one server requests the new file but the server that handles that request doesn't actually have that file yet.

Resources