As part of some recent work I did it was decided that some Javascript files we had written needed ‘minifying’ as part of a build step. For those who have never heard of that term, it’s possible to dramatically reduce the size of Javascript files (whilst keeping the behavior unchanged) by performing certain steps against the file; removing unnecessary white space, comments, line breaks, etc.

Microsoft Ajax Minifier

We ended up using a tool from Microsoft, available here, that allows for minification of both JS files and css files, perfect for us. Once downloaded, the tool has a simple command line usage:

>ajaxmin.exe sourcefile.js -out outputfile.js

This is perfect for experimenting with the tool and validating the output, something I did early on to ensure there wouldn’t be any issues with the resultant files.

Once downloaded, the tool also includes an MSBuild task that can be imported and used in Visual Studio projects to automatically minify your JS and CSS files as part of a project build, something we needed. My requirements were a bit more complicated however, due to the fact that I didn’t want the minified files in source control, I required the files to maintain the same name, and required the minified files to be spat out in to a new ‘output‘ folder as part of the build. Below are the steps I took to achieve this:

Set up the project to use AjaxMinTask.dll (The MSBuild task)

The target project for us was an existing ASP.NET Web Application that contained a couple of web pages and our target JS files. With Visual Studio open, the first thing to do is to unload the project so we can manually edit the .csproj file. In order to do this, you simply right-click on the project and select ‘Unload Project‘. With the project now unloaded, you should be able to right-click the project again and now select ‘Edit myProj.csproj’ (Or whatever your project is named).

When the resultant file opens, scroll down near to the bottom and you should see a piece of commented out text:

— To modify your build process, add your task inside one of the targets below and uncomment it.

It’s that commented out block of text that we need to replace with our custom build step. Let’s build up my minification soloution step by step:

Step 1: Removing any existing Minified Files (Cleaning)

Because of some limitations with our source control and the version of AjaxMin we are forced to use, it’s not possible for us to overwrite any existing minified files, so the first thing we do is to blow away any existing minified files. Add the following MSBuild target to the .csproj file, directly overwriting the commented out text mentioned above:

<!– STEP 1: DELETE THE OUTPUT DIR IF IT ALREADY EXISTS –>
<Target Name=”AfterBuild”>
<RemoveDir Directories=”$(MSBuildProjectDirectory)\Resources\OUTPUT\” ContinueOnError=”True” />
<CallTarget Targets=”CopyJSAndCSS” />
</Target>

Let me explain for a moment what the above block does. The Target element simply describes one or more ‘tasks’ which will run as a group, perhaps to rename some files, move some files, or in the case above, delete some files. The attribute

Name=”AfterBuild”

Ensures that this ‘target’ will run after the project has been built. You’ll see later that my other ‘Target’ blocks have different names, but this first one, the entrypoint if you will, has the reserved name ‘AfterBuild.

The next line calls a built-in MSBuild task ‘RemoveDir’ and passes it a collection of directories (folders) to delete. Here, we only pass it one folder, our ‘Output’ folder that we will later spit out our minified files to. The use of the built in property ‘$(MSBuildProjectDirectory)’ provides a way to get the actual path of the current csproj project file, which out Output file is relative to. The attribute ‘ContinueOnError’ ensures that even if the folder doesn’t yet exist, the task will complete happily.

The final line explicitly calls our next ‘Target’ by name. In this way we can chain together multiple Targets ensuring they get run in the order we want. Here, once this target has completed removing the Output folder we call the next target, ‘CopyJSAndCSS’:

Step 2: Copying JS/CSS files ready for minification

Add this next target directly under the one you previously added:

<!– STEP 2: RECURSIVELY COPY .JS & .CSS FILES TO OUTPUT DIR, MAINTAINING DIR STRUCTURE –>
<Target Name=”CopyJSAndCSS”>
<ItemGroup>
<SourceFilesToCopy Include=”$(MSBuildProjectDirectory)\Resources\**\*.js;$(MSBuildProjectDirectory)\Resources\**\*.css” />
</ItemGroup>
<Copy SourceFiles=”@(SourceFilesToCopy)” DestinationFiles=”@(SourceFilesToCopy->’$(MSBuildProjectDirectory)\Resources\OUTPUT\%(RecursiveDir)%(FileName)%(Extension)’)” />
<CallTarget Targets=”MinifyFiles” />
</Target>

This next target handles copying our JS and CSS files into our Output folder, ready to be minified. The ‘ItemGroup’ element in the above target allows us to define one or more user defined  ‘Items’, each with their own attributes. In the ItemGroup above I have defined an object ‘SourceFilesToCopy’ that has an ‘Include’ attribute, an attribute required by a later task I will call that takes an item describing a list of files. The ‘Include’ attribute above actually describes two sets of files, my JS files and my CSS files, seperated by a semicolon. the \**\*.js and \**\*.css syntax ensures that we match all the files under the ‘Resources’ folder, including all files in any subfolders.

With the ‘SourceFilesToCopy’ item set up, we call another MSBuild built-in task, ‘Copy’. The ‘Copy’ line above takes an input (SourceFilesToCopy) and copys files to the ‘DestinationFiles’ attribute. The syntax in that attribute is simply performing a recursive copy, preserving the folder hierarchy, file name and extension. In effect this simply mirrors the folder and file structure of the ‘Resources’ folder to the ‘Output’ folder. With this Task complete, we call our next Target, ‘MinifyFiles’:

