Saturday, July 25, 2009 2:54 AM Kazi Manzur Rashid

JavaScript File Management

As many of you know that I am currently involved in developing few UI Components for the ASP.NET MVC Framework (Hint: It is not a personal project, and we do have the plan to make it the source open dual license). In this post, I will discuss about our design decisions regarding how we plan to manage the javascript files with our UI Components.

When developing a typical web application we usually find four kinds of javascript files.

  • Framework scripts like jQuery, ExtJS or maybe MS Ajax etc.
  • UI Component/Plug-in scripts like jQuery UI, jQuery Tools, jQuery validation/forms plug-ins etc that depends upon the framework script.
  • Application level common scripts that are shared among multiple pages and might depends upon the above two.
  • Page scripts(not embedded in html, rather as external file) that might depends upon on the above three.

To make the application really responsive/fast we often have to merge/compress/cache these javascript files. Currently most of the framework in .NET world (including the latest ASP.NET AJAX 3.5) supports combining the scripts in a single response. But this is not an optimal option in most of the cases. Why? Because we are either downloading the same file content in different pages or we are downloading some unnecessary file content for a specific page (assuming that you have specified all your merged script in your master page). Certainly it is a one time issue, once the file is downloaded it will be cached and the user does not have to download it again, but does not it also indicate the incapability of your script management components, also this is not viable option in today's heavily ajax sites.. There are also few other considerations like how can I load the scripts from a CDN (free/paid), does it render the scripts at the bottom of the pages etc etc. While considering all the above facts, we think the best way to serve scripts is, if it is merged in groups. Lets consider the following scenario, each url is using the listed javascript files:

http://mysite.com/List http://mysite.com/View/3 http://mysite.com/Edit/3
  1. jquery-1.3.2.js
  2. jquery-ui-1.7.2.custom.js
  3. jquery.template.js
  4. jquery.pagination.js
  5. Utility.js
  6. Search.js
  7. List.js
  1. jquery-1.3.2.js
  2. jquery-ui-1.7.2.custom.js
  3. jquery.template.js
  4. jquery.pagination.js
  5. Utility.js
  6. View.js
  1. jquery-1.3.2.js
  2. jquery-ui-1.7.2.custom.js
  3. jquery.validate.js
  4. jquery.form.js
  5. jquery.watermark.js
  6. Utility.js
  7. Edit.js

We can group the above scripts, in the following groups:

  1. jQueryBase: jquery-1.3.2.js, jquery-ui 1.7.2.custom.js.
  2. ListView: jquery.template.js,jquery.pagination.js.
  3. Form: jquery.validate.js,  jquery.form.js, jquery.watermark.js.
  4. ListLocal: Utility.js, Search.js, List.js.
  5. ViewLocal: Utility.js, View.js.
  6. EditLocal: Utility.js, Edit.js.

Now, we can replace with the following:

http://mysite.com/List http://mysite.com/View/3 http://mysite.com/Edit/3
  1. jQueryBase
  2. ListView
  3. ListLocal
  1. jQueryBase
  2. ListView
  3. ViewLocal
  1. jQueryBase
  2. Form
  3. EditLocal

The benefits of the above comparing to individual file or single file combining are:

  1. We are sending less request to our web server (as the files are now grouped).
  2. We are not downloading the same file between the page visits (comparing to single file response).
  3. We are downloading the files that are only required for that visiting page.

With our Script Management Component it becomes really easy to achieve the above, for example, for the List you can use the following syntax:

<% Html.jQuery().ScriptRegistrar().Scripts(script => script.AddGroup( "jqueryBase",
                                                                        group => group.Add("~/Scripts/jquery-1.3.2.js")
                                                                                      .Add("~/Scripts/jquery-ui-1.7.2.custom.js")
                                                                  )

                                                            .AddGroup( "ListView",
                                                                        group => group.Add("~/Scripts/jquery.template.js")
                                                                                      .Add("~/Scripts/jquery.pagination.js")
                                                                  )

                                                            .AddGroup("ListLocal",
                                                                        group => group.Add("~/Scripts/Utility.js")
                                                                                      .Add("~/Scripts/Search.js")
                                                                                      .Add("~/Scripts/List.js")
                                                                  )
                                        )
                                .Render(); %>

