How to migrate to Aria Templates with noder-js

As explained in this blog article, the latest non-backward-compatible release of Aria Templates (1.6.1) changes the way in which dependencies are managed by the framework.

Thanks to the integration of noder-js, you are able to retrieve the dependencies you need in your modules with the require syntax, as dictated by the CommonJS specifications.

The purpose of this article is to describe the migration of your code to the new syntax.

Don’t panic

The migration will be straightforward for you. Indeed

  • the integration of noder-js with Aria Templates is done in such a way that current (non-CommonJS) Aria Templates classes (using for example $dependencies), can be loaded without any change
  • a tool to convert automatically your modules (classes, template scripts, interfaces) to the new syntax is already available. It can be executed either from the command line, or as a visitor of the atpackager (so that files are converted at build time).

There are only some edge cases that might involve more work on your side. This article will cast a special focus on these cases.

Class/Interface/Beans/tplScript definitions

Consider the following class definition:

Aria.classDefinition({
    $classpath : "src.MainClass",
    $extends : "src.BaseClass",
    $implements : ["src.MainInterface", "aria.templates.IModuleCtrl"],
    $dependencies : ["aria.utils.Json", "anotherSrc.Utility"],
    $constructor : function () {
        this.$json = aria.utils.Json;
        this.$utility = new anotherSrc.Utility();
    },
    $prototype : {

    }
});

Now let’s have a look at how it can be converted to the new syntax:

var Aria = require("ariatemplates/Aria");
var srcMainInterface = require("./MainInterface");
var ariaTemplatesIModuleCtrl = require("ariatemplates/templates/IModuleCtrl");
var ariaUtilsJson = require("ariatemplates/utils/Json");
var anotherSrcUtility = require("anotherSrc/Utility");
var srcBaseClass = require("./BaseClass");

module.exports = Aria.classDefinition({
    $classpath : "src.MainClass",
    $extends : srcBaseClass,
    $implements : [srcMainInterface, ariaTemplatesIModuleCtrl],
    $constructor : function () {
        this.$json = ariaUtilsJson;
        this.$utility = new anotherSrcUtility();
    },
    $prototype : {

    }
});

You can notice that

  • all classes/interfaces that need to be loaded before the classDefinition method is executed (namely all classes in $dependencies, $implements, $extends) are now available through the require method.
  • The paths referring to these classes are not the standard classpaths we are used to: classes in the aria package are retrieved using a path starting with ariatemplates, the other ones are built according to the location of the MainClass.
  • The output of the classDefinition method is copied to the standard module.exports property of the module.
  • The $dependencies property is removed. In fact, mixing both syntaxes in the same module, which means using both require and $dependencies in the same file, is not supported. Aria Templates detects the format (either CommonJS or non-CommonJS) with the following rule: a file is considered using the CommonJS syntax if it either contains calls to require or if it does not contain any Aria definition (recognized by a call to Aria.classDefinition or Aria.interfaceDefinition or Aria.beanDefinitions or Aria.tplScriptDefinition).

The same considerations apply to interface, tplScript and bean definitions. For example, it is straightforward to imagine that

Aria.beanDefinitions({
    $package : "src.CfgBeans",
    $namespaces : {
        "json" : "aria.core.JsonTypes",
        "coreBeans" : "src.CoreCfgBeans"
    },
    $beans : {
    }
});

becomes

var Aria = require("ariatemplates/Aria");
var ariaCoreJsonTypes = require("ariatemplates/core/JsonTypes");
var srcCoreCfgBeans = require("./CoreCfgBeans");
module.exports = Aria.beanDefinitions({
    $package : "src.CfgBeans",
    $namespaces : {
        "json" : ariaCoreJsonTypes,
        "coreBeans" : srcCoreCfgBeans
    },
    $beans : {
    }
});

As explained in noder-js documentation, this is the flow of operations performed by the framework:

  • when file src/CfgBeans.js is fetched from the server, its content is parsed in order to look for calls to the require method.
  • All required dependencies (whose paths are string literals provided as arguments to the require method) are fetched from the server. For instance, in the second sample above, files ariatemplates/Aria.js, ariatemplates/core/JsonTypes.js and src/CoreCfgBeans.js are retrieved (unless they are already available). The javascript code they contain is not yet executed.
  • Once all dependencies are available, the code contained in src/MainClass.js is evaluated. The actual evaluation of the code contained in the dependencies occurs when the corresponding require method is called. For example, code contained in src/CoreCfgBeans.js will be evaluated only when require("./CoreCfgBeans") is called within the execution of src/CfgBeans.js.

