Karl Seguin

Sponsors

The Lounge

Wicked Cool Jobs

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Compressing JS files as part of your build process

We love jQuery – many things are now easier/cleaner/more testable using JavaScript rather than ASP.NET (that statement blows my mind, but it's true!). Because of that, we have a lot of JavaScript code. Ideally, we want to develop using readable JavaScript files (even 3rd party files) and deploy compressed/obfuscated code. Our solution has simply been to apply a post-build task to our project. Here's what we did.

First, we use the .NET port of Yahoo's YUI Compressor, freely available on CodePlex. (note, the YUICompressor comes with an MSBuild Task, which you can use in addition (or as an alternative) to my approach).

We took the library and wrapped it into a simple console application which takes 2 inputs – the input directory containing the debug javascript files, and the target output directory to place the compressed files in:

using System;
using System.IO;
using Yahoo.Yui.Compressor;

public class Program
{
   public static void Main(string[] args)
   {
      var input = args[0];
      var output = args[1];
   
      if (!Directory.Exists(output))
      {
         throw new ArgumentException(string.Format("The output directory '{0}' doesn't exist", output));
      }
      if (Directory.Exists(input))
      {
         CompressDirectory(input, output);                
      }
      else if (File.Exists(input))
      {
         CompressFile(input, output);
      }
      else
      {
         throw new ArgumentException(string.Format("{0} isn't a known directory or file", input));
      }            
   }
   public static void CompressDirectory(string root, string outputDirectory)
   {
      Console.WriteLine("Compressing all .js files within: " + root);
      foreach (var file in Directory.GetFiles(root, "*.js"))
      {
         CompressFile(file, outputDirectory);
      }
      foreach (var directory in Directory.GetDirectories(root))
      {
         //skip .svn folders and what not
         if ((new DirectoryInfo(directory).Attributes & FileAttributes.Hidden) != FileAttributes.Hidden)
         {
            var newOuputDirectory = outputDirectory + directory.Substring(root.Length) + "\\";
            CompressDirectory(directory, newOuputDirectory);
         }
      }
   }
   public static void CompressFile(string file, string outputDirectory)
   {
      var name = Path.GetFileName(file);
      Console.WriteLine("Compressing file: " + name);            
      using (var sw = new StreamWriter(outputDirectory + name))
      using (var sr = new StreamReader(file))
      {
         var compressed = JavaScriptCompressor.Compress(sr.ReadToEnd(), false);
         sw.Write(compressed);
      }
   }
}

We compile our console application and place the .exe somewhere relative to our main solution. Next, we open our project's properties, go into the Build Events tab, and enter the following into the "Post-build event command line" box:

$(SolutionDir)..\Tools\YUICompressor\YUICompressor.exe $(ProjectDir)\content\js\debug\ $(ProjectDir)\content\js\release\

Now, whenever we build, all of our .js files within content/js/debug/ get compressed and moved to content/js/release/.

The last step is to use an HtmlHelper expression method to include our javascript file:

public static string IncludeJs(this HtmlHelper helper, string javascriptFile)
{
#if DEBUG
   var subfolder = "debug";
#else
   var subfolder = "release";
#endif
   var path = string.Format("{0}/Content/js/{1}/{2}.js",  _applicationPath, subfolder, javascriptFile);
   return string.Format("<script type=\"text/javascript\" src=\"{0}\"></script>", path);
}

Which can simply be used like:

<%= Html.IncludeJs("jquery") %>
<%= Html.IncludeJs("main") %>
<%= Html.IncludeJs("jquery_validate") %>

Posted Mon, Dec 29 2008 11:14 AM by karl

[Advertisement]

Comments

DotNetKicks.com wrote Compressing JS files as part of your build process
on Mon, Dec 29 2008 11:35 AM

You've been kicked (a good thing) - Trackback from DotNetKicks.com

Stephen wrote re: Compressing JS files as part of your build process
on Mon, Dec 29 2008 11:48 AM

Great idea, I think its nice to show people just how simple some things are to achieve.. sometimes we can forget that being software developers means we can .. write software to help ourselves.

Max Pool wrote re: Compressing JS files as part of your build process
on Mon, Dec 29 2008 2:38 PM

Well done - I need the exact same thing and have been putting it off for awhile.  Guess I have no excuse now but to get to work.

Paco wrote re: Compressing JS files as part of your build process
on Mon, Dec 29 2008 2:45 PM

I wish you posted this last week! I didn't know about the .Net port and I spent a too much time figuring out how to integrate the Java version...

Website Directory - Arctic wrote Website Directory - Arctic
on Tue, Dec 30 2008 2:30 AM

Pingback from  Website Directory - Arctic

Reflective Perspective - Chris Alcock » The Morning Brew #253 wrote Reflective Perspective - Chris Alcock &raquo; The Morning Brew #253
on Tue, Dec 30 2008 2:58 AM

Pingback from  Reflective Perspective - Chris Alcock  &raquo; The Morning Brew #253

ASP.NET MVC Archived Buzz, Page 1 wrote ASP.NET MVC Archived Buzz, Page 1
on Tue, Dec 30 2008 4:35 AM

Pingback from  ASP.NET MVC Archived Buzz, Page 1

Johan wrote re: Compressing JS files as part of your build process
on Tue, Dec 30 2008 7:19 AM

Truly great, thanks.

Jerry wrote re: Compressing JS files as part of your build process
on Tue, Dec 30 2008 10:42 AM

Nice post.

Did you look into incorporating the -vsdoc.js/.debug.js hotfix (blogs.msdn.com/.../hotfix-to-enable-vsdoc-js-intellisense-doc-files-is-now-available.aspx)?

I'd want to name my files that I edit foo.debug.js then compress them to foo.js.  Then the hotfix takes over and includes the right file.  Added advantage is that this can also be used in html files.

Then you could get rid of your IncludeJS helper.

karl wrote re: Compressing JS files as part of your build process
on Tue, Dec 30 2008 11:16 AM

@jerry:

Nope, didn't know about it. I don't use VS.NET for JS editing, find it crappy. Plain text editor for me. But should be easy to change anything around to fit your particular set-up.

Arjan`s World » LINKBLOG for December 30, 2008 wrote Arjan`s World &raquo; LINKBLOG for December 30, 2008
on Tue, Dec 30 2008 4:16 PM

Pingback from  Arjan`s World    &raquo; LINKBLOG for December 30, 2008

Brennan Stehling wrote re: Compressing JS files as part of your build process
on Tue, Dec 30 2008 5:10 PM

Have you seen Packer for .NET?

It provides MSBuild tasks and can compress with Packer or JSMin modes. Recently I got a contribution of CSSMinify to the project so you can compress CSS as well now. You can either do it at build time or use the assembly to do it at runtime.

www.smallsharptools.com/.../Packer

Peter Mounce wrote re: Compressing JS files as part of your build process
on Tue, Dec 30 2008 6:36 PM

Your Html.IncludeJs registers a single script-block per file?  You'll get significantly better performance (both in terms of number of requests going down, and in terms of compression efficiency) if you, in release mode, include one set of javascripts minified and globbed together into a single file.   See www.julienlecomte.net/.../13 and compar your approach as now to a one-minified-file approach with Firebug/YSlow.

You can do the one-minified-file approach by changing the helper to add the path to a set (as in, set-semantics list), and then (I'm coming from MonoRail, not MS-MVC) write out the script includes as an after-rendering filter - in debug mode, write out one include per file, linking against the debug copies; in release mode, write out a single include that links against the set of scripts, minified.

Except you can't do that (well, you _can_, but... ;-)  ) with a build-time task, because it would need to know all of the possible combinations of script-includes that your app uses.  I think in this case if you care enough to be compressing in the first place (ie, you need to scale / want responsive UI), you can probably eat the production-CPU-cycles and be better off doing this at run-time, not build-time.

For interest, there is a most-of-the-way-there patch to MonoRail that creates a ViewComponent that does most of this - it lacks a debug-mode switch, currently.  You can see that at support.castleproject.org/.../MR-ISSUE-457.

Timothy wrote re: Compressing JS files as part of your build process
on Wed, Dec 31 2008 10:25 AM

I'm not much of a fan of JQuery (MooTools all the way! Or at least Prototype...). But, yea, YUICompressor should be used in every project.

In fact, everyone should get the YSlow extension for Firebug to see what can be done to improve performance.

Visual Studio Hacks wrote Visual Studio Links #94
on Wed, Dec 31 2008 12:41 PM

My latest in a series of the weekly, or more often, summary of interesting links I come across related to Visual Studio. New in Visual Studio Gallery: Delphi Prism - Delphi programming for .NET in Visual Studio. The Web Developer Tools Team posted an

DotNetShoutout wrote Compressing JS files as part of your build process
on Sat, Jan 3 2009 7:19 PM

Thank you for submitting this cool story - Trackback from DotNetShoutout

DotNetShoutout wrote Compressing JS files as part of your build process
on Sat, Jan 3 2009 7:19 PM

Thank you for submitting this cool story - Trackback from DotNetShoutout

#.think.in wrote #.think.in infoDose #14 (22nd Dec - 2nd Jan)
on Sun, Jan 4 2009 4:49 PM

#.think.in infoDose #14 (22nd Dec - 2nd Jan)

Pure Krome wrote re: Compressing JS files as part of your build process
on Wed, Jan 21 2009 11:25 PM

@Karl: Kewl :) I'm stoked that you like (and are even using) the port i did! I was so surprised that no one had any .NET code or even a port of something existing! I found that lots of people just installed Java and integrated that into their build process. EEEEK!!! I couldn't think of anything worse! Anyways, that's the reason why i did it and dropping 12 or so js files down to one, as well as minifying them was a great speed improvement to our site.

@ Timothy : exactly dude. I couldn't agree more, regardless if you use my port or the original or someone elses.

Keep smiling :)

Sergejus apie .NET wrote Automatinis JavaScript Debug/Release versijos parinkimas � 2 dalis
on Fri, Feb 20 2009 4:03 PM

Prie&scaron; tai apra&scaron;ytas b�das automati&scaron;kai formuoti korekti&scaron;k� nuorod� iki suspausto arba nesuspausto JavaScript fail...

Web Development Community wrote Compressing JS files as part of your build process - Karl Seguin
on Fri, Feb 27 2009 5:32 AM

You are voted (great) - Trackback from Web Development Community

Shay Jacoby wrote re: Compressing JS files as part of your build process
on Wed, Mar 4 2009 6:15 PM

Thanks for the lib, I was 1 step before installing java jvm in order to use the yahoo original java jar.

In you console application code above the directories are not created as they should.

It must create automatically target folders if not exists, I modified the code to:

using System;

using System.IO;

using Yahoo.Yui.Compressor;

namespace YuiCompressor

{

   public class Program

   {

      /// <summary>

      /// $(SolutionDir)Tools\YUICompressor\bin\Debug\YUICompressor.exe $(ProjectDir)Scripts\ $(ProjectDir)Scripts\release\

      /// </summary>

      /// <param name="args"></param>

      /// <exception cref="ArgumentException"><c>ArgumentException</c>.</exception>

       public static void Main(string[] args)

       {

           /*

           args = new string[2];

           args[0] = @"D:\www\Framework\vr\Scripts\";

           args[1] = @"D:\www\Framework\vr\Scripts.Publish\";

            */

           var input = args[0];

           var output = args[1];

           if (!Directory.Exists(output))

           {

               //throw new ArgumentException(string.Format("The output directory '{0}' doesn't exist", output));

               Console.WriteLine("Create Directory: " + output);

               Directory.CreateDirectory(output);

           }

           if (Directory.Exists(input))

           {

               CompressDirectory(input, output);

           }

           else if (File.Exists(input))

           {

               CompressFile(input, output);

           }

           else

           {

               throw new ArgumentException(string.Format("{0} isn't a known directory or file", input));

           }

           Console.WriteLine("Finished to compress all .js files.");

       }

       public static void CompressDirectory(string root, string outputDirectory)

       {

           if (!Directory.Exists(outputDirectory))

           {

               //throw new ArgumentException(string.Format("The output directory '{0}' doesn't exist", output));

               Console.WriteLine("Create Directory: " + outputDirectory);

               Directory.CreateDirectory(outputDirectory);

           }

           Console.WriteLine("Compressing all .js files within: " + root);

           foreach (var file in Directory.GetFiles(root, "*.js"))

           {

               CompressFile(file, outputDirectory);

           }

           foreach (var directory in Directory.GetDirectories(root))

           {

               //skip .svn folders and what not

               if ((new DirectoryInfo(directory).Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)

                   continue;

               var newOuputDirectory = outputDirectory + directory.Substring(root.Length) + "\\";

               CompressDirectory(directory, newOuputDirectory);

           }

       }

       public static void CompressFile(string file, string outputDirectory)

       {

           var name = Path.GetFileName(file);

           Console.WriteLine("Compressing file: " + name);

           using (var sw = new StreamWriter(outputDirectory + name))

           using (var sr = new StreamReader(file))

           {

               var compressed = JavaScriptCompressor.Compress(sr.ReadToEnd(), false);

               sw.Write(compressed);

           }

       }

       /*

       public static string IncludeJs(this HtmlHelper helper, string javascriptFile)

{

#if DEBUG

  var subfolder = "debug";

#else

  var subfolder = "release";

#endif

  var path = string.Format("{0}/Content/js/{1}/{2}.js",  _applicationPath, subfolder, javascriptFile);

  return string.Format("<script type=\"text/javascript\" src=\"{0}\"></script>", path);

}

<%= Html.IncludeJs("jquery") %>

<%= Html.IncludeJs("main") %>

<%= Html.IncludeJs("jquery_validate") %>

       */

   }

}

Karl Seguin wrote ASP.NET Performance - Part 2 - YSlow
on Fri, Jan 8 2010 10:44 AM

ASP.NET Performance - Part 2 - YSlow In part 1 we looked at integrating a javascript compressor as part

Cadred.NET wrote .NET Pulse
on Fri, Jan 8 2010 12:42 PM

.NET Pulse

Karl Seguin wrote ASP.NET Performance - Part 3 - Cache Busting
on Mon, Jan 11 2010 8:53 AM

In part 2 of the series we looked at ways to tweak our headers to maximize performance. One of those

uberVU - social comments wrote Social comments and analytics for this post
on Tue, Jan 12 2010 3:49 AM

This post was mentioned on Twitter by 81bronco: @karlseguin Is this the blog post you were referring to? http://bit.ly/12UNJl

Dan Maharry wrote links for 2010-01-14
on Fri, Jan 15 2010 12:04 AM

links for 2010-01-14

Dan Maharry wrote Today's Bookmarks (Jan 14 2010)
on Sun, Jan 17 2010 6:57 AM

Today's Bookmarks (Jan 14 2010)

Devlicio.us