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.

Go to the new Sencha Learning Center

From Sencha - Learn

Jump to: navigation, search

Contents

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: Image:JScout.jpg
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:

Image:JScout-loading.jpg

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

Kanban


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

This page was last modified on 25 September 2008, at 15:21. This page has been accessed 17,666 times.