Tales from the Evil Empire

Bertrand Le Roy's blog

News


Bertrand Le Roy

BoudinFatal's Gamercard

Tales from the Evil Empire - Blogged

Blogs I read

My other stuff

Archives

Enabling the ASP.NET Ajax script loader for your own scripts

(c) 2009 Bertrand Le Roy In previous posts, I’ve shown different ways to build a client-side class browser, using the ASP.NET Ajax Libary and jQuery.

In this post, I’ll focus on a few lines of code from the latest version of that sample. Those few lines of code enable my custom script to benefit from the script loader’s features such as lazy and parallel loading and dependency management.

An important feature of the script loader is the separation of the script meta-data from the script code itself. The meta-data can include the name of the script, its dependencies, instructions on how to figure out if it’s already loaded, its debug and release URL patterns and a declaration of the lazy components and plug-ins it introduces. The script loader can also handle composite scripts but I won’t cover that in this post.

  • The name of the script is what will be used to reference it from other scripts and to generate the URL from a pattern. It shouldn’t include the “.js” extension.
  • The dependencies and executionDependencies are each a simple array of script names that the script depends on. I’ll explain the difference in a minute.
  • The is loaded criterion is a JavaScript expression that returns true if the script is already loaded. This is usually a test on an object that the script file defines. For example, the “templates” script uses !!(Sys.UI && Sys.UI.Templates). It can assume Sys because it’s defined by start.js.
  • Debug and release patterns are expressions that enable the framework to map script names into debug and release URLs. The ASP.NET Library uses “%/MicrosoftAjax{0}.debug.js” and “%/MicrosoftAjax{0}.js” where % gets replaced with the path where the script loader was downloaded from (this can be CDN or local) and {0} gets replaced with the script name. You can create your own pattern or just provide fixed URLs if you have only one script file. The debug pattern is optional.
  • Lazy components and plug-ins are helpers that the script loader can create for you and that enable developers to start using your components before they are even loaded and hide the script loading aspects as much as possible. For example, it’s that feature that enables you to write this while still having only start.js loaded:
    Sys.create.dataView("#myDataView", {
        data: myData
    });
    The code that needed to be written for this helper to be created was this:
    behaviors: ["Sys.UI.DataView"]
    This gets automatically transformed by the script loader into the Sys.create.dataView method, before it even tries to load the actual script that defines Sys.UI.DataView. And if you have jQuery loaded as well, you’ll also get a jQuery plug-in out of it for free, which means that you can do:
    $(".data").dataView({data: myData});
    and instantiate DataView controls over the results of a selector query in one operation. Groovy. Of course, you can do the same with the behaviors and controls that you write in your own scripts, with lots of options to customize names, add parameters, etc.

In the case of the class library code, here’s the meta-data declaration code that I had to write:

Sys.loader.defineScript({
  name: "classBrowserTree",
  releaseUrl: "%/TreejQuery.js",
  executionDependencies:
["jQuery", "Templates", "ComponentModel"], isLoaded: !!(window.jQuery &&
window.jQuery.fn.classBrowserTreeView) });

This declares that my script, named “classBrowserTree”, can be found at the URL TreejQuery.js relative to the base URL of the script loader, that it depends on jQuery, Templates and ComponentModel (which themselves have their own dependencies) and that the loader can determine whether it’s already loaded by evaluating the classBrowserTreeView jQuery plug-in.

Now, loading that script and all its dependencies is as simple as writing this:

Sys.require(Sys.scripts.classBrowserTree);

Notice that as you’re typing this, you actually get full IntelliSense in Visual Studio on the name of the script, which is great for speed and to avoid typos:ScriptLoaderIntelliSense

One of the things that enable the script loader to do its job is a special way to write your script that makes it possible to separate the loading and parsing of the code from its execution.

Don’t freak out (yet) though as the code you have to introduce is very lightweight and it doesn’t prevent your script from being loaded without the script loader (with a plain old script tag for example).

Reversely, a script that doesn’t have the special script loader code can be handled by the script loader but its dependencies must be declared using “dependencies” instead of “executionDependencies” in the meta-data declaration code, which will in effect disable the more advanced features of the script loader such as parallel loading.

The special code in question is the following:

(function() {
  function execute() {
    // Your code goes here.
  }
  if (window.Sys && Sys.loader) {
    Sys.loader.registerScript(
"classBrowserTree", null, execute); } else { execute(); } })();