to configure each group setting you can use:

group => group.Add("~/Scripts/jquery-1.3.2.js")
              .Add("~/Scripts/jquery-ui-1.7.2.custom.js")
              .Version("2.1)
              .Compress(true)
              .CacheDurationInDays(365)
              .Combined(true)

you can also use a CDN instead of loading the each group, it becomes really handy when your application becomes popular, to set the CDN you can use:

group => group.UseContentDeliveryNetwork(true)
              .ContentDeliveryNetworkPath("http//mycdn.com/myScriptGroup.js")

If you do not want to specify the same setting for each group again and again (DRY) you can set the default settings in the application start (global.asax) like the following:

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);

    WebAssetDefaultSettings.CacheDurationInDays = 365;
    WebAssetDefaultSettings.Combined = true;
    WebAssetDefaultSettings.Compress = true;
    WebAssetDefaultSettings.Version = "2.1";
}

When you run the application and open the Firebug, you will find that each group is merged/compressed/cached like the following:

FB 

We have included some default behavior (AKA convention over configuration) backed into the script management components, for example when you are running the application in development mode (debug="true" in web.config) it will include the .debug.js and in release (debug="false") .min.js files no matter what filename you have mentioned in the ScriptRegistrar. If the file does not exist (.min.js/.debug.js) it will automatically fallback to the original value. When developing these components we take the YSlow rules very seriously, for example, when you use the ScriptRegistrar, it will render the script tags at bottom of the page, no matter how many ScriptRegistrar placed in the Master/Content/User Controls. Other than script files, you can also mention your startup and cleanup javascript statements in the ScriptRegistrar. For example, if you have the following:

In Master page:

<% Html.jQuery().ScriptRegistrar().Scripts(script => script.AddGroup( "jqueryBase",
                                                                        group => group.Add("~/Scripts/jquery-1.3.2.js")
                                                                                      .Add("~/Scripts/jquery-ui-1.7.2.custom.js")
                                                                      )
                                            )
                                    .OnPageLoad(() =>
                                                {%>
                                                    test1.init();
                                                <%}
                                               )
                                    .OnPageUnload(() =>
                                                  {%>
                                                      test1.dispose();
                                                  <%}
                                                 )
                                .Render(); %>

and in Content Page:

<% Html.jQuery().ScriptRegistrar().OnPageLoad(() =>
                                                {%>
                                                    test2.init();
                                                <%}
                                               )
                                    .OnPageUnload(() =>
                                                  {%>
                                                      test2.dispose();
                                                  <%}
                                                 ); %>

and in User Control:

<% Html.jQuery().ScriptRegistrar().OnPageLoad(() =>
                                                {%>
                                                    test3.init();
                                                <%}
                                               )
                                    .OnPageUnload(() =>
                                                  {%>
                                                      test3.dispose();
                                                  <%}
                                                 ); %>

When the page renders, it will write the following:

<script type="text/javascript" src="http://weblogs.asp.net/asset.axd?id=eyJjdCI6ImJuIjoic2hCcnVzaFhtbC5qcyJ9XX1dfQ%3d%3d"></script>
<script type="text/javascript"> 
//<![CDATA[
jQuery(document).ready(function(){
test1.init();
test2.init();
test3.init();
});
jQuery(window).unload(function(){
test3.dispose();
test2.dispose();
test1.dispose();
});
//]]>
</script>
</body>
</html>

There should be a little difference in the Content and User Control code. There will no Render() for those two. Only the Master will have the Render() method.

And it is View Engine independent, we have already tested it in Webforms, Spark and NHaml.

What do you think? What features it is currently missing? Comments and suggestions are really appreciated.

Shout it
Filed under: , , , , ,

Comments

# JavaScript File Management

Friday, July 24, 2009 4:30 PM by DotNetShoutout

Thank you for submitting this cool story - Trackback from DotNetShoutout

# JavaScript File Management | ASP Scribe

Friday, July 24, 2009 4:51 PM by JavaScript File Management | ASP Scribe

Pingback from  JavaScript File Management | ASP Scribe

# re: JavaScript File Management

Friday, July 24, 2009 6:02 PM by DigiMortal

