Yes yes I’ve started blogging again (I’ll do a intro post soon), the reason is I want to share usefull #umbraco stuff again. (remember http://www.nibble.be/) …
So as an (#umbraco) webdev we make css and js (client side) changes to our clients solutions… but how often have you need to tell your client to refresh the browser cache to view the most recent changes on their site?
Of course you can do some manual appending of params like .css?v=1 , .css?v=anotherupdate but chances are you’ll forget this… and of course a mismatch between markup and js/css can lead to an ugly looking/even broken website
Wouldn’t it be nice if this could be automated, so each time a change is detected in the js/css file you are certain the client and site visitors get the correct version.
This can be done by adding a simple file hasher (c# code)
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Security.Cryptography; | |
using System.Text; | |
namespace MyUmbracWebsite | |
{ | |
public static class StaticFileHasher | |
{ | |
private static Dictionary<string, string> FileHashes { get; set; } | |
public static string Hash(string path) | |
{ | |
if (String.IsNullOrEmpty(path) || !FileHashes.ContainsKey(path)) | |
{ | |
return null; | |
} | |
return FileHashes[path]; | |
} | |
public static void HashFiles(params string[] directories) | |
{ | |
FileHashes = new Dictionary<string, string>(); | |
var FileExtensions = new string[] { "*.css", "*.js", }; | |
using (var md5 = MD5.Create()) | |
{ | |
foreach (var directory in directories) | |
{ | |
foreach (var extension in FileExtensions) | |
{ | |
foreach (var file in Directory.EnumerateFiles(directory, extension)) | |
{ | |
byte[] hash; | |
using (var stream = File.OpenRead(file)) | |
{ | |
hash = md5.ComputeHash(stream); | |
} | |
StringBuilder sOutput = new StringBuilder(hash.Length); | |
for (var i = 0; i < hash.Length - 1; i++) | |
{ | |
sOutput.Append(hash[i].ToString("X2")); | |
} | |
FileHashes.Add(Path.GetFileName(file), sOutput.ToString().ToLower()); | |
} | |
} | |
} | |
} | |
} | |
} | |
} |
This bit of code will generate a unique hash based on a file (contents) and add it to a Dictionary (limited to .css and .js files found in specified dirs), key being the name of the file.
Of course we need to run this once on site startup…(so we get a hash for all files we want, feeding in directories)
In Umbraco v7 this can be done with an ApplicationEventHandler
using Umbraco.Core; | |
namespace MyUmbracoWebsite | |
{ | |
public class Startup : ApplicationEventHandler | |
{ | |
protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) | |
{ | |
StaticFileHasher.HashFiles(umbracoApplication.Server.MapPath("~/css"), umbracoApplication.Server.MapPath("~/js")); | |
} | |
} | |
} |
In Umbraco v8 with the ApplicationComponent stuff
public class ApplicationComposer : ComponentComposer<ApplicationComponent>, IUserComposer | |
{ | |
public override void Compose(Composition composition) | |
{ | |
StaticFileHasher.HashFiles(umbracoApplication.Server.MapPath("~/css"), umbracoApplication.Server.MapPath("~/js")); | |
base.Compose(composition); | |
} | |
} |
So now in the layout (or wherever I’m linking to the .css . js files) You can add:
/css/app.css?hash=@StaticFileHasher.Hash(“app.css”)
this then get’s outputted as
/css/app.css?hash=da1cc0b5fb4e777fe556fb8d61168c
(of course not with the same hash)
And when the file contents changes after a change/deploy the unique hash will also change … Tada! You are now sure your client and site visitors get the latest css/js
Top comments (2)
Doesn't requirejs and requirecss already accomplish this? Along with minification and specifying the order the files are stacked?
I was walking through some of my code with a non-umbraco dev recently, and he was impressed by requirejs and requirecss.
maybe... but this is for the frontend... so not sure everybody is using that dependency (requirejs and requirecss )