Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bundling and Minifying Durandal Applications

I'm using MVC5/Durandal and wondering what the recommended approach to bundling/minifying a durandal application would be. Ive seen docs on using Weyland but will be deploying to an Azure Website and don't see how to leverage this in my .net-based deployment process. How can I go about configuring automated bundling/minification of my durandal application when deploying to Azure?

like image 397
SB2055 Avatar asked Mar 18 '23 03:03

SB2055


1 Answers

I've spent a bit of time trying to optimize an AngularJS application for one of the biggest banks in Holland. Although it's no Durandal, this might still give you some ideas.

So what did we use for bundling and minification? Out-of-the-box bundling and minifcation from ASP.NET MVC ( which is from the system.web.optimization namespace )

You need to get a couple of things in order to leverage this:

Organize your files

Organize your code files in a way that they can easily be bundled. We had a large tree structure under the /app folder in the web project. Something like:

- App      
   |- Modules
   |     |-Common
   |     |    |- Directives
   |     |    |- Templates
   |     |    |- Filters
   |     --User
   |          ...
   | app.js

So the application skeleton was inside the app.js and all the other JS files were required by the application. Point being: all SPA code is separated from vendor javscript files and the rest of course

Set up the budling inside the bundle configuration

That's a breeze now, just do regular-old-bundling from your Global.asax.cs: Make sure there's a line in the Application_Start() with: BundleConfig.RegisterBundles(BundleTable.Bundles);

That calls into your BundleConfig class which only needs 1 bundle to pack up the whole /app folder:

        bundles.Add(new ScriptBundle("~/bundles/app")
            .Include("~/app/*.js")
            .IncludeDirectory("~/app", "*.js", true));

We needed the app.js to load first - therefore we put it explicitly at the top. Don't worry, it will not be requested twice.

For bundling - only the sequence of files can be important. However, through including that file explicitly, we could control that and it worked like a charm.

Minification

Now for minification we had to do some code changes. AngularJS can be used with different types of syntax - some of which can be minified, others give problems.

Example:

angular.module('myapp').controller(function($http,$scope) { ... });

can not be minified. The minifyer will change the name of $http so something shorter, after which the injector cannot do dependency injection anymore, since it only knows stuff called $http and $scope and not the minified variable name.

So for Angular you need to use a different syntax:

angular.module('myapp').controller(['$http', '$scope', function($http,$scope) { ... }]);

With this, the injector will know that the first argument of the function is '$http' because that's the first string variable in the array. OK, but that's Angular and you're looking for Durandal.

I've heard that Durandal uses AMD right? So within a module, minification shouldn't be a problem, because it should be smart enough. However, if you're using external things, you want to make sure everything still works. I've read here that you'll want to use te following syntax for your AMDs:

define("someModule", ["jquery", "ko"], function($,ko) { ... });

And that gave us a reduction of 80% of the requests and around the same number for the Javascript payload.

Added AngularJS bonus

This might not be of interest to you, but maybe for other readers. The reason we didn't get a 99% reduction of requests is because AngularJS uses something called 'directives'. These are like HTML templates. Those HTML templates still needed to be downloaded every time they were used.

They were also included in our /app folder - hence we had to add an IgnoreRoute in the routeconfig:

routes.IgnoreRoute("app/");

I Googled, but couldn't find anything similair for Durandal. So Angular will go and get all of the small HTML files, but will first check its $templatecache. In case the HTML content is not in the cache, it goes out and downloads it and places it in the cache, so it needs to be downloaded only once.

We, well I, wrote a T4 generator that outputs a JS file in which all the HTML files in the /app folder are added to the $templatecache. So the output would look like:

angular.module('myapp').run(function($templateCache) {
  /// For all *.html files in the /app folder and its children
  $templateCache.put('...filename...', '...content of html file ...');
});

Because this .JS file was inside the /app folder, it would immediately get bundled with the application, no more configuration required. This got our requests down for the whole application to just 1. Since the amount of HTML was quite small, it seemed to be faster to do 1 larger request, then multiple smaller ones.

Point is: if Durandal has something similair and it will look for some templates, find the caching mechanism ( because it will have it ) and try to tap into that.

Controlling bundling and minification

I'll quote this site: http://www.asp.net/mvc/overview/performance/bundling-and-minification

Bundling and minification is enabled or disabled by setting the value of the debug attribute in the compilation Element in the Web.config file. In the following XML, debug is set to true so bundling and minification is disabled.

<system.web>
    <compilation debug="true" />
</system.web>

So for your release build - this flag shouldn't be set and thus bundling + minification should happen. But of course, you will want to test locally - you can either remove this from your web.config or override it with BundleTable.EnableOptimizations = true;

Deployment to Azure

Then you mention deployment to Azure. I don't know how this would be any different from deploying to a local server. We used web-deploy. Bundling and minification doesn't happen build-time, so there are no changes in the build process. Also, the optimization framework is being deployed with the site - so no difficult things for deployment either.

Maybe one thing though: you could consider adding the CDN for the libraries you are using:

bundles.Add(new ScriptBundle("~/bundles/jquery", "http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.min.js")

In case the CDN location of jQuery was already cached by the client's browser, you'll save another request.

Measuring the performance was easy: just open up the network tab on Chrome and reload the page ( make sure it's not caching ). Check the total number of requests and the total amount of data downloaded. Like I said: we saw a huge improvement.

Well, hope it helps or points you in a right direction.

like image 188
Jochen van Wylick Avatar answered Apr 01 '23 17:04

Jochen van Wylick