JavaScript modularity with RequireJS (from spaghetti code to ravioli code)
http://netmvc.blogspot.com/2012/11/javascript-modularity-with-requirejs.html
Today I would like to describe how you can make your JavaScript code much much much better.
We know a lot about how to make our c# code much better. And we always use it.
We split out our c# code to classes, put the classes to modules, put the modules to layers, etc.
But we never do the same for our JavaScript code. And this is a big mistake.
And that's why we have a lot of Spaghetti Code inside our apps.
The main problems with function spaghetti code are:
- It's really hard to figure out where the one block of code begins and where the other ends
- Who's responsible for what?
- How are we deal with global scope pollution (e.g. use the same variables in different pieces of code)
- Low code re-use
- Not easy to maintain, test and debug.
I'm not going to describe in details what the functional spaghetti code
is, because you can find a lot of references in the Internet.
I would like to show here how to avoid it and make your code better using RequireJS.
RequireJS
The following command in the Package Manager console will install RequireJS package into your ASP.NET application:
PM > Install-Package RequireJS
RequireJS is a JavaScript file
and module loader. It is optimized for in-browser use, but it can be
used in other JavaScript environments, like Rhino and Node. Using a
modular script loader like RequireJS will improve the speed and quality
of your code.
In other words RequireJS really helps:
- To define our modules
- To resolve module dependencies
- To load scripts in the proper order (and asynchronously)
So, RequireJS really helps to define a structure to the modules in a JavaScript applications.
RequireJS modules
First I would like to show how you can create modules using RequireJS.
I will use the same example as I used to show how to create a JavaScript module.
define('messenger', ['jquery'], function ($) { var text = 'I am a module', showMessage = function() { $("#messagebox").html(text); }; return { showMessage: showMessage }; } );
It looks as easy as a JavaScript module. But I would like to describe some differences.
The RequireJS module starts with:
define(
'messenger'
,
Where 'messenger' is the module ID. You can use this ID if you want to reference this module in other modules.
The next line describes dependencies of this module:
[
'jquery'
],
And then you have to specify module body as a function.
As you can see it's really simple to create a module using RequireJS.
Using RequireJS
Let's change all of our modules.define('config', //module id [], //no dependency function () { var baseUrl = '/api/messenger/'; return { baseUrl: baseUrl }; } );
dataservice.js
define('dataservice', //module id ['jquery', 'config'], //depend on two other modules function ($, config) { var callApi = function (url, type, callback) { $.ajax({ url: url, type: type, dataType: 'json', success: function (data) { callback(data); } }); }, getMessage = function (id, callback) { url = config.baseUrl + id; callApi(url, 'GET', callback); }; return { getMessage: getMessage }; } );
messager.js
define('messenger', //module id ['jquery', 'dataservice'], //depend on two modules function ($, dataservice) { var showMessage = function (id) { dataservice.getMessage(id, function (message) { $("#messagebox").html(message); }); }; return { showMessage: showMessage }; } );
main.js
(function() { requirejs.config( { paths: { 'jquery': '../Scripts/jquery-1.8.2.min' } } ); require( ['messenger'], function(messenger) { var id = 55; messenger.showMessage(id); } ); })();
All of the modules look as they were before, except of main module.
In this module I have configured RequireJS to specify where RequiteJS can find the jquery module.
requirejs.config( { paths: { 'jquery': '../Scripts/jquery-1.8.2.min' } } );
After all, we have to change our HTML to load our modules.
index.html
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Phase 1</title> </head> <body> <div> <h1>Modular Demo 1</h1> </div> <div id="messagebox"></div> <script data-main="main" src="../Scripts/require.js" type="text/javascript"></script> </body> </html>
Small remarks about our HTML changes.
We should not load any of our modules or jQuery, because RequireJS will handle it for us.
We have to load RequireJS only, and specify 'data-main' attribute,
which tells RequireJS to load main.js script after RequireJS loads. In
other words, we specify start up script in 'data-main' attribute.
And after all RequireJS does the 'magic' and loads all of our modules in proper order automatically.
As a result, our code becomes much much better now, as I promised at the beginning of this post.
That's all. And see you next time.