Sander Spies
zondag 15 april 2012
Decoupling JavaScript with a PubSubManager
PubSubManager works as an intermediary between the code that wants to publish information, and code that wants to know if something happened. In some ways you could see it as being an application wide event system. By using an intermediary like PubSubManager, it doesn't matter anymore who is listening or who is throwing the event.
zaterdag 21 januari 2012
JavaScript dependency management
Large web applications often exist out of a lot of JavaScript, which makes it important to split JavaScript into multiple files. Otherwise the web application will become almost impossible to maintain.
Take for instance Google’s Gmail, which can be considered a large web application. Placing all code in one JavaScript file would cause serious maintenance problems for Google.
Script tags
| Of course, you can use multiple script tags within your HTML file to link everything together. However this isn’t really dependency management since you don’t define what every individual file requires. Also placing everything within the HTML file has several other issues. First of all it can increase the size of the HTML file considerably, which isn’t ideal for maintenance. Secondly, it takes the focus away from the task at hand. This might not seem like a big issue up front; however it’s different from what’s normal within languages like Java and C# where the dependencies are defined within the source file that you are editing. Thirdly, it increases the load time of the webpage in two ways: more latency due to the multiple HTTP requests and it blocks the loading of the page, because script tags are loaded synchronously. | Issues
Not dependency management
-
Big HTML file
-
Takes focus away from task at hand
-
Latency
-
Synchronous loading
|
Basically script tags aren’t a real solution when writing large web applications.
Solutions
Hence several solutions have emerged within the JavaScript community to address these problems, most notably CommonJS, RequireJS and the upcoming Harmony changes to JavaScript.
CommonJS modules
The CommonJS modules system is mainly targeted towards non-browser applications, but it’s also possible to use it for Web Applications when used on the server side. NodeJS supports this for server side JavaScript, and the upcoming 2.0 version of the Java Play Framework uses a variation to merge JavaScript files for the browser.
Button example - CommonJS style
Button.js
exports.Button = (function Button(){
var Button = function Button(id){
this.id = id;
};
Button.prototype.getId = function Button$getId(){
return this.id;
};
return Button;
})();
App.js
var Button = require("button.js");
var okButton = new Button("okButton");
console.log("this is button: ", okButton.getId());
Within the Button.js file, the Button gets exported and therefore is accessible from files which reference to Button.js with the require method. In this case App.js uses it to create a Button with ID “okButton”.
This solution however doesn’t solve the blocking issue within browsers and also create a new issue: the written JavaScript code is now dependent on server side processing.
RequireJS
While CommonJS is mostly targeted towards the server side, RequireJS also targets the client side. On the server side it does mainly the same as CommonJS and on the client side it resolves dependencies by injecting script tags. To support both server and client it passes functions to the require method, so that the loading of a script in the browser can happen asynchronously. Pretty neat and already used by various products like BBC's iPlayer and Firebug.
So how does this look in the Button example?
Button.js
define([], function(){
var Button = function Button(id){
this.id = id;
};
Button.prototype.getId = function Button$getId(){
return this.id;
};
return Button;
});
App.js
require(["button.js"], function(){
var Button = require("button.js");
var okButton = new Button("okButton");
console.log("this is button: ", okButton.getId());
});
Of course in a production environment you preferably want to merge the JavaScript files to limit the amount of latency. Server side options exist to merge JavaScript files which use RequireJS. However merging the JavaScript files still blocks the initial loading of the page. Therefore I recommend using two JavaScript files, of which the first one is require.js and the second one is the actual merged JavaScript file. This way only the require.js file blocks the initial loading, while the merged JavaScript file will be loaded asynchronously.
Harmony modules
The challenges for dependency management within JavaScript haven’t been unheard by the EcmaScript people. In the upcoming Harmony changes for EcmaScript, there is a specification for modules which partially addresses these issues.
A Harmony version of the Button example:
Button.js
module Controls {
export var Button =(function(){
var Button = function Button (id){
this.id = id;
};
Button.prototype.getId = function Button$getId(){
return this.id;
};
return Button;
})();
}
App.js
import * from Controls;
var okButton = new Button("okButton”);
console.log("this is button: ", okButton.getId());
By default the Harmony modules of course don’t get merged on the server side, however it’s very likely that tools will be developed for this.
Google Traceur can be used to try Harmony modules. It compiles JavaScript which contains Harmony modules to JavaScript that runs in the latest browsers. Although it seems it’s not totally according to spec at this moment of writing.
Conclusion
Dependency management in JavaScript isn’t well supported out of the box. Using script tags gives the following problems: not really dependency management, big HTML file, takes focus away from the task at hand, latency and blocks loading of the page.
Using RequireJS solves all these problems, and is therefore the recommended approach for JavaScript dependency management the coming years.