Open Source RDBMS - Seamless, Scalable, Stable and Free

한국어 | Login |Register

Current Events
Join our developers event to win one of the valuable prizes!
posted 2 years ago
viewed 7970 times
Share this article

Toward JavaScript Standards: CommonJS and AMD

The prerequisite to using JavaScript for general purposes is modularity. Node.js was also created because of modularization. CommonJS and AMD are leaders of JavaScript modularization. In this article, we will give a brief description of the JavaScript modularization of CommonJS and AMD.

CommonJS

CommonJS is a voluntary working group organized to use JavaScript not only in browser, but also in server-side and desktop applications. Common in CommonJS reflects its resolution that JavaScript is not only for Web browsers but is a general language.

The group makes a “Specification” that enables its use for general purposes. The group has so far defined Module specification 1.0, 1.1, 1.1.1, etc. Node.js module also complies with Module specification 1.0.

Development Background

Since the development of JavaScript in 1996, efforts to use JavaScript outside of browsers have continued. However, some typical projects, such as Helma, AppJet, Jaxer, Persever, Cappucino, and Rhino, were not very successful.

Table 1: JavaScript generified project
Name of project Related sites Description
Helma http://dev.helma.org/ This site provides a server-side JavaScript development environment created by Java. The final version was released on Feb. 10, 2010, and declares support to RingoJS.
AppJet http://www.appjet.com/ The company was acquired by Google.
Jaxer http://jaxer.org/ Famous for the Aptana-embedded JavaScript server. Aptana was acquired by Appcelerator, the maker of Titanium.
Persever http://www.sitepen.com/blog/category/persevere/ This was made to easily transplant the Dojo module to the server-side JavaScript module.
Cappucino http://cappuccino.org/ This uses Object-Java, a language created based on Script for making desktop applications.
Rhino http://www.mozilla.org/rhino/ This is a server-side JavaScript engine created by Java.

The importance of JavaScript is gaining emphasis amid the growth of Ajax in 2005. With the activation of Ajax, JavaScript operation has increased and naturally requires faster JavaScript engines. In this context, the V8 JavaScript released by Google in 2008 has drawn much attention. The V8 engine was much faster than the existing JavaScript engine, and demonstrated impressive performance that could be used outside of browsers.

The emergence of the V8 engine has sparked server-side JavaScript. In January 2009, Kevin Dangoor started suggesting ideas about server-side JavaScript on his blog, and called people together. For server-side JavaScript to be successful, Kevin believed it necessary to set up a standard and activities to sustain it, rather than focus on the technical context. So began the CommonJS group, and it presented CommonJS API 0.1 in just three months.

Note:
At http://www.commonjs.org/history/, you can see a summary of the history of CommonJS projects.

Major Issues of Server-side JavaScript

Kevin argued the necessity of a system, such as Ruby or Python, for JavaScript to be used globally in order to be more than a mere browser language. The core problems Kevin proposed are as follows:

  • There is no compatible standard library.
  • There is no standard interface that could connect to a database.
  • There is no standard method to insert different modules.
  • A method to package codes for distribution and installation is necessary.
  • Module storage of a common package that solves dependency is necessary.

Modularity is the Key

Therefore, all the aforementioned problems lead to modularity. The major specifications of CommonJS relate to how to define and use the modules.

Modularity is divided into three sections, as follows:

  1. Scope: Each module must have an independent execution area.
  2. Definition: All modules use export objects.
  3. Usage: All modules use required functions.

First, a module should have an independent execution area of its own. Hence, it is crucial to separate global variables from local variables. Each server-side JavaScript has an independent file scope, and thus one module per file will solve problems. That is, global variables do not overlap even if service-side JavaScript is written as follows:

fileA.js

var a = 3;
b = 4;

fileB.js

var a = 5;
b = 6;

If information exchange is necessary between two modules (files), it can be shared via a global object called exports. In the following examples, the sum function of the fileA.js file is exposed outside.

fileA.js

var a = 3;
b=4;
exports.sum = function(c, d) {
    return a + b + c + d;
}

fileB.js

var a = 5;
b = 6;

To use exposed functions in other modules, use require() functions as follows:

fileA.js

var a = 3;
b=4;
exports.sum = function(c, d) {
    return a + b + c + d;
}

fileB.js

var a = 5;
b = 6;
var moduleA = require("fileA");
moduleA.sum(a,b); // 3+4+5+6 = 18

The module specification of CommonJS in the following examples is based on the preconditions that all files are on the local disk and can be imported upon request. In other words, the server-side JavaScript environment is subject to conditions.

This method, however, is a fatal weakness for browsers. You cannot do anything until all of the necessary modules have been downloaded. There have been many discussions in CommonJS about how to cope with this weakness. A solution is to dynamically insert the <script> tag. Dynamic insertion of the <script> tag is the most common method used by JavaScript loaders.

Problems with Asynchronous Module Loading

