russian version

Modularity in JavaScript, aliases

Issue

In my previous article I stopped at unique files' names. Here I'll describe them and their usage.

Having realized the function of dependence indication require , I may not worry about the order of files' connection. But in the files I should indicate paths to the files they depend on. If I point a path with direct links, that will cause a lot of problems:

  • When moving the files, you should change their addresses everywhere
  • While development you should shift between libraries' versions

In fact, it's inconvenient. One would like to indicate the dependence on a functional or an object, but not on a file.

Solution

Our active discussions with Sergey Berezhnoy resulted in the following idea: divide files into groups (common and those related to project of a library) and give them names like "{alias}.name". These names could be translated both into file's name "alias/name.js" and into object's name "alias.name". Alias will identify group of files. That will allow using files as follows:

alias.object = {
    method: function(){
        alias.object1.method();
        alias.object2.method();

        loaded('{alias}.object');
    }
};
require(
    ['{alias}.object1', '{alias}.object2' ...],
    alias.object.method
);
        

Alias as a path to a file

So, in all the files we have a line like "{alias}.name". It should be substituted by a real path to a file. It turned out, that it's very convenient to connect at page a file that lies together with other files with this alias and take the path of this file as a basis.

<script src="path/alias.js"></script>

On loading this file sets the value of alias; the value is taken from scr tag script, i.e. when we change a path to a script, we change the value of alias.

var alias = {};
getBaseAndSetAlias('alias', 'alias.js', 'utf-8');
        

In the first line you create an object, so that all the objects from this alias were its methods. In the second line you activate the core method, which takes a path to a file (second argument) and gives it to alias (first argument). The third argument indicates files' encoding with the alias given. Below I'll show possible realization of this method.

First of all you should get tag script by file name.

var scriptsByFileName = [];
function getScriptByFileName (file, listener, /* private */ tries){

    // if file has already been asked for and has been found
    if(scriptsByFileName[file]){
       listener(scriptsByFileName[file]);
       return;
    }

    // set the amount of attempts to find
    // a file to zero, if it failed
    tries = tries || 0;

    // here you should filter out one file out of list
    // by name
    var scripts = document.getElementsByTagName('script');
    for (var i = 0, l = scripts.length; i < l; i++){
        var src = scripts[i].getAttribute('src');
        if(src && src.indexOf(file) >= 0){
            scriptsByFileName[file] = scripts[i];
            break;
        }
    }

    // if tag was found, we initiate listener with a tag
    // as a parameter
    if(scriptsByFileName[file]){
        listener(scriptsByFileName[file]);

    // otherwise try later
    }else if(tries < 100){
        window.setTimeout(function (){getScriptByFileName(file, listener, ++tries)}, 10);
    }
}
        

I've made this method asynchronous because sometimes you can execute a code of tag script, but the tag itself probably won't be in the document yet. All the following methods should also be asynchronous.

Now you can easily define a path to files.

function getBase(file, listener){
    function returnBase(script){
        var src = script.getAttribute('src');
        listener(src.substring(0, src.indexOf(file)));
    }
    getScriptByFileName(file, returnBase);
};
        

And set the value of alias.

var aliases = {};
function setAlias(name, value){
    aliases[name] = value;
};
        

Function, which realizes this getBaseAndSetAlias.

function getBaseAndSetAlias(alias, file, charset){
    this.getBase(file, function(base){
        setAlias(alias, base);
    });
};
        

Now you can construct paths to files by lines like "{alias}.name".

function construct(string){

    // replace dots by slashes
    string = string.replace(/\./g, '/');

    // replace alias by its value
    function replacealias(match){
        var alias = match.substr(1, match.length - 2);
        return aliases[alias];
    }
    string = string.replace(/\{[^\}]+\}/ig, replacealias);

    // delete odd double flashes
    var http = string.match(/^https?\:\/\//i) || '';
    string = string.replace(/^https?\:\/\//i, '');
    string = string.replace(/\/\//ig, '/');

    // resulting path
    return http + string + '.js';
};
        

As a result you can write files' names like "{alias}.name" in all dependences and notifications (require, loaded)). If you change the location of files, you should change a path only in tag script in HTML.

Alias in object's name

Only once I need to transform "{alias}.name" into object's name. But that's very important. I use the same method of indication in order to show the component type. But now I point to the component, not to the file.

function getComponent(location){

    // remember alias
    var alias = /^\{[^\}]+\}/.exec(location);
    alias = alias[0].substr(1, alias[0].length - 2);

    // path to an object
    var name = location.replace('{' + alias + '}.', '').split('.');

    // get component
    var Component = window[alias];
    for (var i = 0, l = name.length; i < l; i++){
        Component = Component[name[i]];
    }

    // return the component obtained
    return Component;
};
        

Having got the component, you can activate its initializing method and get the component on a page.

Result

Now I use the following constructions in my code:

<script src="path/alias.js"></script>

<div class="component" onclick="return {type:'{alias}.name'}"></div>
        
require(
    ['{alias}.object1', '{alias}.object2' ...],
    alias.object.method
);
        

To change the location of files, you should change the path only in one place.

Examples

You can take the same examples as in the previous article.
The file that loads scripts dynamically is here: http://jsx.ru/scripts/b/1.1.0/jsx.js.
The file that finds components on page is here: /scripts/unstable/Components.js
File alias.js, where the code given in the article is used, is attached to this page. In Firefox the result of the work is displayed in console.