When creating more complex web applications, you end up with more and more Javascript, which can quickly become difficult to maintain. RequireJS is one approach to helping solve this (and other issues).
Managing many Javascript files within a project can be come an issue – especially if you want to mix-and-match components (or at least be able to reuse components elsewhere) – in that it’s hard to specify dependencies between ‘classic’ Javascript files, and it’s also often hard to reuse components as there may be naming conflicts or version issues.
require.js is one approach to solving this – it helps by :
- supporting a AMD module format for loading module(s) and their dependencies
- loads nested dependencies (i.e. the scripts themselves state their dependencies, not the HTML page)
- optimisation for deployment and asynchronous loading
Legacy Example
A basic example is shown below – whereby we start with a “legacy” Javascript routine (js/legacy.js) and show it changing to be loaded via RequireJS. Obviously for the purposes of this article legacy.js is quite minimal.
file: js/legacy.js :
$(document).ready(function() { var field = $('#inputfield'); field.change(function() { $('#message').text('You typed in : ' + field.val()); }); });
and example html file :
<!doctype html> <html> <head> <title>Legacy test</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.js"></script> <script src="js/legacy.js"></script> </head> <body> <h1>legacy demo</h1> <p id="message">No message</p> <input type="text" id="inputfield" value=""/> </body> </html>
The two obvious issues are :
- legacy.js has a dependency on jQuery, but no way of specifying this itself (aside from it breaking if you remove jQuery)
- the browser/page rendering will block until js/legacy.js has loaded – while this is “fixable” by moving the script tag for js/legacy.js to the bottom of the page, it still won’t be loaded asynchronously.
As this “application” grows, it’s likely that more js/xxxx.js files will be created – each of which may have their own external dependencies which will need specifying in each individual html file they’re used in.
RequireJs helps us solve the above problems, as this example shows….
First steps – Introducing requireJS
Firstly we’ll start by just using require.js for dependency management –
file: js/hellorequire-shim.js :
require.config({ // base url where modules are found baseUrl: 'js', paths: { jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery' }, shim : { 'legacy' : { deps: ['jquery'] } } }); require(['legacy']);
The ‘final’ line (require([‘legacy’]);) will cause RequireJS to load js/legacy.js code and run it.
Our HTML page will now change to look like :
<!doctype html> <html> <head> <title>Hello RequireJS</title> <script data-main="js/hellorequire-shim" src="components/require.js"></script> </head> <body> <h1>Require.js demo </h1> <p id="message">No message</p> <input type="text" id="inputfield" value=""/> </body> </html>
Download/install require.js to the components directory.
Note the ‘data-main‘ attribute which is used to tell require.js which component to run.
When RequireJS/require.js loads, it will add an asynchronous script tag to the body to load whatever is specified by the data-main attribute. It handily sticks a ‘.js’ on the end, so we have a little less typing – hence js/hellorequire-shim.js is loaded.
So – we’ve not (yet) made any changes to js/legacy.js – but we’ve got the beginnings of dependency management.
So – we’ve configured RequireJS for –
- Finding Javascript in the ‘js’ directory
- To load jquery from the GoogleAPIs URL
- To record the dependency between legacy.js and jquery.
Configuration Duplication
As our “project” grows, we’ll want to stop duplicating the configuration section – there are a few ways of doing this (see here for more details) – we currently like wrapping the require routines like so –
require(['config'], function() { require(['jquery', 'other', 'things'], function($) { console.log("Require.js demo loading"); // Run/use code here. }); });
where there is a js/config.js file containing :
// http://requirejs.org/docs/api.html#config require.config({ // base url where modules are found baseUrl: 'js', paths: { jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery' } // etc. });
Creating an AMD module.
Next we probably want to refactor our js/hellorequire.js a bit – so there’s a reusable AMD style module – so let’s define a module (we can also lose our dependency on jQuery at this level).
module/track.js :
define([], function() { return { trackField : function(from, to) { console.log('modularized demo'); from.change(function() { to.text('You typed in ' + from.val()); }); } }; });
Now, we can load our new module. If we want we can extend the module to add (or overwrite) functionality.
A “hellorequire-module.js” could now look like :
require(['config'], function() { require(['jquery', 'module/track'], function($, track) { console.log("Require.js demo loading"); track.trackField($('#inputfield'), $('#message')); }); });
with the HTML script tag looking like :
<script data-main="js/hellorequire-module" src="components/require.js"></script>
We could, if necessary, swap module/track to be a different component (as long as it exposes the same API as module/track.js).
The good thing about having moved to a module based approach is that what was once in legacy.js is now no longer polluting the global namespace – so we could load multiple variants of the ‘track’ module, each in isolation.
Further reading : RequireJS API