As you may know Require.js is probably the most wide spread JavaScript module loader that is used for browser development nowadays. Everybody knows that in some ways Require (and AMD) sucks, especially when we are talking about the long lines of code dependencies that you need to declare and to write your module names twice:
define(['dep1', 'dep2', 'dep3', 'dep4', 'dep5'], function (dep1, dep2, dep3, dep4, dep5) { return function () { ... }; });
Personally I prefer CommonJS style and that’s where the idea behind Melchior.js is starting from. If you know the trick you can achieve something similar with Require.js too:
define(function (require) { var dep1 = require('dep1'); var dep2 = require('dep2'); });
But that was not enough for us and we came up with the idea of "Chainable Module Definition" to add some structure and make simplicity of method chaining to work for you in the browser when just creating modules or loading scripts.
Before we start the guide I need to mention that Melchior.js is not production ready yet but it’s fine enough to play with.
Simple example
The goal of this tutorial is to show you the fundamentals in the easiest possible way in order to be able to experiment with Melchior, through a minimalist example.
index.html
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Basic example of MelchiorJS integration</title> </head> <body> <div id="container"> <p>Check console and js files</p> </div> <script data-main="js/main" src="js/vendor/melchior.js"></script> </body> </html>
We begin with a basic structure. The script tag that calls melchior.js includes a "data-main" attribute pointing to the "js/main.js" file, without an extension (we can however keep it if we want).
Important part here is that if you want Melchior to act as a script loader you will need to add config in your main file. Please note that Melchior loads modules via XHR and thus you will need to open html file through a simple static server (here’s an example) in order to make it work properly.
// paths to modules melchiorjs.config({ paths: { 'module1': 'js/module1', 'module2': 'js/module2' } }) // your entry point .module('core') .require('module1') .require('module2', 'two') // aliased as `two` .run(function () { console.log(module1.getName() === two.getModuleOneName()); // true });
That’s how our main entry file will look like. The require() function takes two arguments: module name string and optional alias string that will be accessible inside run() block.
js/module1.js
melchiorjs.module('module1') .body(function () { var _name = 'module1 name'; return { getName: function () { return _name; } }; });
Here we return an object that exposes a getName() method allowing us to to get the private variable _name.
For the second module we have declared a dependency on the first one which becomes available under the same name in the body() function.
This module returns an object too, which exposes the getModuleOneName() method. Thus you see how we can make use of a dependency within a module. But you are totally free on what to do with those dependencies and I’m sure you will find some more interesting ways than in the example!
Loading dependencies that aren’t modules
Probably you will need to load third-party libraries that aren’t defined as Melchior modules, like for example jQuery or underscore.js. For this case Melchior provides a "shim" system for us which is very similar to the one that Require.js has.
melchiorjs.config({ // paths to modules paths: { 'module1': 'js/module1', 'module2': 'js/module2', // the same name as global that lib exposes // saves from optional `shim` property on config 'jQuery': 'js/vendor/jquery', // it will need shim because library exposes `_` globally 'underscore': 'js/vendor/underscore' }, // provide shim to non-melchior modules shim: { 'underscore': { exports: '_' } } }) // your entry point .module('core') .require('module1') .require('module2', 'two') .require('jQuery', '$') .run(function () { console.log(module1.getName() === two.getModuleOneName()); // true console.log('jQuery version:', $.fn.jquery); // 1.9.0 });
The 'paths' declared are relative to the location of the main.js file and don’t include their .js extension optionally. Under the 'shim' key of the configuration object we use module names as keys to point to the global variable defined by the library we want to get hold of.
If the global variable defined by the library is same as the module name you would like to use then you can omit declaring a shim key for it (take a look on jQuery in config).
Melchior handles third-party libs by automatically wrapping them in melchiorjs.module() so you don’t need to add anything by yourself.
Conclusion
I hope this walkthrough will help you straighten things up on how to use MelchiorJS or just inspire you with some DIY spirit.
Melchior is a great technical adventure and the story of its creation contains several interesting pitfalls due to JavaScript nature. Hopefully it will broad the choice of in-browser module loaders. In plans we have several improvements on the existing code base and such features as compiler for concatenating files and extending the require() method. If you’re interested or have any suggestion, please let me know by writing an issue in the Github repository or pledge the project via BountySource. As always, feel free to leave a comment or question on the subject!