Enabling the ASP.NET Ajax script loader for your own scripts
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:
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)