The Problem with Javascript
We love Javascript. As a web-development company, we can make our websites much more interesting and intuitive using it. As we developed many sites over time, however, we noticed two things about the usage of Javascript in our sites: the Javascript code was becoming large and complex as our sites grew in complexity, and we were repeating a lot of Javascript for each site.
It was obvious that we needed a way of structuring our Javascript into modules and providing some kind of centralized repository of Javascript libraries for Medium.
In order to figure out what is required for structuring Javascript, we need to precisely outline the problem we are solving. Let's call a piece of Javascript code a "library." First, libraries have the potential to conflict with each other. Second, libraries may depend on each other, and we need a way to distribute the libraries without placing the burden of dependency management on the developer.
The first problem of library conflicts has been well-thought-out in the Javascript community. The general solution is to nest each library in its own function or hash and store it globally under the library name. jQuery, for example, wraps all of its code inside of a function which executes immediately and sets a global jQuery object.
The second problem of dependencies hasn't been solved as elegantly. It's a harder problem to solve because browsers lack the native functionality to make this easy, and dependency management and library loading must be done completely in Javascript. It's not exactly easy to load other Javascript libraries from Javascript and to monitor the loading process.
This all begs for a module system. If you have ever used Python, you know how useful it is to let a module system figure out namespacing and dependency loading for you. So we began to search for an existing javascript module system. It needs to support dependency sorting and cross-domain loading, and hopefully will help with namespacing in any way possible. Here's what we found:
- modules.js: Hard-core module system which provides dependency management and even creates separate environments for each module, but it uses XHR.
- JSModule: Python-esque module system similar to modules.js, also uses XHR.
- dojo: Dojo's module system is nice and provides dependency management, but uses XHR.
- YUI Loader: A rather bulky dependency loader that still uses XHR.
So far, these all use XHR for loading modules, which means you can't pull modules from a different domain. We want to host a set of standard modules from a separate domain, however, so we can't use these module systems.
There are many other suggested module systems out there, some getting closer to what we need, and others simply suggestions for namespacing conventions.
Introducing jMod
Because we couldn't find anything which fits our needs, we wrote jMod. jMod began as an experiment with sorting javascript dependencies and loading them across domains. It evolved into a full-blown module system which supports some nifty namespacing mechanisms to make it easier to use libraries.
Example
Let's start with an example. Here is how you would write a module which depends on the jQuery and string libraries:
module('foo',
imports('lib.jquery',
'lib.string'),
function($j, $str) {
// ...
// $j is the jQuery object
// $str is the string library object
// ...
function bar() {
var text = $j('.text').text();
return $str.title(text);
}
this.bar = bar;
});
There's a lot there, so lets examine it carefully:
1. module declares a module. It takes a name, an optional import statement, and the module's code as a function.
2. imports declares dependencies.
3. The module's code is represented as a function. This function is passed all of the modules it depends on in the same order as the dependencies were declared. Using these objects is the only way to use other modules.
4. We exported bar by setting it on this. this represents the object that is your module.
Module Names
Names of modules follow Python in that dots represent levels in the filesystem, so lib.jquery would be found at <MODULES_ROOT>/lib/jquery.js. Note that when declaring a module, the name must match where it lies on the filesystem. For example, the above module must be located at <MODULES_ROOT>/foo.js.
The default modules root location is /js. We will explain how to change this later.
Anonymous Modules
Every module resides in its own javascript file. jMod also provides thunk for executing inline javascript on a page. thunk is the same as module except that it does not take a module name; it defines an anonymous module solely used for executing javascript inside a script tag on a page.
thunk(imports('lib.jquery'),
function($j) {
// ... code ...
});
Installing and Using jMod
Now, how do we simply load a module on a page? First, lets look at how to include jMod on a page. Once you have jmod.js, you simply include it and configure it:
<script type="text/javascript" src="/js/jmod.js"></script>
<script type="text/javascript">
jMod.set_global_path('/path/to/js');
</script>
The most common configuration will be changing the root url for your modules. The default root url is /js.
To load modules you call jMod.load which takes any number of module names:
<script type="text/javascript">
jMod.load("foo", "bar");
</script>
The previous command will find the modules foo and bar at <MODULES_ROOT>/foo.js and <MODULES_ROOT>/bar.js, and will load them and all of their dependencies in the correct order. A single module is guaranteed to only be loaded once.
Conclusion
Those are the basic mechanisms of jMod. This system enables an extremely modular way of programming Javascript without worrying about making sure all the libraries are included and in the right order. It's also nice to use Javascript this way because jMod can take care of loading Javascript in the most efficient way possible. There are certainly ways of optimizing when Javascript is loaded and executed, and now we can play around with it in jMod and all of our sites benefit from the results.
jMod also supports customizing namespaces so you can pull modules from several different sources, which is how we implemented our centralized set of libraries. We will make another blog post about our javascript distribution mechanism which will explain some of the more advanced examples of jMod. For now, feel free to play around with site-local modules in jMod.
jMod is hosted on GitHub along with our set of javascript libraries. Ignore most of the files in this repository because it contains everything required for hosting our javascript modules from a central domain. jMod can be found in the lib directory, or you can download it from the direct link at the top of this article.
