Skip to content

jaubourg/usejs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

75 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

usejs

usejs is a simple module loader and dependency manager for the browser. It provides sandboxing of modules and the means to transform any script into a usejs module.

usejs weights only 1.48kB when minified gzipped and is released under the MIT license.

usejs has been tested in Internet Explorer (6 to 9), Chrome (latest), Firefox 5, Opera (latest) and Safari (latest).

Getting the library

Build the library

  • Clone this repository
  • Make sure you have nodejs installed
  • Enter the usejs directory and type node .

Load the Library

Yep, even a loader has to be loaded. Simply point a script tag to where the usejs script is located:

<!-- minified version -->
<script src="path/to/script/use.min.js"></script>

<!-- non-minified version -->
<script src="path/to/script/use.js"></script>

Usage

Your First Module

Nothing speaks like a simple example. So let's say you have a file called simple.js as follows:

var myVar;

use.expose({
    get: function() {
        return myVar;
    },
    set: function( value ) {
        myVar = value;
    }
});

You'd load simple.js by adding the following code into an inline script block in your main HTML page:

use( "path/to/script/simple.js", function( simple ) {
    console.log( simple.get() ); // will output "undefined"
    simple.set( "hello world" );
    console.log( simple.get() ); // will output "hello world"
    console.log( window.myVar ); // will output "undefined"
});

Everything is pretty straight-forward; use will load the files which URLs have been provided then, when they completed their execution, will call the callback with the modules as arguments.

A module is just the collection of methods and attributes that have been exposed using use.expose.

The final line of the callback in the last snippet needs some explaining: despite myVar being declared globally in simple.js, it is not available as a global variable in the main HTML file. That's because, in usejs, modules are sandboxed. Note that sandboxing doesn't involve any wrapper around the module's code so it will work cross-domain!

Now, let's say you do the following later in your page:

use( "http://mydomain/path/to/script/simple.js", function( simple ) {
    console.log( simple.get() ); // will output "hello world"
});

The exact same module is given to this second callback. This works because usejs caches modules and is able to recognize when the same module is required even when mixing absolute and relative URLs.

You can also use a hash in the URL passed to use in order to extract a specific element from a module:

use( "path/to/script/simple.js#get", function( get ) {
    console.log( get() ); // will output "hello world"
});

In-Module Dependencies and Relative URLs

Now, let's say you have another module, dependent.js which is located in the same directory as simple.js:

use( "simple.js#get", function( get ) {
    use.expose({
        show: function() {
            console.log( get() );
        }
    });
});

First of all, simple.js is required using a URL relative to dependent.js. usejs always knows in which path context the current module is and will resolve URLs accordingly.

Then note that we expose the show method only when simple.js is loaded. usejs will keep track of outbound requirements and wait for them to complete before it considers it loaded. So everything will work as expected if you add the following code to your page:

use( "path/to/script/dependent.js", function( dependent ) {
    dependent.show(); // will output "hello world"
});

Furthermore, since use can accept any number of requirements, we could do the following:

use( "path/to/script/dependent.js", "path/to/script/simple.js", function( dependent, simple ) {
    simple.set( "... and good-bye!" );
    dependent.show(); // will output "... and good bye!"
});

Beyond Expose

Sometimes, it can be handy for a module to be something more than just a plain object, like a function for instance.

Let's create a function.js module that does just that:

use.module(function() {
    return use.module()._data;
});

use.expose({
    _data: "hello world";
});

The use.module function will return the module as it's currently set. If a non-null, non-undefined value is given, then the module is set to this value. In our example, we set the module to a function then add the _data attribute onto it. We can then use function.js as follows:

use( "path/to/script/function.js", function( func ) {
    console.log( func() ); // will output "hello world"
});

Turning any Script into a Module

Often, you'll need to load scripts that are not modules. Thankfully, usejs provides a means to transform any script into a module. For instance, here is how you would transform jQuery into a usejs module:

use.bridge( "path/to/script/jQuery.js", function( use ) {
    if ( window.jQuery ) {
        use.module( window.jQuery.noConflict( true ) );
    } else {
        throw "cannot load jQuery";
    }
});