When JavaScript is operated on a browser, there is no file scope, unlike server-side JavaScript. When fileA and fileB, the examples covered earlier, are loaded in order by using the standard <script> tag, global variable issues will occur where fileB’s variable overwrites all the variables of fileA.

To tackle this problem, CommonJS additionally defined a module transport format, which delivers server modules to the client asynchronously. In accordance with the specification, server modules can be loaded asynchronously if modules used on the server side are enclosed with transport format, like the browsers shown in the following examples.

Modules used in server-side

// complex-numbers/plus-two.js

var sum = require("./math").sum;
exports.plusTwo = function(a){
    return sum(a, 2);
};

Modules used on browsers

// complex-numbers/plus-two.js

require.define({"complex-numbers/plus-two": function(require, exports){
    //Define modules in the callback function.
    var sum = require("./complex-number").sum;
    exports.plusTwo = function(a){
        return sum(a, 2);
    };
}, ["complex-numbers/math"]);
//First, write the module that should be loaded.

What you need to be concerned about with modules that use browsers is that global variables are being controlled through require.define() function (function closure).

Note:
For more details on the CommonJS module transport format and specifications, see http://wiki.commonjs.org/wiki/Modules/Transport.

Compliance with CommonJS

Currently, CommonJS is a de facto standard. Many third-party vendors are making modules or module-load systems according to the CommonJS module specification. Node.js is a typical project that complies with the specification. The loaders and frameworks shown below also comply with the CommonJS module specification.

As you can see in the list above, CommonJS is not necessarily limited to server-side. However, using CommonJS for server-side is more advantageous, as it was created to use JavaScript from server-side.

AMD

CommonJS is not the only JavaScript Standard API library production group, there is also AMD (Asynchronous Module Definition). The AMD is separated from the production group, as it failed to reach an agreement in discussions with CommonJS about using JavaScript module in an asynchronous situation.

CommonJS created JavaScript as part of an effort to retrieve it outside of browsers; thus, CommonJS could not generate agreement with AMD, which was focused on operation within browsers. The CommonJS Wiki also reveals that AMD is an independent developer.

Actions toward Standardization of both branches

AMD aims to make a standard that allows the use of modules in a browser environment, which requires necessary modules be downloaded via a network. Currently, debates on JavaScript modularity are mainly divided into CommonJS and AMD. Of the two, we cannot say which one is better, as AMD also did not define a module for JavaScript that operates on browsers.

Module loaders and frameworks that support AMD are as follows:

Comparison between CommonJS and AMD

The difference in the module specifications defined by the two territories are in module load.

In a situation where all the necessary files can be imported from local disk; that is, for server-side, CommonJS method is simpler than AMD. On the other hand, AMD offers a more flexible approach than CommonJS in a browser environment, where you need to download the necessary files through a network.

Module Specification of AMD

As you can see in “Asynchronous Module Definition,” AMD handles standards for asynchronous modules (which allows the necessary modules to be downloaded over a network). CommonJS also provides a module transport format that considered asynchronous modules, but they could not reach consensus with people who support genuine AMD. These are the environments in which AMD provides features that are compatible or greatly similar to those of CommonJS. You can use require() function, and define a module in the exports type.

There are also characteristics unique to AMD. The most typical one is the define() function. As there is no separate JavaScript file scope for a browser environment, the define() function plays this role. In other words, it plays a kind of namespace role and separates variables that are used in modules from global variables. The define() function is a global variable, and a third-party vendor that implements the AMD specification must implement it in module loaders.

There is also a property called define.amd. For more details about AMD, see this online presentation (JavaScript powered presentation!).

define() function

As shown below, define the define() function as a global variable:

define(id?, dependencies?, factory);

The first parameter id is used to identify a module, and should be used selectively. If there is no id, configure the src value of the <script> tag requested by loader to the basic id. You do not need to use it if there is nothing to specify. If you are specifying id, the absolute path must be the identifier.

The second parameter is an array to display the dependency of a module to be specified; it displays modules that must be loaded first. The modules loaded first are delivered to the parameter of a factory function, which is the third parameter. The second parameter is also used selectively, but it can be omitted and specified with a basic name such as ['require', 'exports', 'module']. These three modules all function as global variables defined by CommonJS.

The third parameter is a factory function; it handles the actual implementation of which instantiate modules or objects. If a factory parameter is a function, it performs one time in a singleton; if any value returns, the value is assigned as an attributed value of exports objects. On the other hand, if a factory parameter is an object, it is assigned as an attributed value of exports objects.

Global Variables and define.amd Property

Global variables defined in the AMD specification are: define, require objects used in CommonJS, and exports objects. In addition, the define.amd property can be used, which is used to explicitly indicate variable modules. However, you should not add other global variables or methods (or property).

Examples of Modules Defined by AMD

The following examples are basic AMD modules that use all three parameters; this illustrates the necessity of the beta module in order to define the alpha module.

define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
    exports.verb = function() {
        // You may use received parameters,
        return beta.verb();
        
        // or use require()
        // to use obtained modules.
        return require("beta").verb();
    }
});