Remark: the at-noder-converter tool is able to automatically perform the conversion shown above.

 

$resources

We showed how to use the require syntax when the dependency is static, so that it can be hard-coded as a string. This strategy won’t work with localized resources, whose physical path will depend on the locale that is configured for your application.

Consider the following class

Aria.classDefinition({
    $classpath : "src.UtilityClass",
    $resources : {
        myFirstRes : "src.UsefulRes",
        mySecondRes : "aria.utils.UtilsRes"
    }
});

When this class is loaded, the resource file that will be fetched as a dependency will change according to the locale settings (for example src/UsefulRes_en_US.js if you set en as primary language and US as region). This means that it’s not possible to hard-code the path and provide it as an argument of the require method.

In order to circumvent this limitation and allow for dynamic dependencies, it is possible to use a so-called “loader plugin“: a special type of module whose code is evaluated before executing the require method that caused its loading. Any module whose filename starts with $ is identified by noder as a loader plugin. Let’s take a look at the following example in order to get more insight about how it works and why it can be used for dynamic dependencies management.

Consider the following module (for future references, let’s call it sampleModule)

// sampleModule.js
var myResource = require("./$myResLoader").bundle("myResFileName");

and its dependency, $myResLoader

// $myResLoader.js
var getFullFileName = function (path) {
    return path + someMethodToGetLocale();
};

var bundle = exports.bundle = function (bundleName) {
    // since the argument of the require is not a literal, the dependency won't be fetched before executing this file
    return require(getFullFileName(bundleName));
};

var asyncRequire = require('noder-js/asyncRequire').create(module);

bundle.$preload = function (bundleName) {
    return asyncRequire(getFullFileName(bundleName));
};

This is the flow of operations performed by the framework:

  • when file sampleModule.js is fetched from the server, its content is parsed in order to look for calls to the require method.
  • Since the filename of the required module starts with a $, file $myResLoader.js will be fetched and executed.
  • On top of that, noder realizes that in sampleModule.js there is a call to the bundle method of module $myResLoader. It will then check if a $preload property exists in the bundle function and call it by providing as parameter the same parameter that will be used later to actually call the bundle method (that’s why it has to be a static literal).
  • The $preload method is asynchronous, it returns a promise. In this case, the $preload method is making an asynchronously loading a module whose name is obtained from the original bundle name by appending the locale (for more information about the asyncRequire module go to this page).
  • When this promise is resolved (namely when myResFileName_en_US.js module is fetched), then finally the original module sampleModule is evaluated. In our scenario, the bundle method of $myResLoader module is executed. It returns a require of myResFileName_en_US.js, which will immediately be available because it will have been pre-loaded.

The loader plugin mechanism is a generic feature of noder, but it perfectly suits the resources management usecase. That’s why a loader plugin module has been included in the framework in order to handle resources: ariatemplates/$resources. Its behaviour is very similar to the one described by our example. It exports a file method (equivalent to the bundle method of our example), which receives as parameter the path of the resource to load (or the name of the directory and the path of the resource). It is able to load the localized resource, just like it used to happen in previous versions of Aria Templates.

This means that class src.UtilityClass can be transformed into:

var Aria = require("ariatemplates/Aria");
var srcUsefulRes = require("ariatemplates/$resources").file(__dirname, "./UsefulRes");
var ariaUtilsUtilsRes = require("ariatemplates/$resources").file("ariatemplates/utils/UtilsRes");
module.exports = Aria.classDefinition({
    $classpath : "src.UtilityClass",
    $resources : {
        myFirstRes : srcUsefulRes,
        mySecondRes : ariaUtilsUtilsRes
    }
});

Remark: once again, the at-noder-converter tool is able to automatically perform this conversion.

$resources providers

Sometimes resources are not just static files to retrieve but they are fetched by a provider class.
Consider this example:

Aria.classDefinition({
    $classpath : "src.ClassWithProviders",
    $resources : {
        myFirstRes : {
            provider : "src.FirstResProvider",
            onLoad : "_afterResOneLoaded",
            handler : "/myFirstUrl",
            resources : ["resA", "resB"]
        },
        mySecondRes : {
            provider : "anotherSrc.SecondResProvider",
            handler : "/mySecondUrl"
        }
    },
    $prototype : {
        _afterResOneLoaded : function () {
            // this method will be called once the first resourse providers data will have been fetched
        }
    }
});

