U4-11014 - Implement cache busting for Angular views in third party packages

Created by Anders Bjerner 26 Feb 2018, 17:27:31 Updated by Sebastiaan Janssen 15 Mar 2018, 18:08:51

Tags: Up For Grabs

Umbraco has implemented logic for cache busting the Angular views used natively in Umbraco (see link below):

https://github.com/umbraco/Umbraco-CMS/blob/cf86409e3fec4198744189a48255543fc2f32614/src/Umbraco.Web.UI.Client/src/app.js#L44

This works great, but Umbraco does however not provide the same mechanism for Angular views that are part of third party packages. So developers have to invent this on their own mechanism for cache busting (which in my case usually is a manual process).

The feature request in that Umbraco should support cache busting for third party views by default.

I'm not sure exactly how though, but I see a number of ways:

A)

Umbraco itself uses Umbraco.Sys.ServerVariables.application.cacheBuster, which is a hash of the current client dependency version ([see |https://github.com/umbraco/Umbraco-CMS/blob/cf86409e3fec4198744189a48255543fc2f32614/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs#L389]).

By removing a check at [this line|https://github.com/umbraco/Umbraco-CMS/blob/cf86409e3fec4198744189a48255543fc2f32614/src/Umbraco.Web.UI.Client/src/app.js#L41] that the URL must start with views/, the existing cache busting would also apply to views from packages.

B)

As the ClientDependency version isn't bumped when the application is recycled, it may still be hard to clear any cached views. So another approach could be to to use the version of the package as the value in the query string.

Packages - at least when looking at package.manifest files - does not currently have a version, so this is something that should be introduced. Keeping it simple, it could be something like:

{
    "version": "1.2.3",
    "propertyEditors": []
}

Umbraco would then keep track of the version number of each package.manifest file in /App_Plugins/, and make this available for Angular as well. Eg. through the full version of the server variables, but not in the bare minimum version so we don't expose too much information.

When the browser then makes a request to an URL like /App_Plugins/MySuperAwesomePackage/Views/FantasticPropertyEditor.html, Umbraco will look for the version of MySuperAwesomePackage (as specified in /App_Plugins/MySuperAwesomePackage/package.manifest), and append the version to the query string if specified.

If the developer hasn't specified a version in the manifest file, approach A) could be used as fallback.

Umbraco already has the variable Umbraco.Sys.ServerVariables.umbracoPlugins (which is only used my models builder for now). To keep track of the version numbers, a similar Umbraco.Sys.ServerVariables.packages object could be introduced as well, where each property would then be an object representing an installed package. To visualize this, it might look like:

Umbraco.Sys.ServerVariables.packages = {
    "MySuperAwesomePackage": {
        "version": "1.2.3"
    },
    "MyOtherAwesomePackage": {
        "version": "1.0.1"
    }
};

Each object might even contain more information than just the version, but that's a topic for another time.

So assuming that the package version numbers are exposed through Umbraco.Sys.ServerVariables.packages, the app.js mentioned in the beginning, could be changed from:

if (Umbraco.Sys.ServerVariables.application && url.startsWith("views/") && url.endsWith(".html")) {
	var rnd = Umbraco.Sys.ServerVariables.application.cacheBuster;
	var _op = (url.indexOf("?") > 0) ? "&" : "?";
	url += _op + "umb__rnd=" + rnd;
}

to

if (Umbraco.Sys.ServerVariables.application && url.startsWith("views/") && url.endsWith(".html")) {
	var rnd = Umbraco.Sys.ServerVariables.application.cacheBuster;
	var _op = (url.indexOf("?") > 0) ? "&" : "?";
	url += _op + "umb__rnd=" + rnd;
} else if (Umbraco.Sys.ServerVariables.packages && url.toLowerCase().startsWith("/app_plugins/")) {
	var alias = url.split('/')[2];
	var pkg = Umbraco.Sys.ServerVariables.packages[alias];
	if (pkg && pkg.version) {
		var _op = (url.indexOf("?") > 0) ? "&" : "?";
		url += _op + "umb__rnd=v" + pkg.version;
	}
}

As this is just a quick example, it doesn't contain a fallback, but otherwise does the trick. It also assumes that the package "alias" doesn't change case, so it won't work if the package folder is named MySuperAwesomePackage, but the requested file is /App_Plugins/mySuperAwesomePackage/Views/FantasticPropertyEditor.html. We could handle this as well, but not sure whether we should.

The example also doesn't handle anything outside out /App_Plugins/ and /umbraco/view/ - but then again, I can't think of a reason to place Angular views outside of those two locations.

C)

There obviously also might be an approach I haven't thought of, so please share your suggestions ;)

Comments

Anders Bjerner 26 Feb 2018, 17:51:21

Oh, and for B) there might as well be an optional cachebuster value as as alternative to version (cachebuster would have higher priority, and thus will be used instead of version should cachebuster be specified).

For instance if I make changes (eg. a hotfix) to a package that isn't my own, I would prefer being able to change a cachebuster value in package.manifest rather than changing version.


Anders Bjerner 15 Mar 2018, 17:48:22

@sebastiaan I can see that you've changed the status to "Up For Grabs".

I'd be happy to look into this myself, but I think it requires some feedback from the HQ (or others), as there are different approaches - where one of the adds a new property to package.manifest.


Sebastiaan Janssen 15 Mar 2018, 18:08:51

Yep, sorry for not adding a comment before, I was getting there. I think it will be beneficial to use the versioning technique in the package.manifest indeed. That way a package developer can easily release a new version and be confident that on the next environment(s) their caches get cleared. It's also intuitive as you would update the version anyway when releasing a new version.

Not sure if an additional cacheBuster variable would add much, but if you often change other people's packages then sure, could be an optional extra variable to add.


Priority: Normal

Type: Feature (request)

State: Open

Assignee:

Difficulty: Normal

Category:

Backwards Compatible: True

Fix Submitted:

Affected versions:

Due in version:

Sprint:

Story Points:

Cycle: