Javascript runtime compilation using Asp.Net and Google’s Closure Compiler


Working on complex javascript projects usually means working with lots of javascript files. When it comes time to deploy this to production it can be be very tedious and sometimes dangerous to do this manually. This solution compiles all the javascript files in a directory to a single minified file ideal for release. This code will also automatically recompile this file if any of the files change.


 /// This class will compile (using Google's Closure Compiler) a directory of javascript files if: /// - In Release Mode /// - The release (minified) file is older than other files in the directory (stale) /// - Not on localhost (Google cannot access your files) /// 

public class JavascriptRuntimeCompiler { private static readonly ILog log = LogUtil.Logger(typeof (JavascriptRuntimeCompiler)); private const string CACHE_MARKER = "CACHE_MARKER"; private readonly string releaseFileName; private readonly string baseScriptsUri; private readonly HttpContextBase context; private FileInfo releaseFile; ///
 /// Creates the JavascriptRuntimeCompiler. It is safe to create this per request as it is very efficient and /// will only create the new minified file if required. /// 


/// The relative file name of the release minified file. Eg: 'mydir\scripts\myscript.min.js' /// The relative uri of the directory holding the javascript files. Eg: '~/mydir/scripts/'. It is safe /// for this to be the same directory as the one holding the release file. /// /// The HttpContextBase object (Asp.Net Mvc) public JavascriptRuntimeCompiler(string releaseFileName, string baseScriptsUri, HttpContextBase context) { this.releaseFileName = context.Request.PhysicalApplicationPath + releaseFileName; this.baseScriptsUri = context.Request.Url.GetLeftPart(UriPartial.Authority) + baseScriptsUri; this.context = context; } ///
 /// Wether to use the release script or not. If true ensure your page points to your release file. If false /// your page should reference all of the debug scripts. /// 


public bool UseReleaseScript() { bool debug = false; #if (DEBUG) debug = true; #endif if (debug || baseScriptsUri.IndexOf("localhost") >= 0) { return false; } CheckReleaseScriptValidity(); return true; } private void CheckReleaseScriptValidity() { releaseFile = new FileInfo(releaseFileName); if (releaseFile.Exists && !IsReleaseFileOutOfDate()) { return; } RebuildReleaseScriptFile(); // This marker allows us to do a date check on all the files that the minified release file depends on context.Cache.Add(CACHE_MARKER, new Object(), new CacheDependency(GetJSFiles()), Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); } private bool IsReleaseFileOutOfDate() { if (context.Cache[CACHE_MARKER] != null) { return false; } DateTime releaseFileDate = releaseFile.LastWriteTime; foreach (string s in GetJSFiles()) { if (File.GetLastWriteTime(s) > releaseFileDate) { return true; } } return false; } private void RebuildReleaseScriptFile() { string uri = ""; foreach (string f in GetJSFiles()) { uri += "&code_url=" + baseScriptsUri + f.Substring(f.LastIndexOf("\\") + 1); } log.Debug("Requesting Compiler: " + uri); WebRequest r = WebRequest.Create(uri); r.Method = "POST"; r.ContentLength = 0; using (Stream s = r.GetResponse().GetResponseStream()) { using (StreamReader sr = new StreamReader(s, Encoding.UTF8)) { string content = sr.ReadToEnd(); log.Info(content); FileUtils.WriteFileContents(releaseFileName, Encoding.UTF8.GetBytes(content)); } } } private static string[] cached_js_files; private string[] GetJSFiles() { if (cached_js_files != null) return cached_js_files; List jsFiles = new List(); if (releaseFile.Directory == null) throw new ApplicationException(); foreach (FileInfo f in releaseFile.Directory.GetFiles()) { if (f.Name.Equals(releaseFile.Name) || f.Extension != ".js") { continue; } jsFiles.Add(f.FullName); } return cached_js_files = jsFiles.ToArray(); } }

Using this Class

This example uses Asp.Net Mvc and the Spark view engine. However it should be trivial to change the above file or this example to use any other framework.

<if condition="new JavascriptRuntimeCompiler('resources\\scripts\\custom\\scripts.min.js', Url.Content('~/resources/scripts/custom/'), Context).UseReleaseScript()">
    <script language="javascript" src="${ Url.Content('~/resources/scripts/custom/scripts.min.js') }"></script>
    <script language="javascript" src="${ Url.Content('~/resources/scripts/custom/Util.js') }"></script>
    <script language="javascript" src="${ Url.Content('~/resources/scripts/custom/Class1.js') }"></script>
    <script language="javascript" src="${ Url.Content('~/resources/scripts/custom/Class2.js') }"></script>
    <script language="javascript" src="${ Url.Content('~/resources/scripts/custom/Class3.js') }"></script>


Guido Tapia
Software Development Manager
PicNet Pty Ltd


About The Author

Guido Tapia

Over the last 2 years Guido has been involved in building 2 predictive analytics libraries for both the .Net platform and the Python language. These libraries and Guido's machine learning experience have placed PicNet at the forefront of predictive analytics services in Australia.

For the last 10 years Guido has been the Software and Data Manager at PicNet and in that time Guido has delivered hundreds of successful software and data projects. An experience architect and all round 'software guy' Guido has been responsible for giving PicNet its 'quality software provider' reputation.

Prior to PicNet, Guido was in the gaming space working on advanced graphics engines, sound (digital signal processing) engines, AI players and other great technologies.

Interesting Links:

Leave a Reply

Your email address will not be published. Required fields are marked *


You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <p> <ul> <li> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>