To save some performance you can also use ASP.NET MVC resource combining. This is server-side resource combining technology that provides something similiar to ASP.NET ScriptCombiner. I wrote about MVC resource combiner the following blog entry: weblogs.asp.net/.../asp-net-mvc-how-to-combine-scripts-and-other-resources.aspx

# Content Management | All Days Long

Friday, July 24, 2009 8:55 PM by Content Management | All Days Long

Pingback from  Content Management | All Days Long

# JavaScript File Management - Kazi Manzur Rashid&#39;s Blog

Pingback from  JavaScript File Management - Kazi Manzur Rashid&#39;s Blog

# JavaScript File Management - Kazi Manzur Rashid&#39;s Blog &laquo; Management

Pingback from  JavaScript File Management - Kazi Manzur Rashid&#39;s Blog &laquo;  Management

# re: JavaScript File Management

Saturday, July 25, 2009 5:21 AM by Joe Chung

Support Web analytics scripts, e.g., WebTrends, Omniture, Google Analytics, etc.  Those are generally a hybrid between application common and inline scripts.

# re: JavaScript File Management

Sunday, July 26, 2009 10:45 AM by JavaScriptBank.com

very cool & good tip, thank you very much for sharing.

# JavaScript File Management - Kazi Manzur Rashid's Blog

Sunday, July 26, 2009 12:30 PM by 9eFish

9efish.感谢你的文章 - Trackback from 9eFish

# re: JavaScript File Management

Monday, July 27, 2009 4:37 AM by Peter Mounce

You might be interested in IncludeCombiner - github.com/.../includecombiner.  It combines, minifies and compresses script/css resources, and then sets cache-headers; at run-time, without needing to introduce steps to the build-process (and then manage those) or arbitrary groups of resources to one's views.  

One gives up the ability to serve script/css assets out of a CDN, but one gains no-management-overhead on top of combine/minify/compress/cache.

# JavaScript File Management | pc-aras

Tuesday, July 28, 2009 4:29 PM by JavaScript File Management | pc-aras

Pingback from  JavaScript File Management  | pc-aras

# re: JavaScript File Management

Thursday, July 30, 2009 12:50 AM by Liam McLennan

Nice work. One small mistake

"The benefits of the above comparing to individual file or single file combining are:

  1. We are sending less request to our web server (as the files are now grouped). "

3 requests is not less than one.

# jQuery和asp.net mvc相关资源链接

Thursday, July 30, 2009 1:06 PM by ASP.NET Chinese Blogs

jQuery: Simplify calling ASP.NET AJAX services from jQuery jQuery Splitter jHtmlArea &#8211; The all

# re: JavaScript File Management

Wednesday, August 05, 2009 4:57 AM by Arnis L.

Already thought to myself - this could be a nice addition for MvcContrib. Using your technique with great success so far. :)

# re: JavaScript File Management

Thursday, August 06, 2009 7:16 AM by Jesper Mortensen

Fantastic -- nice functionality for the end user there, I hope you'll release it soon under a open source license, or as not-too-expensive & bothersome commercial components.

2 improvements I'd like to suggest:

1) We would like to use the same component for scripts in the HTML Head and at the bottom of the page. It is about dependencies, sometimes its simply easier to manage dependencies by putting main site-wide libraries in the HTML Head. Perhaps it's mostly easier for our frontend guy, but that counts too. :-)

2) Client-side dependency management & parallel downloads. When you put multiple < script > tags into a page, then the browser may block for quite long time; and you don't have explicit guarantees that dependencies are met.

Say I load jQuery from Google's free CDN, and I have scripts which depend on jQuery that I load from my own servers, then my own scripts could execute before jQuery is loaded.

One great way to resolve this could be to inline labs .com's script (Load And Block JavaScript), and output the script links in LABjs's format. This should of course be optional.

# re: JavaScript File Management

Thursday, August 06, 2009 12:40 PM by Jesper Mortensen

Hmnn, another very handy capability would be automatic HTTP / HTTPS handling. I.e. if the requested HTML page is HTTPS, then make HTTPS links to the CDN and to the asset, so that the browser does not display a nasty "some content unencrypted" warning.

# { jm }Blackboard is looking forward to improve HTTP performance - { jm }

Pingback from  {    jm    }Blackboard is looking forward to improve HTTP performance - { jm }