Me and my colleague Eero Anttila are working in a project where we are using Eero’s Continuous Calendar plugin for jQuery in the frontend. The plugin utilizes a set of date handling functions for formatting, parsing, and so on. The functions are grouped into objects (
Locale) which are injected into the global window object. A very useful aspect of the functions is that they are immutable. For example,
dateTimeObj.firstDateOfMonth() returns a new instance of
We found out that we could benefit from these functions in the application generally, needing date handling also elsewhere than in the calendar component.
Our frontend loads with RequireJS, and we’ve been happy composing our application from small modules. Now, in order to get access to the date handling functions in our modules, we need either to ensure that Continuous Calendar gets loaded before our application’s modules, or we need to introduce optional AMD support for the date functions. Because it doesn’t make sense to load the whole Continuous Calendar just to get access to the functions, we decided add AMD support to them.
Here’s an example how
DateTime global object supports AMD loaders and traditional browser script loading:
DateTime factory executes without external dependencies. This is communicated in the code by
define call having empty array as its second argument for the AMD case, and the factory function call having no arguments in the traditional browser loading case.
However, for building
DateRange, we need jQuery,
What happens here? With AMD loader, such as RequireJS, the
if block of the bootstrap code executes. There we call
define, specifying a module named
DateRange (the first argument), needing jQuery,
DateTime as its dependencies (the array as the second argument). Eventually, after loading all the specified dependencies, the AMD loader calls the factory function (the third argument) with the dependencies as the arguments to the function.
If were are not using an AMD loader, but loading the script in the browser traditionally with
<script> tag, the
else block of the bootstrap applies. Before that, however, we have to ensure that we load modules in such an order that the dependencies of each module exist at the evaluation time of the module. That can be satisfied by careful organization of
<script> tags or bundling the modules in a single source file. In this case, jQuery,
DateTime.js must be loaded before loading
DateRange.js. When the browser evaluates
DateRange.js, it calls the factory function with dependencies fetched from the global window object.
I really like the factory function spelling out the dependencies as parameters to the function.2 We get to know the dependencies just by looking at the function signature. In addition, we have located the change made to the global window object (if any) in one predefined place (the
else block). If we’re using an AMD loader, we avoid polluting the global window object altogether!
The UMD pattern drives the module author to make at most one addition to the global window object. That’s a great guideline for organizing modules.
Of course, it is up to the module author to play by these rules. There’s nothing preventing the factory function from referring to the window object for other dependencies or polluting the global window object. But why would the author want to surprise the users of the module?