While developing our products we use wide variety of technologies and tools. Some of those technologies are now obsolete and were superseded with new ones, some were improved over time and some still do the job.

With this series of articles we'll try to uplift the curtain and share our experience. It's not an ultimate HOWTO; it's an attempt to describe how WE do it. So, here we go!

Typical web site consist of static HTML pages, or HTML code generated dynamically and additional resources requested and loaded by HTML pages, such as images, style sheets (CSS) and JavaScript files. We'll speak about last two today.

Browser sends the request to the server and receives the response over the network. While some extra bytes does not seem significant, if you count the traffic generated by millions of requests you'll end up with megabytes of extra network load and hours of extra time. You can squeeze bytes by configuring your web server to compress the response. By why not squeeze few more bytes by removing letters and digits, spaces and punctuations that are not needed for web site operation? This is the goal number one.

Also, JavaScript and CSS are languages each with own syntax and rules. Normally they are interpreted when requested by the browser. Some browsers report syntax errors, some do not. Moreover obeying certain simple programming rules help to avoid common pitfalls and diagnose hard-to-find runtime errors – errors that pop up when the code is executed; but some code executed rarely. So, here is the goal number two: provide syntax check early in the development process, before deploying the files on web server.

There are number of tools to do this. Microsoft's Ajax Minifier aka AjaxMin, Yahoo's YUI Compressor, Google's Closure Compiler, JSLint, JSMin and many others; all of different quality, performance and feature set.

In terms of quality and feature set first three tools are winners – all of them provide a lot of checks and optimizations such as renaming local symbols, dead code elimination etc. Yet, to simplify build process configuration process we wanted to use single tool for both CSS and JavaScript syntax check and compression. This requirement narrowed the choice to first two as Closure Compiler does not support CSS validation and compression. As we are using Microsoft tool chain, MSBuild in particular, we have chosen Ajax Minifier since it is packaged with extra goodies to make integration with build process easier – no surprise.

Now, to the build process: in production environment everything is built for efficiency. For developers it's easier to maintain multiple, smaller files than one big file, yet, each request to the file take some network resources, so you'll likely want your files to be minified and concatenated together to minimize the number of requests needed to load all necessary resources. Moreover while some tools allow performing minification on the fly upon request we would prefer to do it prior to deployment to reduce web server's load.

In the debug build you'll do not care about network, but you'll want to have your original source code intact: all modern browsers have debug console were errors are reported and in case of runtime error it's better to see your own code than some hyper-crunched nonsense.

Speaking of concatenation keep in mind that you may need to control the order files are appended to each other as former can define some rules, functions and variables used in latter.

To control the order of concatenation and set of files to concatenate we end up with creating a sort of "project files" with .cssproj extension for CSS output and .jsproj extension for JavaScript code. Each project file results to a single output file and lists one source file per line such as:

compat.js
jquery/jquery.js
jquery/jquery-ui.js
fs/fs.namespace.js
fs/fs.datepicker.js
fs/fs.colorpicker.js
fs/fs.cssresize.js

In debug build each file should be validated and the output file contains "include" directives of original source files (via CSS @import directive or via document.write("<script …>") calls). To avoid filename clash, we name output file as "projectname.debug.js" (or.css, depending on project type). In production build the output files contain minified and concatenated content of each file listed in the project file, the output is "projectname.min.js". Site code is referencing either .debug or .min version of the output depending on build switches.

Ideally, in debug build any changed JS/CSS file should be re-validated. The output file should be re-generated only if the project file was changed, since output file does not contain source code content. In production build output file should be regenerated if the project file or any of the files listed in the project were changed. Yet, ideal implementation would require extra efforts. Since minification assumes validation as well, in a build process we do not differentiate between debug and production builds and produce both .debug and .min version of the files. Moreover, we trigger rebuild if ANY (not necessary those listed in the project) of .js or .css files were changed; we spend more time working on server code and few extra seconds of build time is the price we ready to pay.

Finally, while there is a special AjaxMin task for MSBuild we have decided to use command line version of the tool since it produces error reports in better format (as of version 4.23). Since there is nothing special about command line, with a little modification you can easily substitute AjaxMin with your favorite compression tools.

All build definitions are placed in a separate Minify.targets file.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<ItemGroup Label="Minifiers">

<JsProject Include="**\*.jsproj" />

<JsFiles Include="**\*.js" Exclude="**\*.debug.js;**\*-vsdoc.js;**\*.min.js"/>

<CssProject Include="**\*.cssproj" />

<CssFiles Include="**\*.css" Exclude="**\*.debug.css;**\*.min.css"/>

</ItemGroup>

<PropertyGroup>

<AjaxMinPath>"$(MSBuildExtensionsPath32)\..\Microsoft\Microsoft Ajax Minifier\AjaxMin.exe"</AjaxMinPath>