When executing the Aria.classDefinition method, the framework retrieves the classes specified as providers and creates an instance of them. This instance will be injected in the prototype of class src.ClassWithProviders when the class prototype is built.
Unless an onLoad property is specified in the provider configuration, the framework will have to wait until the resources are fetched (each provider must have an asynchronous fetchData method) before finalizing the load of the class. If the onLoad is provided, then the framework will not wait for the corresponding provider to fetch the data: the method specified in the onLoad property will be called when the data retrieval is finalized. The provider data can be obtained by calling the getData method on the created instance. From within class src.ClassWithProviders you can hence call

this.myFirstRes.getData();
this.mySecondRes.getData();

In order to convert the syntax of a class using resources providers, another loader plugin is available in the framework: ariatemplates/$resourcesProviders. So what does the converted class look like?

var Aria = require("ariatemplates/Aria");
var firstRes = require("ariatemplates/$resourcesProviders").fetch(__dirname, "./FirstResProvider", "src.FirstResProvider", "_afterResOneLoaded", "/myFirstUrl", "resA", "resB");
var secondRes = require("ariatemplates/$resourcesProviders").fetch("", "anotherSrc/SecondResProvider", "src.FirstResProvider", "", "/mySecondUrl");

module.exports = Aria.classDefinition({
    $classpath : "src.ClassWithProviders",
    $resources : {
        myFirstRes : firstRes,
        mySecondRes : secondRes
    },
    $prototype : {
        _afterResOneLoaded : function () {
            // this method will be called once the first resorse providers data will have been fetched
        }
    }
});

The $resourcesProviders loder plugin exports method fetch that returns the instance of resources provider that was created for that class. It receives the following parameters

  • the reference path of the provider module
  • the path of the provider module
  • the classpath of the class declaring the resources. It has to be provided so that the plugin can retrieve its prototype and call the method specified in the onLoad property (if any)
  • the method of the caller prototype to call after the resources are fetched
  • the handler
  • the flattened list of resources.

The reason why the provider configuration has to be flattened before passing it over to the fetch method lies in the way noder-js parser works: if the arguments of the loader plugin method are different from a string (or standard variable like __dirname, __filename or module), then the flow explained above (fetch the required class, call the fetch.$preload method and so on) will not be executed.

Remark: once again, the at-noder-converter tool is able to automatically perform this conversion.

Variable resources

Sometimes resources paths (or arguments, in the case of providers) are not hard-coded strings. in that case, using the simple conversion above won't work for the same reasons already explained at the end of the previous section.

Consider the following example

Aria.classDefinition({
    $classpath : "src.UtilityClass",
    $resources : {
        myFirstRes : "src." + myGlobal.getResourceName()
    }
});

in this case you cannot convert the class to

var Aria = require("ariatemplates/Aria");
var srcUsefulRes = require("ariatemplates/$resources").file("src." + myGlobal.getResourceName());
module.exports = Aria.classDefinition({
    $classpath : "src.UtilityClass",
    $resources : {
        myFirstRes : srcUsefulRes
    }
});

Noder-js will not correctly execute the preload of the resource.

In these situations, you are forced to write your own very simple loader plugin. It looks like this

// src./$myLoaderPlugin.js

var resources = require("ariatemplates/$resources");

var myFetch = function () {
    // since the argument of the require is not a literal, the dependency won't be fetched before executing this file
    return resources.file("src." + myGlobal.getResourceName());
};


myFetch.$preload = function () {
    return resources.file.$preload("src." + myGlobal.getResourceName());
};

module.exports = {
    myFetch : myFetch
};

This plugin will help you re-write your class:

var Aria = require("ariatemplates/Aria");
var srcUsefulRes = require("./$myLoaderPlugin").myFetch();
module.exports = Aria.classDefinition({
    $classpath : "src.UtilityClass",
    $resources : {
        myFirstRes : srcUsefulRes
    }
});

Templates and resources definitions

For the time being, templates (.tpl, .tml, .tpl.css, .tpl.txt, .cml) cannot be migrated. The same holds for resources definitions.

Conclusions

In this article we discussed the impact of a migration to a version of Aria Templates (1.6.1) which uses noder-js as a dependency manager. The changes that you will see in your code have been analysed by providing more insight on how noder-js actually works (with a particular focus on loader plugins).
The good news is that you are likely not to perform the changes yourself, as you can rely on the at-noder-converter tool.

In some cases, this tool won't be able to do the job, so you will have to write a little bit of code yourself, but it shouldn't be too cumbersome!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>