Step 3: Minification

Add the next Target directly below the last:

<!– STEP 3: MINIFY JS & CSS –>
<UsingTask TaskName=”AjaxMin” AssemblyFile=”$(SolutionDir)..\OurCustomMSBuildTasks\AjaxMin\AjaxMinTask.dll” />
<Target Name=”MinifyFiles”>
<ItemGroup>
<JSFilesToMinify Include=”$(MSBuildProjectDirectory)\Resources\OUTPUT\**\*.js” />
<CSSFilesToMinify Include=”$(MSBuildProjectDirectory)\Resources\OUTPUT\**\*.css” />
</ItemGroup>
<AjaxMin JsKnownGlobalNames=”jQuery,$” JSSourceFiles=”@(JSFilesToMinify)” JSSourceExtensionPattern=”\.js$” JSTargetExtension=”.min_js” CssSourceFiles=”@(CSSFilesToMinify)” CssSourceExtensionPattern=”\.css$” CSSTargetExtension=”.min_css” />
<CallTarget Targets=”DeleteOrigFiles” />
</Target>

Ok, this one is a longer one, since it’s actually doing the minification, so lets look at it in a bit more detail:

1) (UsingTask) In this first line we explicitly reference a third-party MSBuild task, in our case the task that AjaxMin supplies when you download it from MIcrosoft’s website.

2) (ItemGroup) In this Item group, I set up two file collections as before; a list of JS files to minify, and a list of CSS files to minify.

3) (AjaxMin) Here I actually call the AjaxMin task. The documentation for the arguments/attributes I’m passing here are well documented on the Microsoft web page, but briefly;

– We ensure that ‘jQuery’ and ‘$’ literals do not get renamed as part of the minification process

– We pass a list of JS files to be minified

– We pass the extension of the JS files that the AjaxMin task should look for/target

– We pass the extension we want for the minified  JS files

– We repeat the above 3 steps for our CSS files too.

With the files minified to their new .min_js and .min_css counterparts, we call our next target, ‘DeleteOrigFiles’:

Step 4: Delete Non-minified (original) files

Add the next step directly below the last one:

<!– STEP 4: DELETE NON-MINIFIED JS/CSS IN OUTPUT DIR –>
<Target Name=”DeleteOrigFiles”>
<ItemGroup>
<OriginalJSFilesToDelete Include=”$(MSBuildProjectDirectory)\Resources\OUTPUT\**\*.js” Exclude=”$(MSBuildProjectDirectory)\Resources\OUTPUT\**\*.min_js” />
<OriginalCSSFilesToDelete Include=”$(MSBuildProjectDirectory)\Resources\OUTPUT\**\*.css” Exclude=”$(MSBuildProjectDirectory)\Resources\OUTPUT\**\*.min_css” />
</ItemGroup>
<Delete Files=”@(OriginalJSFilesToDelete)” />
<Delete Files=”@(OriginalCSSFilesToDelete)” />
<CallTarget Targets=”RenameMinifiedFiles” />
</Target>

This next Target should be self explanatory by now, but all it does is delete the original .js and .css files we copied over to the ‘Output’ directory, ensure we dont delete the .min_js/.min_css files by using the ‘Exclude’ attribute on the file collections we set up. After deleting the original files, we’re onto the last step!

Step 5: Rename minified files

Add this last step directly after the last one:

<!– STEP 5: RENAME *.MIN.* FILES –>
<Target Name=”RenameMinifiedFiles”>
<ItemGroup>
<MinifiedJSFilesToRename Include=”$(MSBuildProjectDirectory)\Resources\OUTPUT\**\*.min_js” />
<MinifiedCSSFilesToRename Include=”$(MSBuildProjectDirectory)\Resources\OUTPUT\**\*.min_css” />
</ItemGroup>
<Copy SourceFiles=”@(MinifiedJSFilesToRename)” DestinationFiles=”@(MinifiedJSFilesToRename->’$(MSBuildProjectDirectory)\Resources\OUTPUT\%(RecursiveDir)%(FileName).js’)” />
<Copy SourceFiles=”@(MinifiedCSSFilesToRename)” DestinationFiles=”@(MinifiedCSSFilesToRename->’$(MSBuildProjectDirectory)\Resources\OUTPUT\%(RecursiveDir)%(FileName).css’)” />
<Delete Files=”@(MinifiedJSFilesToRename)” />
<Delete Files=”@(MinifiedCSSFilesToRename)” />
</Target>

This last step handles renaming our minified (.min_js/.min_css) files back to thier original file extensions. The syntax follows that of one of our previous steps (The copying of the files in step 2) but overrides the file extension of the recursive copy:

<Copy SourceFiles=”@(MinifiedJSFilesToRename)” DestinationFiles=”@(MinifiedJSFilesToRename->’$(MSBuildProjectDirectory)\Resources\OUTPUT\%(RecursiveDir)%(FileName).js‘)” />

And that’s it! Now, when we build that porject, we get an Output folder created that is an exact copy of our ‘Resources’ folder, but with the JS and CSS files minified.