The callback is called when the script has been loaded. Obviously, such a script is always loaded within the main HTML document so that it has access to the main global window. Inside of the callback, the use provided as the single parameter will work as if you were in your usual module sandbox. Context (this) of the callback is the global object for said module.

Within the callback, URLs are resolved relatively to the path of the page and/or module where the use.bridge statement is located, not relatively to the directory into which the script being treated is located.

Note that redefining a script to module bridge will erase the previous one.

Using use.bridge will just let usejs know that the script needs extra-care: you still have to use the jQuery script for it to be actually loaded:

use( "path/to/script/jQuery.js", function( $ ) {
    $( "head title" ).text( "jQuery loaded" ); // Will change the page title to "jQuery loaded"
});

Random asynchronous operations

Sometimes, you wish to wait for an asychronous operation to finish before tagging the module as loaded. For instance, you may need to read a json configuration over the network. Here is how to proceed:

use.hold(function( release ) {
    use( "path/to/script/jQuery.js", function( $ ) {
        $.getJSON( "path/to/mydata.json" ).done(function( data ) {
            // Expose whatever you need to expose
            release();
        });
    });
});

use.hold accepts a single callback as a parameter. This callback will get passed a release function to be called when the asynchronous operation is finished: as simple as that!

Routing

One thing usejs is particularly good at is URL resolution and aliasing.

Say you're developping your application and use a non-minified version of your module:

use( "path/to/my/module.js", function( module ) {
   // ...
});

Now you want to switch to the minified version in production, but the module is used in a lot of different other modules and you don't feel like going through all of them to rewrite the url. You could do something like this:

use.route( "path/to/my/module.js", "path/to/my/module.min.js" );

Now, whenever module.js is used, it's actually module.min.js that'll be used.

Sometimes, you need to refactor modules and one can become a sub-element of yet a bigger module in the process, it's pretty easy to let usejs know:

use.route( "path/to/my/module.js", "path/to/my/module/biggerModule.js#module" );

You can even point to fields in the module as if it was in its own file:

use( "path/to/my/module.js#field", function( module ) {
    // ...
});
// is equivalent to
use( "path/to/my/module/biggerModule.js#module.field" );
    // ...
});

Even better: what if you're waiting for a colleague to deliver a module yet need a mockup to test your application now. Usually, you'd have to create a new file and deal with replacing it when the real module is finally delivered. In usejs, you could use routing in the same fashion you would use.bridge:

use.route( "path/to/my/nonExistingModule.js", function( use ) {
   // use, expose, hold, whatever you need
});

After this definition, you can use nonExistingModule.js as if it was an actual file.

All of this is fine, but what if you need to reference to a module globally so that, given a convention, modules of different domains may still refer to the same thing without lengthy, absolute, unreadable and unflexible paths?

Well, routes are just URLs but they're not limited to existing protocols, so you can easily use custom protocols as you see fit:

use.route( "jquery:core", "path/to/script/jQuery.js" );
use.route( "namespace:path/to/element", "http://myDomain/path/to/actual/module.js" );

No matter the domain or path it's in, the following statement will always load the same file:

use( "namespace:path/to/element", function( alwaysAsConfigured ) {
	// ...
});

Utilities

First of all, use and all its methods are chainable, so the following will work:

use.bridge( "path/to/script/jQuery.js", function( use ) {
    if ( window.jQuery ) {
        use.module( window.jQuery.noConflict( true ) );
    } else {
        throw "cannot load jQuery";
    }
}).route( "jquery:core", "path/to/script/jQuery.js" );

Then use exposes two utility methods:

  • use.type which returns the type of the given argument (akin to jQuery.type)
  • use.resolve which resolves the URL according to the context (ie. the path of the module it's called from)

Future Developments

The library should be usable as is. It obviously needs some tweaking and more thorough unit testing. But what it needs most is you. Toy with it, test it to its limits then report bugs or, even better, propose pull requests!

Features we intend to add in the future:

  • an easy way to create plugins (use.hold makes it easy to define blocking asynchronous tasks, we just need a hook in usejs)
  • the possibility to load a script without having it defined as a module (very useful for legacy libraries)
  • provide a build tool to merge as much dependencies as possible as an initial, minified, JavaScript file for production use.

About

Yet another Module/Dependency Manager

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published