The second example omits the first parameter; here, an unnamed module that requires the alpha module is created. If you want to use the module with the require() function, you should assign the file path specified for the module.

define(["alpha"], function (alpha) {
    return {
        verb: function(){
            return alpha.verb() + 2;
        }
    };
});

// Suppose the module above can be accessed with
// http://somewhere.com/js/modelBeta.js,
require(["/js/modelBeta.js"], function(moduleBeta)(){
    // the actual code that uses moduleBeta should be here.
});

The following example defines modules that have no dependency. This module also has no first id identifier; thus, a file path with the defined module will be assigned automatically as identifier.

define({
    add: function(x, y){
        return x + y;
    }
});

Finally, you can also wrap a module of the CommonJS pattern.

define(function (require, exports, module) {
    var a = require('a'),
    b = require('b');
    exports.action = function () {};
});

Strength of AMD

The strength of the AMD module specification is that it not only performs very well in an asynchronous environment, but also with the same code on the server side. In addition, it is definitely simpler and clearer than the module transport of CommonJS.

The AMD specification uses the define() function (a module pattern by using closure) to implement a module, and thus has no global variable issues. You can also utilize the Lazy-Load technique, which loads the corresponding module when it is needed.

In terms of performance, there are many benefits when used in old versions of Internet Explorer, while it demonstrates similar performance on the latest browsers. Merging and distributing into one file is recommended to achieve the optimum performance, but there are no significant differences in performance even if the AMD loader is used.

RequireJS

RequireJS is one of the high-profile JavaScript loaders, which completely implemented the AMD specification and also supports the CommonJS style format. The greatest advantage of RequireJS is its neat API and documentation, as well as ease of use. It is also small.

I applied it to my personal homepage (http://miconblog.com) to check its simplicity.

First, add the require.js library to an index page, as shown below:

<script data-main="js/main.js" type="text/javascript" src="js/require.js"></script>
// notice here that we used "data-main" attribute which sets the baseUrl path
// for require.js relative to this path, i.e. now baseUrl = 'js'.

Then, dynamically load the Jindo framework from the main.js file.

// jindo.min.js is inside js/ directory
require(["jindo.min"], function(jindo) {
    // The actual implementation goes here...
})

Finally, wrap the Jindo framework (jindo.min.js) to be loaded with define() function.

define(function(){
    // Jindo implementation code location ...
    return jingo;
    // Return the final Jindo namespace object
}

If you write and execute as shown above, the jindo.min.js file will be dynamically loaded, as follows. You will see normal operation despite the fact that the jindo.min.js file is loaded later than the main.js file.

web_resource_load_waterfall.png

Figure 1: Jindo framework loaded via the Lazy-Loading method.

Modularity and HTML5

Recently, there are some cases in which localStorage of HTML5 interworking is utilized in module loaders. In other words, localStorage is utilized to optimize performance, which is used to load modules from a remote server. Certainly, a module loader is responsible for dependency according to versions.

Note:
You can see some examples of AMD compatible loaders that utilize localStorage at https://github.com/zazl/lsjs.

Thus far, the framework has been the solution for all problems, but the time will come when we will be able to use only the necessary modules. Modularity has become even more crucial as size grows in importance, especially in mobile platforms. In smartphones that use the latest browsers, performance optimization via localStorage seems to be essential.

In Conclusion

Modularity is also the key to AMD. The biggest strength of AMD is that it can be applied to a browser environment now. Moreover, JavaScript modularity will emerge as an important issue soon. Currently, JavaScript standardization is progressing, and ECMAScript Harmony is also preparing for modules. The summarized details show that discussions about the Script standardization are well underway. It is difficult to estimate when ECMAScript Harmony will become a reality.

However, there is no need to wait for ECMAScript Harmony. In the market, there are many useful loaders that implement AMD, and the current JavaScript also makes it possible to develop servers and clients. We can even develop desktop applications, as well as mobile Apps.

The day when JavaScript is redefined as a more general and advanced language is not far away.

By Sohn Byung-dae, Mobile Ajax Team, NHN Corporation.

About the auhor:
The writer likes everything about JavaScript. He has a keen interest in NodeJS, CommonJS, even Titanium, including Java Scripting activities outside a browser. Along with development, Son also writes and translates articles.

Reference

  1. Kris Kowal, “CommonJS effort sets JavaScript on path for world domination”.
  2. Addy Osmani, “Writing Modular JavaScript With AMD, CommonJS & ES Harmony”.
  3. AMD versus CJS. What’s the best format?”.
  4. Kris Zyp, “Asynchronous CommonJS Modules for the Browser (and Introducing Transporter)”.
  5. Ben Cherry, “JavaScript Module Pattern: In-Depth”.
  6. Modules > Frameworks”.
  7. harmony:modules [[ES Wiki]]”.


comments powered by Disqus