<AjaxMinJsOpts>-js</AjaxMinJsOpts>

<AjaxMinCssOpts>-css</AjaxMinCssOpts>

<AjaxMinOpts>-silent -clobber -term</AjaxMinOpts>

<JsDebugSuffix>.debug.js</JsDebugSuffix>

<JsReleaseSuffix>.min.js</JsReleaseSuffix>

<CssDebugSuffix>.debug.css</CssDebugSuffix>

<CssReleaseSuffix>.min.css</CssReleaseSuffix>

</PropertyGroup>

<Target Name="CleanProjects" AfterTargets="Clean">

<Message Text="Cleaning .js and .css" Importance="high" />

<Delete Files="@(CssProject->'%(RelativeDir)%(Filename)$(CssDebugSuffix)');@(CssProject->'%(RelativeDir)%(Filename)$(CssReleaseSuffix)')" />

<Delete Files="@(JsProject->'%(RelativeDir)%(Filename)$(JsDebugSuffix)');@(JsProject->'%(RelativeDir)%(Filename)$(JsReleaseSuffix)')" />

</Target>

<Target Name="BuildCssProject" AfterTargets="Compile" Inputs="@(CssProject);@(CssFiles)" Outputs="%(CssProject.RelativeDir)%(CssProject.FileName)$(CssDebugSuffix);%(CssProject.RelativeDir)%(CssProject.FileName)$(CssReleaseSuffix)">

<ReadLinesFromFile File="%(CssProject.Identity)">

<Output TaskParameter="Lines" ItemName="CssLines" />

</ReadLinesFromFile>

<PropertyGroup>

<CssProjPath0>%(CssProject.RelativeDir)</CssProjPath0>

<CssProjPath>$(CssProjPath0)</CssProjPath>

<CssOutFileName>$(CssProjPath)%(CssProject.Filename)</CssOutFileName>

<CssDebugFile>$(CssOutFileName)$(CssDebugSuffix)</CssDebugFile>

<CssReleaseFile>$(CssOutFileName)$(CssReleaseSuffix)</CssReleaseFile>

<CssImports>@(CssLines->'@import &quot;%(Identity)&quot;%3B&#13;&#10;','')</CssImports>

<CssInFiles>@(CssLines->'$(CssProjPath)%(Identity)',' ')</CssInFiles>

</PropertyGroup>

<Message Text="%(CssProject.Identity)" Importance="high"/>

<WriteLinesToFile File="$(CssDebugFile)" Lines="$(CssImports)" Overwrite="true" />

<Exec Command="$(AjaxMinPath) $(AjaxMinCssOpts) $(AjaxMinOpts) $(CssInFiles) -out $(CssReleaseFile)" />

</Target>

<Target Name="BuildJsProject" AfterTargets="Compile" Inputs="@(JsProject);@(JsFiles)" Outputs="%(JsProject.RelativeDir)%(JsProject.FileName)$(JsDebugSuffix);%(JsProject.RelativeDir)%(JsProject.FileName)$(JsReleaseSuffix)">

<ReadLinesFromFile File="%(JsProject.Identity)">

<Output TaskParameter="Lines" ItemName="JsLines" />

</ReadLinesFromFile>

<PropertyGroup>

<JsProjPath0>%(JsProject.RelativeDir)</JsProjPath0>

<JsProjPath>$(JsProjPath0)</JsProjPath>

<JsOutFileName>$(JsProjPath)%(JsProject.Filename)</JsOutFileName>

<JsDebugFile>$(JsOutFileName)$(JsDebugSuffix)</JsDebugFile>

<JsReleaseFile>$(JsOutFileName)$(JsReleaseSuffix)</JsReleaseFile>

<JsImports>(function(d){&#13;&#10;@(JsLines->'d.write(&quot;&lt;script src=\&quot;&quot;+gRootURL+&quot;/$(JsProjPath)/%(Identity)\&quot;&gt;&lt;\/script&gt;&quot;)%3B','&#13;&#10;')&#13;&#10;})(document)%3B&#13;&#10;</JsImports>

<JsInFiles>@(JsLines->'$(JsProjPath)%(Identity)',' ')</JsInFiles>

</PropertyGroup>

<Message Text="%(JsProject.Identity)" Importance="high"/>

<WriteLinesToFile File="$(JsDebugFile)" Lines="$(JsImports)" Overwrite="true" />

<Exec Command="$(AjaxMinPath) $(AjaxMinJsOpts) $(AjaxMinOpts) $(JsInFiles) -out $(JsReleaseFile)" />

</Target>

</Project>

Import this file to your project file as

<Import Project="Properties/Minify.targets" />

and enjoy!

Author
Date
Share