Blog

Effectively loading CSS and Javascript with ClientDependency in Umbraco

Effectively loading CSS and Javascript with ClientDependency in Umbraco

This is not a (very) technical article - it's rather my specific approach to doing what the title says.

When creating a static HTML template, web authors tend to load every script and CSS on all pages - required or not - to make it easy for other people to use them. This is often also adopted by front-end developers creating Umbraco templates. What they usually do to make things more efficient is either combine all scripts and CSS files to a single respective file and load that one, or use ClientDependency, a library bundled with Umbraco which essentially does the same thing (but can do some more things as well).

ClientDependency (https://github.com/Shazwazza/ClientDependency) is a very simple to use library that combines and compresses scripts and CSS files. You declare that you need a file, then another and another, and when you're done you tell the library to render the combined/minified result where you want it. This gets cached in the browser, of course, and you can re-generate it with a different URL forcing a reload by changing the version number setting in the /config/ClientDependency.config file.

For example, that's how you declare you need some stuff in a view/template:

Html.RequiresJs("/scripts/plugin/jquery.stellar.min.js");
Html.RequiresCss("/Css/plugin/jquery.fs.tipper.css");
Html.RequiresJs("/scripts/plugin/jquery.fs.tipper.min.js");

And here's how you will tell ClientDependency to create the bundle and spit it on the page where you need it:

@Html.RenderJsHere()
@Html.RenderCssHere()

If you don't do anything else, the order in which you do the declarations is the order the files are loaded. In our example, Stellar will be placed before Tipper in the combined scripts file. Scripts and CSS are combined in two different files and loaded at the point you put your RenderJsHere() and RenderCssHere() calls respectively. 

So ClientDependency can assist you in dynamically combining/minifying your scripts and CSS. But you still load everything on your "master" template.

However insignificant it may seem, stuffing a lot of not-really-required things in a page has its impact, especially when testing for page speed and efficiency. For one, scripts that scan the DOM to find specific elements take time to run, while unused CSS rules have a negative impact on page performance as well since the browser will have to work with a larger set of rules to apply where possible.

And, although we can't adapt everything to suit each and every one of our pages at 100%, we (developers) can take care so that huge slider script, for example, is not included when there's not a slider present.

Enter ClientDependency Groups and Priorities.

ClientDependency's RequiresJs() and RequiresCss() functions can optionally take two more arguments after the file's path: Priority and Group. Both are numeric. When you specify a Group number, every request to ClientDependency that has the same Group number gets bundled together. This means that if you specify 3 different groups for your JS files, RenderJsHere() will produce three different files, each containing bundled scripts that have the same group number.

Priority is the priority of the items WITHIN the same group - when you use a priority value, you override the natural declaration order with a custom one. So, even if you declare something first, if you give it, let's say, a priority of 99999 (supposing there is no greater priority number than that one), it'll always be placed last in the combined file.

Groups have their own priority as well - a group with a lower number gets rendered earlier. This means that the combined file for Group 1 will be loaded before the combined file for Group 2 and so on.

By setting up Groups (and, if you need to, Priorities) you can control what gets rendered where. Even though you are allowed only one RenderCssHere() and one RenderJsHere() call, they will render what has been declared, changing priorities and splitting stuff into groups if applicable. Plus, ClientDependency's greatest advantage is that it treats multiple declarations for the same file as one, eliminating duplicate loads, meaning that multiple RequiresJs() or RequiresCss() calls for the same file will be treated as a single one.

With that in mind, let's take a hypothetical scenario, where you have the following that are going to be used on your site:

Script_Global.js (a globally used script)
Script_Map.js (a script only used in pages that contain a map)
Script_Slider.js (a script only used in pages that contain a slider)

Let's also suppose that you have the following partial views:
PartialWithMap (uses a map, obviously)
PartialWithSlider (I think you get it by now)

And the following templates:
"Master" template (inherited by all other pages)
HomePage (doesn't use a map or a slider)
SomePage (includes the partial for a map, a slider, or both, according to user's choices on the back office).

And let's see how we would break our ClientDependency calls so that everything is loaded where needed.

On our "master" template, we would naturally have a call to RenderJsHere(). Before that, we would obviously call

Html.RequiresJs("Script_Global.js", group:1);

since this is going to be used everywhere. Notice that I'm not using priority at all since all we need to demonstrate here is groups.

On our HomePage template, we wouldn't need to declare anything, since nothing else than the global script is needed.
On our SomePage template, we ALSO wouldn't need to load anything. This will be taken care of by the specific partials.

On our PartialWithMap view we load the relevant script. Notice that I'm using a different group number here.

Html.RequiresJs("Script_Map.js", group:2)

On our PartialWithSlider view we load the relevant script as well. Again, a different group number.

Html.RequiresJs("Script_Slider.js", group:3)


So we have now three groups, each containing a single JS file (for the sake of simplicity, of course your groups will probably contain much more). This will create three different files when RenderJsHere() is called.

The cool trick here, though, is that the map and slider scripts will not be loaded on our home page, making it significantly lighter. Also, if we have multiple pages where the SomePage template is used and some of those only use the map, only the global script and map script will be loaded for them. And similarly for pages that only use the slider.

What's the downside? You must be careful in choosing your groups, because in a real environment you'll probably have dozens of files to combine, and if you rely on dynamic combinations like the above you'll end up with multiple combined/minified files from ClientDependency containing almost the same original stuff. Imagine what would happen in the above scenario with 10x such partials and assorted scripts for each: If you loaded everything according to whether it's used on a page or not, you could have dozens of combined files, each one used for a different page, each containing a subset of the total number of scripts available, effectively nullifying any benefit you could have from combination/minification.

So a rule of thumb here is break things in groups where it makes sense. If something is expected to be loaded, let's say, 70% of the time, it may not make sense to put it into a different group than your "global" one.

I'm personally using groups and priorities heavily, reason being that I'm following a "content blocks" approach in Umbraco, where each "block" has its own partial view for rendering. A page can be made of any number of those "blocks", meaning that each one will probably have their own scripts to load. However, if something is being used in a large percentage of my "blocks", I prefer to load it by default even when there's a chance it won't be used. This way I keep the possible combinations to a minimum while loading seldom-needed stuff where they're actually needed only.

Moreover, I've created a (somewhat arbitrary) classification for Groups and Priorities to avoid using numbers:

(Don't judge my naming habits! This makes sense to me, for you it could be simpler or more complex :) Also not necessary to use a class here, that's only me)

So when I need something that must always come last into its group but needs to be always loaded, that's what my declaration looks like:

Html.RequiresJs("myscript.js", CdfPriority.LastOfLast, CdfGroup.SiteWide);

(I know you'll probably ask why "Essentials" AND "SiteWide" since they seem to mean the same thing. It's a matter of priority - "Essentials" can be things like, for example, JQuery, while the "SiteWide" group can contain scripts that USE JQuery and have to be loaded afterwards (thus a greater group number than essentials).

I hope this post will give you a starting point for optimizing your JS and CSS delivery and getting better Page Speed rankings (meaning better overall SEO). As always, your opinions on this are more than welcome.

Happy coding!



;