So after introducing the new minification process the first issue I see is that there's no way to tell it to include comments. Some comments are required, such as jQuery licences.

A little digging and I found out that Bundling in MVC just uses the Microsoft.Ajax.Utilities Minifier and I know that has more configuration options. I then had a look at the System.Web.Optimizations dll to see what the ScriptBundle was doing internally which turns out is pretty simple. So I created this little replacement which does pretty much the same thing except passing more options to the Minifier.

This is the new ScriptBundle, creates a bundle using our new Minifier instead of the default JsMinify.

internal sealed class CommentedScriptBundle : Bundle  
{
    public CommentedScriptBundle(string path)
        : base(path, (string)null, (IBundleTransform)new LicencedMinify())
    {
    }
}

Our new minifer to replace JsMinify, based on what JsMinify actually does. We can easilly add more options to this with CodeSettings to have far greater control over the minification process.

internal class LicencedMinify : IBundleTransform  
{
    private const string JsContentType = "text/javascript";

    private static void GenerateErrorResponse(
        BundleResponse bundle, 
        IEnumerable<object> errors)
    {
        var stringBuilder = new StringBuilder();
        stringBuilder.Append("//* ");
        stringBuilder.Append("Minification Error").Append("\r\n");
        foreach (var obj in errors)
            stringBuilder.Append(obj).Append("\r\n");
        stringBuilder.Append(" *//\r\n");
        stringBuilder.Append(bundle.Content);
        bundle.Content = stringBuilder.ToString();
    }

    public void Process(BundleContext context, BundleResponse response)
    {
        if (context == null)
            throw new ArgumentNullException("context");
        if (response == null)
            throw new ArgumentNullException("response");
        if (!context.EnableInstrumentation)
        {
            var minifier = new Minifier();
            var content = response.Content;
            var codeSettings = new CodeSettings
                {
                    EvalTreatment = EvalTreatment.MakeAllSafe,
                    RemoveUnneededCode = true,
                    PreserveImportantComments = true,
                    TermSemicolons = true
                };
            var str = minifier.MinifyJavaScript(content, codeSettings);
            if (minifier.ErrorList.Count > 0)
                GenerateErrorResponse(response, minifier.ErrorList);
            else
                response.Content = str;
        }
        response.ContentType = JsContentType;
    }
}

We now create our bundles like this:

bundles.Add(  
    new CommentedScriptBundle("~/Bundles/Master").Include(
        new[]
            {
                "~/Script/jquery-uncompressed.js"
                ...
            }));

Now with these, when we create a bundle the important script comments are left intact and we avoid any potential nasty legal issues. Yay

Working on http://hotelscombined.com.au we have a JS bundling process in place which is pretty interesting. There's an executable that runs as part of the TFS build that combines and minifies the JS files into specified a bundle file. It's a pretty old solution so obviously has legacy implications to changing anything.

Firstly we have to deal with affiliates and the white label version of the site. We must maintain backwards compatibility with these sites at all cost and we have a rather interesting http handler which serves a JS file bundle based on a requested file name.

So we have to maintain this lookup of legacy filenames to bundles but I really want to switch to the newer MVC bundling process, then I can look at introducing JSLint/JSHint locally and get build time errors.

Using the MVC bundling creating the bundles is pretty straight forward, I then replaced the links to our legacy JavaScript manager.

bundles.Add(  
    new ScriptBundle("~/Bundles/Master").Include(
        new[]
            {
                "~/Script/jquery-uncompressed.js"
                ...
            }));

Next step is the more complicated one. I created a legacy lookup dictionary containing the old file names and the newer bundle names so we maintain backwards compatibility. We then need to dynamically load the contents of a bundle and serve it via the handler, this also needs to serve minified and unminified versions depending on the environment.

This is the code that I came up with to handle this. It's still in proof of concept stage right now so may change.

var bundle = BundleTable.Bundles.FirstOrDefault(b => b.Path == bundleName);  
if (bundle != null)  
{

    var bundleContext = new BundleContext(
        context, BundleTable.Bundles, bundleName)
        {
            EnableOptimizations = true
        };
    var defaultBundleBuilder = new DefaultBundleBuilder();
    var bundleContent = defaultBundleBuilder.BuildBundleContent(
        bundle, bundleContext,
        bundle.EnumerateFiles(bundleContext));
    var response = new BundleResponse(
        bundleContent, bundle.EnumerateFiles(bundleContext))
        { ContentType = "application/javascript" };

    if (minify)
    {
        var minifier = new JsMinify();
        minifier.Process(bundleContext, response);
    }

    return response;
}
development, javascript, jquery

The codebase I'm currently working on uses $.extend quite a lot. It's a jQuery feature I've not used much myself.

I created a set of default options that could be overwritten by passed in options. This worked for the most part.

The issue I was having came when changing the passed in options to better support RTL, (switching left configured elements to be positioned right etc). I found that changing the position of the first would change the position of the second too.

It took me a while to track this one down, I originally assumed there was some kind of scope issue with the options. But it turns out $.extend doesn't quite do what I expected it would.

var defaults = {  
    param: {
        value1: 1,
        value2: 2
    }
}

var test = $.extend({}, defaults, { something: 'else' });  
var test2 = $.extend({}, defaults);

console.log(test2.param.value1); // = 1  
test.param.value1 = 3;  
console.log(test2.param.value1); // = 3  

I was expecting by default a deep copy. So that in the above scenario test.param and test2.param could be updated independantly.

Turns out what I needed to do in this case is this:

var test = $.extend(true, {}, defaults, { something: 'else' });  
var test2 = $.extend(true, {}, defaults);