Moduler i JavaScript med CommonJS

JavaScript är ett av våra mest underskattade programmeringsspråk. Detta beror på flera saker; det har förmodligen skrivits fler fulhack i JavaScript än i något annat språk, möjligtvis med undantag för Perl. Dessutom har JavaScript några rejäla skönhetsfläckar. De flesta brukar uppröras av jämförelseoperatorerna som ibland gör oväntade typkonverteringar. Personligen tycker jag att avsaknaden av ett modulsystem är värre.

CommonJS är ett initiativ startat för tre år sedan under namnet ServerJS av Kevin Dangoor. Det syftar till att göra JavaScript till ett seriöst alternativ som serverplattform. CommonJS innehåller ett antal API-specifikationer, exempelvis för filsystemaccess, hantering av binärdata, kommandoradsargument och utmatning till konsol. Dessutom finns en specifikation för modulhantering: CommonJS Modules. Enligt denna specifikation publicerar man variabler och funktioner med exports och importerar dem med require. Så här kan en modul se ut:

movieFinder.js

var priv = [];
 
exports.add = function (item) {
    priv.push(item);
};
 
exports.findAll = function () {
    return priv;
};

Variabeln priv är inte tillgänglig utanför modulen. Det är däremot funktionerna add och findAll, eftersom de lagts till objektet exports. Om en modul behöver importera en annan görs det med require:

movieLister.js

var finder = require("./movieFinder");
 
exports.moviesDirectedBy = function (arg) {
    var hasDirector = function (movie) {
        return movie.getDirector() === arg;
    };
 
    return finder.findAll().filter(hasDirector);
};

Dessa två moduler kommer bara att finnas i ett exemplar vardera. Om man istället behöver en klass där man kan instansiera objekt tilldelar man en konstruktor till exports:

Movie.js

var constructor = function (title, director) {
    return {
        getTitle: function () {
            return title;
        },
 
        getDirector: function () {
            return director;
        },
 
        toString: function () {
            return title + " by " + director;
        }
    };
};
 
module.exports = constructor;

Notera att det här är ett av många sätt att skriva konstruktorer i JavaScript. Douglas Crockfords JavaScript: The Good Parts har ett helt kapitel om detta. Huvudmodulen kan sedan se ut så här:

main.js

var Movie = require("./Movie");
var movieFinder = require("./movieFinder");
var movieLister = require("./movieLister");
 
movieFinder.add(Movie("Terminator", "James Cameron"));
movieFinder.add(Movie("Blade Runner", "Ridley Scott"));
movieFinder.add(Movie("Titanic", "James Cameron"));
 
var movies = movieLister.moviesDirectedBy("James Cameron");
 
for (var i = 0; i < movies.length; i++) {
    console.log(movies[i].toString());
}

För att allt det här ska fungera behöver man naturligtvis en implementation av CommonJS Modules. På serversidan implementerar exempelvis Node.js specifikationen. Om man skriver kod som ska köras i browsern kan man bland annat välja på inject och require.js.