Minifying CSS & JS in ASP.NET MVC application
Tags: ASP.NET MVC June 10th, 2011In my project I have many small JavaScript files containing mostly jQuery plugins. Also there are many CSS files, because I prefer to split my CSS declarations to multiple files based on some context. All this results in many static files that must be loaded in every page load which increases latency.
So I decided to combine multiple files into one single file and also minify it. I looked for some libraries that would get the job done for me and I stick with YUI Compressor which requires Java runtime. I still want to step through readable javascript when I’m developing the app so I decided to combine and minify files only when I’m compiling in release mode, but we will get to that point later on. There were two action I needed to take to achieve this. I needed to modify the web project to include after build action for combining files and to write some kind of extension that’s either loads debuggable source files or compressed files. None of the steps were hard.
Post build step is actually two actions – temporarily combining multiple files into a single file and then minifying that file with YUI Compressor. It gets triggered only when building in a release mode. All the required tools including YUI Compressor are included in VCS so that all dependencies are there when developer checks out code.
<PropertyGroup> <yuiCompressor>java -jar ..\..\Tools\yuicompressor\yuicompressor-2.4.2.jar</yuiCompressor> </PropertyGroup> <Target Name="AfterBuild"> ... <CallTarget Targets="Minimize" Condition="'$(Configuration)' == 'Release'" /> </Target> <Target Name="Minimize"> <GetAssemblyIdentity AssemblyFiles="bin\$(ProjectName).dll"> <Output TaskParameter="Assemblies" ItemName="assemblyInfo" /> </GetAssemblyIdentity> <ItemGroup> <JqJsFiles Include="Content\Scripts\jquery.*.js;Content\Scripts\json2.js;" /> </ItemGroup> <ItemGroup> <OldVersions Include="Content\Scripts\jq.plugins.min-*.js" /> </ItemGroup> <!-- js Merge and Minimize --> <ReadLinesFromFile File="%(JqJsFiles.Identity)"> <Output TaskParameter="Lines" ItemName="jqJsLines" /> </ReadLinesFromFile> <WriteLinesToFile File="Content\Scripts\jq.merged.js" Lines="@(jqJsLines)" Overwrite="true" /> <Delete Files="@(OldVersions)" /> <Exec Command="$(yuiCompressor) --type js "$(ProjectDir)Content\Scripts\jq.merged.js" -o Content\Scripts\jq.plugins.min-%(assemblyInfo.Version).js --charset utf-8" /> <Delete Files="Content\Scripts\jq.merged.js" /> </Target> <PropertyGroup> <CopyAllFilesToSingleFolderForPackageDependsOn> CollectMinifiedFilesInPackage; $(CopyAllFilesToSingleFolderForPackageDependsOn); </CopyAllFilesToSingleFolderForPackageDependsOn> </PropertyGroup> <Target Name="CollectMinifiedFilesInPackage" Condition="'$(Configuration)' == 'Release'"> <ItemGroup> <_JsFiles Include="Content\Scripts\jq.plugins.min-*.js" /> <FilesForPackagingFromProject Include="%(_JsFiles.Identity)"> <DestinationRelativePath>Content\Scripts\%(Filename)%(Extension)</DestinationRelativePath> </FilesForPackagingFromProject> </ItemGroup> </Target>
As you can see, I include assembly version number (which is generated by build) in the names of the compressed files. This way I can always refer to correct JS files which are accordance with the given assembly. There’s also a target called CopyAllFilesToSingleFolderForPackageDependsOn which is required to include those compressed files in the MSDeploy package because they are not included in the web project and therefore not automatically included.
For the other part I wrote a partial view to load CSS & scripts files based on the configuration. And I use the partial view in my layout files (or wherever I need) to include all required resources. A part of the view:
@if (HttpContext.Current.IsDebuggingEnabled) { @Html.Script("~/Content/Scripts/jquery-1.4.4.js") @Html.Script("~/Content/Scripts/jquery-ui-1.8.9.custom.min.js") @Html.Script("~/Content/Scripts/jquery.ui.datepicker-en-GB.js?" + DateTime.Now.Ticks) @Html.Script("~/Content/Scripts/jquery.selectboxes.js?" + DateTime.Now.Ticks) @Html.Script("~/Content/Scripts/jquery.tooltip.js?" + DateTime.Now.Ticks) @Html.Script("~/Content/Scripts/json2.js?" + DateTime.Now.Ticks) } else { @Html.Script("~/Content/Scripts/jquery-1.4.4.min.js") @Html.Script("~/Content/Scripts/jquery-ui-1.8.9.custom.min.js") @Html.Script(string.Format("~/Content/Scripts/jq.plugins.min-{0}.js", Html.AssemblyVersion())) }
Side note: I append the timestamp at the end of the file names to prevent any caching issues in development environment
I make the decision which files to load based on the debug configuration in the web.config, not based on the build configuration in the compile time. I also deploy all the resource files (compressed and original) so I am able to switch between them easily, without recompiling and deploying.
That’s it. No custom out-of-the-solution build scripts needed. And the solution can be also applied to ASP.NET Webforms applications with little modification in including files.
June 16th, 2011 at 08:43
There’s also a .NET assembly that is a port of the java code.
http://yuicompressor.codeplex.com
and it’s on nuget No more java required to be installed!
June 16th, 2011 at 09:50
Thanks, I’ll check it out