Monday, October 27, 2014

An argument for using WebGrease instead of the ASP.Net Web OptimizationFramework

ASP.Net Web Optimization was introduced to ease the minification and bundling of JavaScript and CSS files within your project. It's available out of the box with any new project that uses the MVC 4 project templates. In your App_Start folder, there's a file called BundleConfig where you configure what files should be minified and bundled into one file. Minification and bundling are a good thing of course, because they reduce the number of requests the browser has to make in order to retrieve the resources it needs to render a page, and they reduce the size of those resources.

But, what if you have an MVC 3 or WebForms project? What do you do? There are plenty of tutorials out there that will walk you through adding the ASP.Net Web Optimization NuGet package to your project, and the steps that it will take to get your project configured. This post however takes a different approach. What if we don't use the ASP.Net Web Optimization package at all... what if we just use WebGrease instead?

What's WebGrease you ask? Well, if you notice in the NuGet Package Manager window, WebGrease is a dependancy of the ASP.Net Web Optimization pacakage. WebGrease is what the System.Web.Optimization.dll utilizes to minify and bundle resources behind the scenes when you define bundles in your BundleConfig. You can actually do more with WebGrease than you can utilizing the System.Web.Optimization assembly. According to the WebGrease CodePlex site, WebGrease:
"optimizes a web application’s static files, including CSS, JavaScript, and image files. It includes the following capabilities:
  • Minimize CSS and JavaScript files
  • Bundle all static files into a single output file
  • Combine all images referenced in a CSS file into a single sprite
  • Verify JavaScript files
  • Rename static files based on a hash of their content
WebGrease can be run completely from the command line, or you can specify a WebGrease configuration file that contains the settings. WebGrease also includes a WebGrease build task that can be integrated with an MSBuild project."
To get started using WebGrease, add it to your project using NuGet. Search for "WebGrease".

After the package has been added to your project you will be able to utilize the WebGrease Executable in Pre and Post build operations.

Here is an example of calling the executable to minimize a CSS file:
$(SolutionDir)packages\WebGrease.1.3.0\tools\wg -m -in:$(ProjectDir)Content\themes\base\source\normalize.css -out:$(ProjectDir)Content\themes\base\source\normalize.min.css
The WebGrease executable accepts arguments but can also be executed in conjunction with a configuration file.
$(SolutionDir)packages\WebGrease.1.3.0\tools\wg -b -in:$(ProjectDir)Scripts\ -out:$(ProjectDir)Scripts\ -c:$(ProjectDir)Scripts\WebGrease.config -type:$(ConfigurationName)

So, why use WebGrease without the ASP.Net Web Optimization Package? Well, for one, you can create CSS Sprites using WebGrease, which is currently something that can't be done using the ASP.Net Web Optimization Package (There is currently a Sprite and Image Optimization Preview package available here.).

In my opinion, the biggest reason to go down this road, comes down to a shortcoming with ASP.Net WebForms.

I was recently working on a client project that was created using ASP.Net WebForms. Like many WebForms projects, a ScriptManager control had been added to the MasterPage to handle any necessary script management in the content pages. I initially added the ASP.Net Web Optimization package to the project in order to get some minification and bundling setup. However, I quickly ran into an issue....

You can't add code blocks to the declarative markup inside the Scripts collection of the ScriptManager control. In turn, this meant that I could not add the declarative call to output the script bundle created by the ASP.Net Web Optimization framework. And while I technically could have made all of this work by programmatically adding the output of the bundle to the ScriptManager, I really did not want to have to introduce dependencies on  the bundling process in the application.

Secondly, if you notice, I'm using the CompositeScript collection in the ScriptManager. This performs a similar function to WebGrease in that it bundles all of the scripts defined in its collection and outputs them as one script. It gets better though, because you can reference scripts that have been embedded as resource in assemblies, and combine them into one script. So, for example, in the following image we reference a bunch of the default scripts used by WebForms, the GridViewControl, MenuControl, Validation, and Microsoft Ajax (UpdatePanel), and all of these scripts will be loaded from their respective assembly, combined into one script, and then transferred to the browser as one file.

So how do you add your minimized and bundled project JavaScript files to the ScriptManager?

First, identify all of the scripts that you want to be included in your bundle. In this case, I'm using a bunch of Microsoft scripts that are embedded in assemblies. Instead of trying to extract them from the assemblies, just grab them from the CDN. Add them to your project along with your other JavaScript Files.

Second, create the necessary Pre Build commands to get WebGrease to minimize your JavaScript files.

Third, create the necessary Pre Build command to get WebGrease to bundle the various JavaScript files into a single file. In this example, I've used a configuration file with the WebGrease executable to bundle the minimized files from the various script folder and they are all bundled into the "FacilityOptimization.js" file.

Finally, reference the minimized and bundled JavaScript file in your ScriptManager's CompositeScript Path property. There seems to be some confusion on the web that this property can be used to set the name of the script that will be sent to the browser. This is incorrect. The correct usage of the Composite Script Path property is to set the path to a script file on the file system that contains all of the scripts defined in the CompositeScript Scripts collection, whether they exist on disk or as a resource in an assembly. This way, the ScriptManager knows that the scripts that would normally be loaded from assemblies will now be loaded from the script identified by the CompositeScript Path property.

So what does this get you? Here's what the requested resources looked like before optimization:

Here's what the requested resources looked like after optimization:

The pages have considerably fewer resources that need to be loaded, and those resources are comparatively smaller, which leads to faster page loads.

To sum up, I believe there are situations that warrant straying from the path and utilizing different methods that best address the situation. In this case, moving away from the ASP.Net Web Optimization framework and utilizing WebGrease was the right solution. It does however take considerably more effort, and is ultimately more brittle than using the web optimization framework.

So, if you find yourself in a similar situation, consider dropping the ASP.Net Web Optimization framework and just stick with WebGrease!

No comments:

Post a Comment