What you see here is a (function() {…})(); wrapper, which is standard practice to isolate code from the global namespace (essentially, it’s a local scope); we also have another named scope in there that we arbitrarily call “execute” (but the name doesn’t matter). This is where you’ll typically put your actual code. Then we have the bootstrapping code that looks for the script loader. If it isn’t found, the code in the execute function is immediately run. If it is found, the execute function is registered with the script loader but is not immediately executed.

This wrapper code is what enables the script loader to do its magic. Thanks to this little bit of code, it doesn’t care at all in what order the scripts are being loaded, because it can delay the time when any script actually gets executed until all its dependencies have been successfully loaded.

This also allows the script loader to load scripts in parallel, even if one depends on another. Normally a script loader would have to download a dependent script first and wait for it to completely load before loading the next script. This ‘serializes’ the loading process and is not good for performance. Modern browsers can download 6 to 8 scripts simultaneously. Separating the loading of a script from its execution allows the loader to take advantage of that, and in most cases, even complex dependency trees can be downloaded completely in parallel, meaning the total time is not the sum of each script, but only the longest one.

Of course, in order to be wrappable, your code needs to be able to run in a non-global scope. This is good practice anyways and is relatively easy to achieve in most cases, so if it can’t, it may be a good idea to try to determine why and fix it.

And this is it. I hope I’ve convinced you that the script loader can help you to improve the performance of your applications at a really low cost. It also provides a very worthy client-side equivalent of the server-side ScriptManager that ASP.NET has been providing to developers for years: switching the whole application between debug and release scripts is a breeze, which minimizes the risk of deploying debug scripts in production; dependencies are being handled automatically; script combination is handled.

(many thanks to Dave Reed for helping with this post and of course for implementing an awesome script loader)

Comments

bobby said:

Nice post. It would be great if you can provide running sample of this. Thanks.

# November 24, 2009 12:22 AM

marius said:

hehe! Sys.UI.DataView

i like this soooooo much...

too many requests with Sys.loader... let's say 5 up to 10 small  files (5 up to 10 kb) and we don't really gain anything... (see firebug's net tab)

But there is also "the" ajaxmin ;)

so my preferd way is to use the Sys.loader for DataView or Watermark or Whatever microsoft extended but keep my own j plugins in a directory where i can minify them all at buildtime...

# December 3, 2009 11:41 PM

Bertrand Le Roy said:

@marius: your comment got eaten by the spam filter again. If it continues that way, I'll have to ask people to send me e-mail when they comment. <sigh>

You are right that like with any performance tool, you need to measure and adapt. If you exhaust the browser's pool of connections, you may end up with no perf gain. In those cases, combination can help if done responsibly.

Build time minification is *always* a good thing that the script loader in no way replaces. Did I seem to imply otherwise?

# December 4, 2009 12:11 AM

mattbrooks said:

My comment didn't show up either. Sys.UI.DataView is immense! The AJAX Minifier is great too. Now we just need a CSS cruncher and have the two rolled into VS as standard :-)

# December 4, 2009 3:33 AM

marius said:

Did I seem to imply otherwise? No.

... In those cases, combination can help if done responsibly....

Its just exactly what i think. For real jHeads your advise was acurate, implied by using jQuery.[fn].xx.

We can see a lot of posts these days about "how"

to use the loader but almost none about "when and why"...

Most of time using jQ people do not write "global like"  fn.xx. Not everyone is a plugin writer.

Also loading jQ.ui and jQ.effects from CDN is not the best choice for me. They still have some minor bugs in the animation engine wich i like to override. (to get rid of NaN..)

# December 4, 2009 9:23 AM

Bertrand Le Roy said:

All good points, yes.

# December 4, 2009 11:54 AM

Luke said:

Thanks guys like you, I hate ms ajax for absolutly no support and no library for that whats going on client side, there are just telling you how to implement the whole javascript and ajax stuff without all the javascript and ajax stuff. You know, for this guys who just want to write <html> <body>. Grrr, if you want to go deeper in, you have to first read all the source code to understand whats going on, and the problem is that im not the big programer, so that i remain decrypting all the source for days...

# April 29, 2010 7:06 PM

Ashwani K Tiwari said:

Nice post.

i want to implement this feature

can you provide me steps like

what js file i required as start.js

what js references i need to add and

Most importantly where should i define my scripts

Sys.loader.defineScript({

 name: "classBrowserTree",

 releaseUrl: "%/TreejQuery.js",

 executionDependencies:

   ["jQuery", "Templates", "ComponentModel"],

 isLoaded: !!(window.jQuery &&

   window.jQuery.fn.classBrowserTreeView)

});

in which file so that i can access from my aspx file.

# May 4, 2011 4:46 AM