JScout (Legacy)
This version of our Learning Center is unmaintained.
This article may be out-of-date or contain incorrect information.
Please visit the new Sencha Learning Center for up-to-date material.
From Sencha - Learn
jScout
- full-featured javascript/css on-demand package loader, based on Ext core
Version: 0.3
Download links:
http://samurai-jack.org/jScout/jScout-0.3.tar.gz
ftp://samurai-jack.org:21/jScout-0.3.tar.gz
Synopsis:
// Basic usage //async declare('base::dir::myModule',function(use) { use( ['base::dir1::myModule1', 'base::dir2::myModule2.css', ... ], function(use) { //base::dir::myModule definition base.dir.myModule = function () { ... } } ); }); //sync declare('base::dir::myModule',function(use) { useSync( ['base::dir1::myModule1', 'base::dir2::myModule2.css', ... ], function(useSync) { //base::dir::myModule definition base.dir.myModule = function () { ... } } ); },null,true);
// Recursive dependencies declare('base::dir::myModule',function(use) { use('base::dir1::myAnotherModule1', function(use) { //part of base::dir::myModule definition use('base::dir2::myAnotherModule2', function(use) { //part of base::dir::myModule definition }); }); });
// preloading big module after first page is shown Ext.onReady(function(){ ... use('start::page::dependencies', function(use) { startPage = new Ext.Window(...); startPage.show(); //last action for showing start page //preload big module, so its further usage will be instant use('base::gMapEngine'); }); });
// Using external module (which is not declared with jScout and may be on external URL) declare('base::dir::myModule',function(use) { use( { name : 'base::dir1::externalModule', URL : 'http:://external.module.url' }, function(use) { //part of base::dir::myModule definition use('base::dir2::myAnotherModule2', function(use) { //part of base::dir::myModule definition }); } ); });
// Using external module which in turn use on-demand loading (Google-maps example) declare( 'base::gMapEngine', function (use){ use ( { name : 'google::load', URL : 'http://www.google.com/jsapi?key=yourKey' }, function(use, checkState){ google.load('maps','2',{ callback : function (){ base.gMapEngine = 'ready'; GM = google.maps; checkState(); } }); use({ name : 'google::maps', evalStr : 'base.gMapEngine', externalLoading : true }); } ); //eof use }); //eof declare
Description
jScout is trying to compensate the lack of packaging mechanism in JavaScript. The main feature of jScout is the correct handling of recursive dependecies along with parallel loading of in-depth dependencies (which significantly optimize page loading time) jScout fully supports external modules (modules, which were written without using jScout declaration), even for cases, when these modules in turn use on-demand-loading.
Documentation
Configuration
jScout will load scripts from your server-side Library, which should be organized with "One directory per namespace principle". The global namespace is assumed to be in
- http:/your.host/lib
Each further namespace should have its own directory, like:
- http:/your.host/lib/furtherNamespace
Namespaces and packages names are delimeted with "::" symbol,
so, package t0 in jScout namespace will have full name and URL like:
- jScout::t0, corresponding to URL: http:/your.host/lib/jScout/t0.js
and, package t1 in jScout::t0 namespace will have full name like:
- jScout::t0::t1, corresponding to URL: http:/your.host/lib/jScout/t0/t1.js
You can change the location of global namespace, by setting libRoot configuration parameter. It should contain the array, with path elements from your webroot to your javascript library:
jScout.libRoot = ['deploy','jslib'];
Methods
jScout exporting to global namespace further methods:
declare(name, packageDefinition, scope?, sync?)
This is the main function of jScout. It declares package name, and execute its packageDefinition function within optional scope.
packageDefinition function will be called with 2 arguments, like : packageDefinition(use,checkState) (you can choose your own style of arguments naming, for example : (require,check)). The first argument use is the function, the package should use for loading external dependencies, the second checkState - the function, package should use to notify jScout that, external package with its own loading mechanism complete its loading (see below for further details).
If you'll provide true value for sync parameter the 1st argument for packageDefinition will be synchronous call: useSync
//declaring declare('base::dir::myModule',function(use) { ... //use should be used for loading dependecies, like use('base::dir::myAnotherModule'); }); //declaring declare('base::dir::myModule',function(require) { ... //require should be used for loading dependecies, like require('base::dir::myAnotherModule'); });
declare will also declare the namespace for your module (with Ext.namespace function), so declaring 'base::dir::myModule' package will be preacted with Ext.namespace('base.dir');
use([ packageSpecs ], callback?, scope?)
This function is passed as the first argument to the packageDefinition function.
packageSpecs - single packageSpecification object or array of such objects
Optional callback is the function, to execute in scope after ALL packages (along with their's dependecies) in packageSpecs were succesfully loaded. callback will be called with the same parameters as packageDefinition function: use and checkState
packageSpecification object can have following attributes:
- name - the name of the package
- URL - URL of the package (in case that this package is not placed in your library)
- evalStr - the string, which will be eval'uted in case, when this package was not declared. If this string will eval'ute to true, then the package will be considered already loaded
- redefinitionAllowed - if not set in 'true', jScout will not allow you to redefine the URL, evalStr and others attributes of this package.
- externalLoading - if 'true', then jScout will only register this package, but will not attempt to load it. You should also provide evalStr in this case. You should report, that external package is loaded with checkState() call (see declare method) or specify the interval to check the state automatically.
- interval - the interval in milliseconds to check the state of the package.
use will load packages in packageSpecs asynchronously (in parallel), correctly handling their's dependicies (see examples illustration).
After the package was loaded, any further use on it will be executed without delays. Also, the situation when several packages trying to use the same package is handling correctly - only the 1st package will succeed to load it, others will wait without duplicated loading.
useSync([ packageSpecs ], callback?, scope?)
This is the synchronous analog of use. Your dependencies will be loaded synchronously, one-by-one in order they were specified, after that callback will be executed and the function will return.
Note: You can freely intermix use and useSync calls, but after 1st encountered useSync call, all further in-depth calls to use will be silently changed to useSync
Distribution
jScout is distributed as Catalyst application. After unziping to localdir, you can start the local http server with following commands (you need to have Perl and Catalyst installed):
localdir$ cd jScout localdir/jScout$ perl script/jscout_server.pl
After this prompt
.................. [info] jScout powered by Catalyst 5.7014 You can connect to your server at http://your-host:3000
open the following URLs to run the test suite:
http://your-host:3000/static/t/async.html
http://your-host:3000/static/t/sync.html
Compatibility with normal <script> tag loading
jScout is fully compatible with normal static script loading, via <script> tag, if the packages being loaded are declared with declare function. See also next section for additional details.
Using external modules
If you are using external package (package that was written without jScout declaration) then appears the question, how to determine if the package is already loaded (with <script> tag) or not. To solve this problem you can choose 2 methods.
- The 1st (recomended) is, that package is considering loaded, if its name is evaluted to 'true', after replacing '::' to '.'. This method requires, that external package should be correctly named and placed in properly directory in library. For example, if you are using Ext.ux.BroadcastEvents extension, then you should create 'Ext::ux' namespace in you library, rename extension's file to BroadcastEvents.js and make it available via URL:
- http://your.host/lib/Ext/ux/BroadcastEvents.js
- After that you can refer to it, with ordinary use('Ext::ux::BroadcastEvents') call.
- Generally, the package you are loading should define the object, corresponding to its name (in the example above 'Ext::ux::BroadcastEvents' defines Ext.ux.BroadcastEvents singleton).
- The 2nd method is to provide evalStr parameter in packageSpecification object. In this case you can name and place package at your choice.
The situation when several packages trying to use the same external package is also handling correctly.
On-demand loading outside of package context
You can use jScout to implement on-demand script loading outside of any package context. To do so, you should use the jScout.use function, which will create anonymous context, load all dependencies in parallel, and then execute callback function.
//Somewhere in your code jScout.use( ['base::dir2::myAnotherModule2'], function(use) { //processing results //independed use jScout.use(' ... '); //use in the same context use(' ... '); }, scope );
Note, that the use parameter of callback function will be tied to the same anonymous context, which was create with outer jScout.use call.
Using jScout for smart preloading of big modules
If your application was designed as 'one-page application' and have many dependencies which will be needed after the initial screen, you can smartly preload them. For example, this can be done for Google Maps Api, which is about 200kb in size (see example below). To do so, make empty call to use, after the last statement, which is present your start screen. While the user will examine the start screen, your dependencies will be silently loaded in background, and further references to their's packages will be executed without delay.
// preloading big module after first page is shown Ext.onReady(function(){ ... use('start::page::dependencies', function(use) { startPage = new Ext.Window(...); startPage.show(); //last action for showing start page //preload big module, so its further usage will be instant use('base::gMapEngine'); }); });
On demand CSS loading
CSS files are considered as external packages and should ending on '.css'. The syntax for CSS loading is essentially the same:
// CSS loading declare('base::dir::myModule',function(use) { use('base::dir2::myModule2.css',function(use) { //base::dir::myModule definition base.dir.myModule = function () { ... } }); });
Examples
Test suite
This module was tested with following dependency diagram:

and module template like:
declare( 't::async::t0', function (use){ use( ['t::async::t1','t::async::t2','t::async::t3','t::async::t4'] , function(use) { if (t.async.t0) throw "t0 double definition"; t.async.t0 = true; t0 = true; if (!t1 || !t2 || !t3 || !t4) throw "t0 not satisfied"; jScout.use('t::async::t7',function (use) { if (!t7) throw "t0 not satisfied with t7"; }); Ext.Msg.alert('mesage','test passed'); }); //eof use }); //eof declare
The resulting loading diagram is below:
as you can see, jScout loaded packages t1-t4 and t5-t6 in parallel, as early as possible.
Advanced example - Google Maps API loading
This example demonstrate, how to create package, which will load Google Maps API.
//declaring a package for external refernces declare( 'base::gMapEngine', function (use){ use ( { //note, that the 'google::load' name will evalute to 'true' value if //the google API loader is already loaded //optionally you can use any package name, and provide //evalStr : 'google.load' attribute name : 'google::load', //load the google API loader URL : 'http://www.google.com/jsapi?key=yourKey' }, //note checkState function(use, checkState){ //load Maps API google.load('maps','2',{ callback : function (){ //set shortcut for access to GoogleMaps API along with condition //that package is loaded GM = base.gMapEngine = google.maps; //inform jScout that package is loaded checkState(); } }); //just registering the package 'google::maps', doesnt trying to load it //the condition that package is loaded is //evalStr : 'base.gMapEngine' - should evalute to 'true', use({ name : 'google::maps', evalStr : 'base.gMapEngine', externalLoading : true }); } ); //eof use }); //eof declare
After creating this package you can reference to it with "use" in every package with GMap functionality.
Limitations
jScout was tested only with IE6 and FF2, support for other browser will appear in the future.
External dependencies
jScout is Ext-core depended (mostly on Observable features)
jScout is patching Ext core for synchronous calls support. After including jScout, you can make synchronous calls, adding sync:true option to Ext.Ajax.request call
Ext.Ajax.request({ url : this.getUrl(), callback : function () { ........... }, scope : this, sync : true });
TODO
- Circular references deadlocks determination
- Better test coverage
BUGS
None yet )
See also
Author
Nickolay Platonov
Contact: http://extjs.com/forum/private.php?do=newpm&u=36826
Forum topic:
http://extjs.com/forum/showthread.php?t=40865
http://extjs.com/forum/showthread.php?t=40866
Copyright
Copyright(c) 2008, Nickolay Platonov
License
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
http://www.gnu.org/licenses/gpl-3.0.txt
Changelog
version 0.3 - added ability to safely mix sync and async package loading - migrated to Catalyst framework to improve test suite infrastructure (added local http server) - test suite cleaned - added synchronous calls test coverage
version 0.2 - added synchronous calls support - added libRoot configuration option
version 0.1 beta - initial release
