Release Notes
Browsers tested and passing the Test Suite (please help to fill in gaps):Windows: IE (6, 7, 8, 9b), FF (1.5, 2, 3, 3.5, 3.6, 4b8), Chrome (latest), Opera (9.6, 10, 10.5), Safari (4, 5), Netscape (7.2, 8.1, 9), Mozilla 1.7Mac (OSX): Safari (3.2, 4, 5), Netscape 9, FF (1.5, 2, 3, 3.5, 3.6), Opera (9.6, 10, 10.5), Mozilla 1.7, Camino 2Linux: FF (1.5, 2, 3.5, 3.6), Opera (9.5, 9.6, 10, 10.6), Netscape 9
Safari 2, Opera 8, Konquerer (3.4, 4.1), FF 4b7
Old and busted:
<script src="framework.js"></script> <script src="plugin.framework.js"></script> <script src="myplugin.framework.js"></script> <script src="init.js"></script>New hotness:
<script> $LAB .script("framework.js").wait() .script("plugin.framework.js") .script("myplugin.framework.js").wait() .script("init.js").wait(); </script>In the above example, all scripts load in parallel (by default). "framework.js" needs to execute first, before the others. But "plugin.framework.js" and "myplugin.framework.js" have no dependencies between them, so their execution order is not important (first-come, first-served). "init.js" needs to wait for all 3 scripts to execute before it runs.
There are a few other variations on the .script(...) signature. For instance, you don't have to do a single script() call for each file (though I think it makes thing more readable). You can pass as many scripts singularly as parameters to one script() call. You can also pass an array of scripts, and it will loop through them and load them in the same way. Lastly, you can pass in an object instead of string, and the object literal can contain "src", "type", and "charset" specifications, if you need to override the defaults (for instance, load a "text/vbscript" script).
So, there's some special gotchas you need to look out for when using LABjs not only to load scripts but also to "couple" inline scripts to execute in the proper order with the loaded scripts. Take this expanded example from above:
<script src="framework.js"></script> <script src="plugin.framework.js"></script> <script src="myplugin.framework.js"></script> <script> myplugin.init(); </script> <script> framework.init(); framework.doSomething(); </script>In this example, the browser would normally make sure that the two inline script blocks at the end will not even be parsed/executed until after the 4 scripts have been loaded. So, the references to "myplugin.init", "framework.init", and "framework.doSomething" will already be guaranteed to be defined (in the scripts that are loaded before the code is parsed).
But, because LABjs is an asynchronous loader, this "guarantee" will not be true. So, this code will fail:
<script> $LAB .script("framework.js").wait() .script("plugin.framework.js") .script("myplugin.framework.js") </script> <script> myplugin.init(); </script> <script> framework.init(); framework.doSomething(); </script>The reason is because "myplugin.init", "framework.init", and "framework.doSomething" will be undefined symbols the instant after the $LAB chain first executes and starts loading the scripts. To make this code asynchronous safe, we do this instead:
<script> $LAB .script("framework.js").wait() .script("plugin.framework.js") .script("myplugin.framework.js") .wait(function(){ myplugin.init(); framework.init(); framework.doSomething(); }); </script>We wrap the inline script code in a ".wait(function(){ ... })" wrapper, which will defer its execution until the proper time in the loading order of the chain.
A simple pattern for converting <script src="..."></script> and <script>... /* inline code */ ...</script> tags into $LAB API calls, use these two rules:
For every <script src="..."></script> tag you are replacing, you should have a ".script(...)" call For every <script>... /*inline code*/ ...</script> inline script block with code in it, we need a ".wait(function(){ ... })" call to wrap around the codeIf this still is a little confusing, it's ok! Read more about converting inline synchronous code to LABjs suitable asynchronous code over on getiblog.
LABjs is able to fork it's behavior between browsers because of the use of some small but fairly solid browser inference sniffs. This is different from feature-detection (which is always preferable when possible), but with script-loading quirk behavior, there's no way to feature-detect, we simply have to fork behavior based on browser family. So, the following two browser inference sniffs are in the code at this point:
is_opera = window.opera && Object.toString.call(window.opera) == "[object Opera]" -- this inference relies on the fact that Opera declares an "opera" property on the window object, and that it's a special native, whose toString() return value cannot be duplicated by someone just declaring a window.opera property themselves. This gives a reasonably strong inference that we are in fact in the Opera browser. is_gecko = ("MozAppearance" in document.documentElement.style) -- this inference relies on "MozAppearance" only appearing in Gecko browsers in the documentElement's style object. This isn't as solid as the Opera inference, but Gecko/FF has had this property for a really long time and there's a good chance they won't remove it, nor would any other browser have any reason to add a property of that name.Another thing to note is that LABjs is now feature-testing for `async=true` on script-inserted script elements. Read more about this feature on the Dynamic Script Execution Order WHATWG Wiki Page and Henri Sivonen's HTML5 Script Execution Changes.
Basically, HTML spec currently has wording in it which, when implemented by Firefox (FF 4b7) and Webkit (nightly changeset 67245), breaks LABjs. The two browser families break in different ways, but it basically centers around the browser's current inability to have multiple scripts dynamically loaded in parallel but ensure their execution order (for dependencies). As you may have read in this documentation, Mozilla Gecko (FF) and Opera have always preserved order of dynamically inserted script elements (which is preferable behavior for LABjs' usage). The spec currently says it should not preserve order, but should execute the scripts in "as soon as possible" order.
Moreover, HTML spec also says that script resources with an unrecognized `type` value (such as LABjs' usage of "script/cache") should not be fetched. LABjs currently relies on that non-standard behavior in IE and Webkit (including Chrome) to "preload" scripts into cache but not execute them. So that behavior (as following spec) breaks LABjs in Webkit nightly.
Bottom Line: The informal "proposal" to the W3C is to allow script elements to control their own execution ordering behavior by the author specifying the `async` property and giving it a value of `true` or `false`. The spec already defines `async` as an attribute on parser-inserted script elements (in HTML markup), so the proposal extends that same behavior to script-inserted script elements as well.
The feature test for `async=true` looks for this new behavior by assuming that a script-inserted script element would only have this value defaulted to `true` on a newly created script element in a browser implementing this new behavior (a stipulation of the proposal). Currently, only FF 4b8pre implements this feature (fixing LABjs in FF4), but Webkit has indicated they will probably follow suit. Discussions are ongoing with the other browsers and the W3C to make `async=true` behavior standard and implemented across all browsers.
void $LAB.setGlobalDefaults(object optionsObject)
Sets one or more options as global defaults to be used by all $LAB chains on the page. This method is not chainable, and thus must only be called stand-alone like this: $LAB.setGlobalDefaults({...});
ParametersoptionsObject : an object which contains name/value pairs for the options to set:
AlwaysPreserveOrder: a boolean (defaults to false) which controls whether an implicit empty wait() call is assumed after every script loading, which essentially forces all scripts in the chain to execute serially in order (loading in parallel, by default, is not affected by this setting). UsePreloading: a boolean (defaults to true) which controls whether LABjs attempts to use various "preloading" "tricks" to accomplish parallel loading of all scripts while still controlling execution order. These "tricks" can be controlled separately by subsequent options. The only behavior which cannot be controlled separately from this option (that is, that to turn it off you must turn all preloading tricks off with UsePreloading=false) is that Firefox/Gecko and Opera browsers automatically preserve execution order with dynamically appended <script> elements. So, in those two families of browsers, the other "tricks" are not used as they are not necessary.If you turn off "preloading" (or disable the "preloading tricks" below), LABjs will default to loading groups of scripts in serial. This means that scripts will not be pre-fetched for later exeuction, but will be loaded and executed only when the proper order in the chain is reached. This will still allow scripts in each group (all script() calls before a wait() call) to load in parallel to each other, but look-ahead in the chain will not occur. Only turn off "preloading" functionality if it is causing negative performance issues, which there are a few very rare niche cases where this might occur. In general, though, if you experience problems with "preloading", it's probably because there is either an incorrect usage of the API (relating to dependencies and execution), or there may be a bug that needs to be addressed. UseLocalXHR: a boolean (defaults to true) which controls whether LABjs will use an XHR Ajax call to "preload" scripts which are "local" to the current page's domain (note: does *not* take advantage of any cross-domain Ajax techniques). This "preload" is effective and quite performant for local scripts, but it has the caveat that such script loadings are not in any way cached by the browser, meaning there's a lot of unnecessary reloading of such scripts on subsequent page loads/views. You should probably not use this option if the files in question are of significant size. Update, it appears that most XHR requests in most browsers do get cached.Note: Again, as noted in "UsePreloading" section above, this technique is not used on Firefox/Gecko or Opera browsers, regardless of this setting's value. UseCachePreload: a boolean (defaults to true) which controls whether LABjs will use a specific trick to "preload" scripts, which I'm calling "text/html caching". Essentially, you can create a <script> element in the DOM that references a script URL, but set the "type" property to "text/html". The browsers which LABjs uses this trick on will fetch the script into the browser's cache, but will ignore it and not execute it (since the type is not a recognized MIME type). So, the script is loaded into cache (assuming it is cacheable with proper caching headers), and then when the LABjs chain is ready to execute it, it creates a second <script> element with the same source but this time with the proper "type" property. The browser will immediately load the code from the cache and execute it.This "trick" is effective and works well, but has some caveats. For one, if your script(s) don't have proper caching headers, or are specifically intended to not be cacheable (like for instance, a JSON call), this "trick" should be avoided, as it will cause a double-load of the script. Or, if the script in question is particularly large (over 1MB probably), the double-cache-hit may experience a non-trivial delay while the file is re-read from disk. This delay is probably much less than just loading the script for the first time over the wire, but depending on your script properties, you may want to turn off this feature. Note: Again, as noted in "UsePreloading" section above, this technique is not used on Firefox/Gecko or Opera browsers, regardless of this setting's value. AllowDuplicates: a boolean (defaults to true) which controls whether or not LABjs will inspect its internal script URL cache and the DOM's <script> elements to prevent a script URI from being (accidentally, most likely) loaded a second time. Setting this to false (from default true) adds a slight performance overhead for the checks, but if you are running LABjs in a concurrent or dynamic system and it may be possible to accidentally specify multiple loadings of the same script, this feature will prevent the unnecessary loadings/executions. Duplicates in the chain will simply be skipped over. AppendTo: a string (defaults to "head") which can either be "head" or "body", which controls which DOM element to append all scripts to. In almost all cases, "head" as the default is the appropriate setting. But there are a few rare niche cases where appending the script to the "body" is more appropriate. BasePath: a string (defaults to empty "") which specifies a path value to prepend to every script URL. All relative script URL's are already automatically fully qualified to the page's location, so this BasePath should really be used to set some other absolute path to override this qualification for otherwise relative "looking" script URL's.Returnsnone
[$LAB] $LAB.setOptions(object optionsObject)
Sets one or more options only to be in effect for the current $LAB chain being executed. This method is chainable (in fact, for it to have any effect, it must be part of a chain!), but it must be the first call in the chain (as changing many of the options mid-chain would create race conditions). So, it must be called like this: $LAB.setOptions({...}).script(...)...;
ParametersoptionsObject : an object which contains name/value pairs for the options to setReturns$LAB : the chained object reference so subsequent calls to script() and wait() can be made.
[$LAB] $LAB.script(varies,...)
This method accepts one or more parameters of varying types. Each parameter value is intended to specify a script resource URI to load.
Parametersvaries (can be any number/combination of the following):
string : a relative or absolute path URI to a local or remote script to load object : an object containing one or more of these properties: string src : (required) a relative or absolute path URI to a local or remote script to load string type : (optional) the MIME type attribute (ie, "text/javascript", "text/vbscript", etc) string charset : (optional) the charset attribute (ie, "utf-8", etc) bool allowDup : (optional) whether to suppress a duplicate script URI check for this script. (defaults to the value of the AllowDuplicates global or chain option. array : an array of parameters of any of the allowed types (including array)function : if a function is found as one of the parameters, that function will be be executed immediately, which must return a value directly. The return value must be one of the other allowable types (string, object, or array). If the function call results in no return value ("undefined") or the value is "falsy" (false, null, etc), it will be interpreted as no script to load.
This feature allows the construction of "conditional chains", where run-time logic is used to decide ultimately what scripts will be loaded by the $LAB chain.
See Example 8 below for usage demonstration.
Returns$LAB : the chained object reference so subsequent calls to script() and wait() can be made.
[$LAB] $LAB.wait(function inlineScript[=null])
This function serves two purposes. Firstly, when inserted into a chain, it tells the chain to ensure that all scripts previously listed in the chain should finish executing before allowing the internal 'execution cursor' to proceed to the remaining part of the chain. Essentially, you can specify "blocking" behavior in terms of execution (not loading!) to ensure dependencies execute in the proper order (regardless of how they were loaded).
Secondly, you can specify a function reference (usually an inline anonymous function) that will be executed in order, immediately following the chain's previous scripts executing, but before subsequent scripts in the chain are executed. This is synonymous with inline <script> tags before or after <script src="..."> type tags.
For instance, it can be used to defer the initialization execution of a script you are downloading, like this: $LAB.script("script.js").wait(function(){initScript();}); where initScript() is a function that is defined in "script.js".
ParametersinlineScript : (optional, defaults to null)Returns$LAB : the chained object reference so subsequent calls to script() and wait() can be made.