Modular Code Dependencies
RequireJS and AMD (Asynchronous Module Definition) provide a pattern for authoring modular code and also managing dependencies without complex namespacing or even adding any properties to the (head) window object. When we write modules in JavaScript, we want to handle the reuse of code intelligently and have the option to build an entire application into a single script or load modules as they are required to execute the business requirements of the application.
Writing AMD modules with RequireJS
Note - This section “Writing AMD modules with RequireJS” is an excerpt from Backbone Fundamentals
The overall goal for the AMD format is to provide a solution for modular JavaScript that developers can use today. The two key concepts you need to be aware of when using it with a script-loader are a define()
method for facilitating module definition and a require()
method for handling dependency loading. define()
is used to define named or unnamed modules based on the proposal using the following signature:
1 2 3 4 5 |
|
As you can tell by the inline comments, the module_id
is an optional argument which is typically only required when non-AMD concatenation tools are being used (there may be some other edge cases where it’s useful too). When this argument is left out, we call the module ‘anonymous’. When working with anonymous modules, the idea of a module’s identity is DRY, making it trivial to avoid duplication of filenames and code.
Back to the define signature, the dependencies argument represents an array of dependencies which are required by the module you are defining and the third argument (‘definition function’) is a function that’s executed to instantiate your module. A barebone module (compatible with RequireJS) could be defined using define()
as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
The main.js file bootstraps the Web application
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
|
Example of Backbone.js Model definition using AMD
Below is an example of defining a Backbone Model object which returns a constructor rather than an instance. The recommended convention for naming variables is to use Upper CamelCase for constructors and lower camelCase for others.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
|
Example of Backbone.js Collection definition using AMD
The events collection defined below returns an instance of the Backbone Collection object. In this collection constructor “EventsCollection” the event model is assigned to the model property of the object literal used to extend the base collection object (pseudo class, as in JavaScript everything is an object). Notice that one of the required scripts is “models” which contains the “Event” constructor in an object which is referenced as the variable “models.Event”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
In the code above there is a single var statement which lists the local variables in the current scope. This is a best practice and encouraged so the developers have a common place to find the local variables for this module. Also it provide some syntax to how the code dependencies are used at the top of the module. For example the eventsModel is a new instance of the required EventsData constructor and Event is the constructor included in the required models object.
The previous code examples that the objects used to extend when defining the constructors are : BaseModel and BaseCollection not Backbone.Model and Backbone.Collection. We are using base classes so that they may be extended similar to the use of an abstract class.
Grouping objects simplifies dependency management and also facilitates an organized build
The strategy we are using for authoring modular and reusable code includes the use of site-wide or global objects as well as code organized in packages intended for specific components of the application. To provide global objects as dependencies which will be required by various packages we group (or wrap) common types of objects into a single object which can be optimized in the build process with r.js. Grouping objects also helps to keep your list (array) of dependencies in the first argument of the (RequireJS) global define function call much cleaner and concise.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
Wrapper: Utils Library
Another example of grouping objects for a base library used to extend our base objects e.g. BaseModel…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
Events Collection
The events collection object can be extended to further segment the collection for specific lists presented to site visitors. In the example below the required script ‘collections/events’ returns an instance, however the instance’s constructor is easily referenced like so eventsCollection.constructor and extended to create a new constructor which will have methods to filter the collection by segment.
Events Segments Collection
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
|
The truncated module above returns a multi-dimensional “array[schedule][type]” using the extended constructor. This module is useful throughout the application where ever segments of the events are listed and presented in various formats whether in a menu, list or as graphic slides the data is common and already segmented as required for the business of the application.
Defining a module with non-JavaScript dependencies like a Mustache html template
The view module below needs an HTML template file written for use with the template library Mustache. The required template 'text!packages/header/templates/nav.html'
has the prefix text!
which is a plugin to the RequireJS library.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Summary
Functions require() and define() on the head object (window) are created by the RequireJS library and are strategic in authoring modular and reusable code. The function require() is used in the main.js to kick off the application; main.js is referenced in our html file in a script object or could be using in an inline script as well. The call to require() has as the first argument an array of the required scripts which are referenced in the second argument’s anonymous function definition which is used as a callback. When the requirements are ready they are passed to the function which lists the arguments by name giving reference to the returned objects of each required script. The define() function works the same way as require() using an array as the list of dependencies; however define() is called to define a module of code. the RequireJS library also has plugins to load non-JavaScript dependencies, e.g. Mustache template files, to reference the plugin use the prefix text!. All scripts in the Web application framework should use these two methods which provide the closures and scope for authoring modular code in JavaScript; it is not necessary to add any properties to the head (window) object.