Download demo project : RequireJsDemos.zip
I don't know how many of you use lots of JavaScript libraries, but we found
ourselves using quite a lot within certain projects. One of the strange issues
that came about from linking JavaScript files was that we occasionally ran into
dependency issues, where by a particular JavaScript source file would have a
dependency on another JavaScript source file that was not loaded (and therefore
not available) yet.
As luck would have it there is an excellent library for dealing with
interlinked JavaScript dependencies and module loading which is called :
RequireJs
You may be asking what problem
RequireJs is attempting to
solve. Well lets consider a most common example, which is that of a custom
jQuery plugin, where jQuery MUST be loaded before the plugin will work. If we
stuck to using standard script tags something like this
<script src="scripts/myCooljqueryPlugins.js" type="text/javascript"></script>
<script src="scripts/jquery.js" type="text/javascript"></script>
We instantly have a problem, our custom jQuery plugins will not work, as
jQuery itself has not be loaded yet. Sure we could move the jQuery script tag to
be before the custom jQuery plugins, which would indeed fix the problem
this once. This is however a very isolated small use case, which is not really
the norm on large scale applications, where we may have many many related files
which all have dependencies. This is where
RequireJs can help. Want to
know more, read on.
So now that you know the problem what is a possible solution? Well as I have
already stated this article will use
RequireJs a one viable
solution to the problem just described.
RequireJs does a bit more
than just solve dependencies, it also provides asynchronous module and file
loaded. In my mind the fact that
RequireJs uses modules is a
very good thing, as it gives a guiding push to the developer, which should nudge
them into creating a module which has a single responsibility which is never a
bad thing. Using
RequireJs we are able to
specify things like:
- Define a module
- Require a module
- The modules dependencies (via a
RequireJs config called
shim)
- The module paths
This section will outline several different RequireJs examples, starting
with a typical simple use case, and then building on to slightly more complex
examples.
This is the simple basic 101 sample, and it illustrates the very basics of
using RequireJs. This will
be expanded upon later, but it does illustrate a few key points that we will
come across in later examples.
Here is what the structure of this project looks like in Visual Studio:
One of the things you must do with RequireJs is to tell it which file will be used for the overall
configuration. Here is a simple example
<head>
<title>My Sample Project</title>
-->
<script data-main="scripts/main" src="scripts/require.js"></script>
</head>
You can see that the data-main
attribute points to a main file
which is what sets up the RequireJs
configuration. Lets see a simple example of this shall we:
(function() {
require(["app/data", "helper/util", "helper/logger"], function (data, util, logger) {
alert('util module : ' + util.doubleItMethod(10));
alert('data module [0] : ' + data.storedData[0]);
alert('data module [1] : ' + data.storedData[1]);
logger.logThePair();
});
})();
What we can see there is the use of the require()
function, which is what
RequireJs
gives us. The 1st parameter (which may be an array as shown) specifies the
dependencies that this function has. Lets now focus our attention on one of
those modules that we have a dependency on.
This following snippet is the logger
module, and note above how
we state that we require()
the logger module, which RequireJs
gives us, and then are free to use the modules code. Which again can be seen
above where we use the logger modules logThePair( )
function
define(function (require) {
var pair = require(".././app/keyValuePairs");
return {
logThePair: function () {
alert("color: " + pair.color + ", size: " + pair.size);
}
};
});
In this example we define a module using define()
and we also take in RequireJs which gets special treatment,
where RequireJs
knows what to do with it. We are now able to use this defined module using another
require()
call somewhere else, which we saw above when we wrote out
require(["app/data", "helper/util", "helper/logger"], function (data, util, logger) {
....
}
One thing of note there is the path is also included "helper/logger" so require will
look for a file that matches that path and use that. We
can also specify paths like this
define(function (require) {
var pair = require(".././app/keyValuePairs");
return {
logThePair: function () {
alert("color: " + pair.color + ", size: " + pair.size);
}
};
});
We will see more
on path configuration later
We can use both require()
and define()
to load
module dependencies. The require()
function is used to run
immediately, where as define()
is used to define modules which may be used
from
multiple locations.
So now that we have seen a trivial example of using RequireJs
lets continue our journey. Lets see what it takes to create an example where we
have actually library depenencies, such as a jQuery plugin that needs jQuery in
order for function.
Here is what the structure of this project looks like in Visual Studio:
As before we start (as we always will) by telling RequireJs
the main javascript code file to boostrap the app
<!DOCTYPE html>
<html>
<head>
<title>jQuery+RequireJS Sample Page</title>
<script data-main="scripts/app" src="scripts/lib/require.js"></script>
</head>
<body>
<h1>jQuery+RequireJS Sample Page</h1>
<input type="text" maxlength="150" id="txt" />
<input type="button" id="btn" value="click me" />
</body>
</html>
Where the scripts/app javascript file looks like this
requirejs.config({
"baseUrl": "scripts/lib",
"paths": {
"app": "../app"
},
"shim": {
"jquery.appender": ["jquery"],
"jquery.textReplacer": ["jquery"],
}
});
requirejs(["app/main"]);
This time we can see something new. We can see that we are actually configuring RequireJs
for certain things, such as:
- BaseUrl : Specifies the base path for all the scripts, you can still use RequireJs
with relative paths, but this is its base path
- Paths : Is a map of named paths where we specify a well known name and a
path
- Shim : Is a map of files, and their dependecies. What this does is that
is gives a hint to RequireJs
about the required module dependencies such that RequireJs
will then know to load them in the correct order. Remember that RequireJs
uses a technique which means modules are loaded asychronously. It can be
seen from the example above that "jquery.appender" and "jquery.textReplacer"
are dependent on jQuery. As such RequireJs
will load that first.It may seem that we are just swapping the evils of
having the correct javascript imports in the html in the correct order for
this, but what we do get from RequireJs
is that modules are loaded asychronously
The last point is that we then kick of the whole app by telling RequireJs
wihch is the initial app file that should be run. This is done using the line
requirejs(["app/main"]);
So lets examine that file for a minute
require(["jquery", "jquery.appender", "jquery.textReplacer"], function ($) {
$(function () {
$(document).ready(function () {
$('#btn').click(function () {
var old = $('#txt').val()
var rev = reverseString(old);
$('h1').replaceTextWithFade('Old text was : ' + old);
$('body').appendData(rev);
});
});
});
function reverseString(str) {
if(typeof str !== 'string') {
throw new Error('reverseString(str) accepts only strings.');
return;
}
var strArr = str.split('');
var reversed = strArr.reverse();
return reversed.join('');
};
});
It can be seen that is uses jQuery and the 2 custom jQuery plugins, that have
by magic had their dependency on jQuery satisfied by RequireJs,
so we can now safely use them within the javascript that asked for them as
requirements.
Just for completeness here is one of the custom jQuery plugins that the code above makes
use of
(function ($) {
$.fn.appendData = function (data) {
this.each(function () {
return this.append('<p>' + data + '</p>');
});
}
}(jQuery));
RequireJs magically makes sure that
this is not loaded until jQUery itself is loaded, at which poit it will
be provided to these plugins
I also wanted to demonstrate how to use RequireJs
within a ASP .NET MVC application (I chose MVC 4). and here is how I did it.
Here is what the structure of this project looks like in Visual Studio:
I used the same boostrapper "app.js" code that we saw above, and the same 2
custom jQuery plugins, that we just saw.
So now that you know are using some of the same stuff we just saw, lets
proceed to the ASP .NET MVC 4 specific bits shall we.
It starts with the Bundle.Config file which for me is as follows;
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/lib").Include(
"~/Scripts/Lib/require.js"));
bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css"));
}
}
And then we have a master layout page which makes use of the bundles like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<meta name="viewport" content="width=device-width" />
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/lib")
<script type="text/javascript" src="~/Scripts/app.js"></script>
@RenderSection("scripts", required: false)
</head>
<body>
.....
.....
.....
</body>
</html>
However this time since we are relying on ASP .NET MVC 4s bundling capabilities we need
to provide a separate link to the boostrapper for
RequireJs, which is done as
follows:
<script type="text/javascript" src="~/Scripts/app.js"></script>
Where app.js
is the same as the previous example.
From there all that is left to do is to declare what scripts you want per page (which is
typical for any ASP .NET MVC app)
@{
ViewBag.Title = "Home Page";
}
....
....
....
....
@section Scripts {
<script src="~/Scripts/Controller/Home/Index.js"></script>
}
....
....
....
....
Which we can then create a standard javascipt file such as the following
require(["jquery", "common/Utils", "jquery.appender", "jquery.textReplacer"], function ($,
common) {
$(document).ready(function () {
$('#mainDiv').append('<p>Index page is working with Require.js</p>');
alert('Type some text into the text box and then click the button');
$('#btn').click(function () {
var old = $('#txt').val()
var rev = common.reverseString(old);
$('#appendToDiv').appendData(rev);
});
});
});
Which is specific for the controller/page combo and will be injected into the "Scripts"
section
of the layout page.
Typescript is the new kid on the block and
is Microsofts attempt at proding
OO like features to JavaScript. Typescript
essentially has its own compiler
which take a Typescript file (*.ts) and
compiles that to a JavaScript file
(*.js). I thought for completeness I should also cover
Typescript, though this
only covers how to use
RequireJs with
Typescript.
Here is what the structure of this project looks like in Visual Studio:
Before we start I should mention that when working with
Typescript you
MOST definately want to install the
Web Essential Visual Studio
Extension which makes working with Typescript so much better. It provides a
Typescript item template for Visual Studio.
When the compilation process is
kicked off it will also create the JavaScript for the
Typescript file as well.
One of the nice things about Typescript is that is will moan at save time
(with the
Web Essential Visual Studio
Extensions installed) if you do not have the appropriate definition files
loaded. Here is an example of that :
Click for larger image
Luckily help is at hand by way of the awesome set of definition files that are
available for all the major JavaScript libraries out there.
You just need to head over to the
DefinitelyTyped web
site which has definition files for Typescript
of all the major JavaScript libraries out there. Here is an example of what it
looks like with the correct definition files loaded, see how the compile errors
have gone. Oh all I did was include the correct definition files from the
DefinitelyTyped web
site and included them in the the project and dragged them onto the Typescript
file.
Click for larger image
There is one last thing that I wanted to mention when using Typescript
and
RequireJs together, which is
a command line argument that MUST be provided to work with
RequireJs correctly.
This is the module type which can either be "commonJS" or "amd". "commonJS"
would be used if you were doing node development, whilst "amd" would be used
when you are doing things like
RequireJs. By the way amd
stands for "Asynchronous Module Definition".
There is a really nice article available here which has some more information
avaiable on the differences between "commonJS" and "amd" modular javascript:
http://addyosmani.com/writing-modular-js/
Luckily installing the
eb Essential Visual Studio Extension makes things a lot easier for us, as
all we need to do is go set an Option in Visual Studio and the correct command
line will be emitted for us.
Anyway enough of all the background stuff, let's see some code shall we.
This is the raw html demo page
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>TypeScript HTML App</title>
<link rel="stylesheet" href="app.css" type="text/css" />
<script data-main="scripts/app" type="text/javascript"
src="scripts/lib/require.js"></script>
</head>
<body>
<h1 id="header">TypeScript HTML App</h1>
<div id="content" />
</body>
</html>
Where is can be seen that we use the same stuff as before where we link through to a
scripts/app bootstrapper file. This bootstrapping file is shown below. It is largely the
same as
previous examples but we are of course able to make use of some of Typescripts features such as lambdas ( () =>
).
You can click the image below to see a larger version of what JavaScript gets
created. Like I say it is largely the same as the previous examples:
Click for larger image
So now that we have a bootstrapper in place, lets look at the main app.js
file.
I just wanted to show you the difference between what the Typescript
compiler would do depending on the command line option for picking your module
type that we discussed earlier, before we talk about the nitty gritty of using
RequireJs (although if you
just read the code below, you can see it clearly):
This is what would be compiled (the JavaSript is on the right hand side) with the AMD
command line arg turned on.
Click for larger image
This is what would be compiled (the JavaSript is on the right hand side) with the AMD
command line arg turned off.
Click for larger image
See how its quite different. In fact using the "amd" command line argument
causes the Typescript
compiler to actually use
RequireJs syntax.
Anyway to use
RequireJs inside of a Typescript
file is pretty easy, all you have to do, is make sure the Typescript
file knows about the correct
RequireJs definition file
(which we talked about earlier) and then start using
RequireJs.
Here is small example:
require(['jquery'], ($) => {
var el = $('#content');
var greeter = new gt.Greeter(el);
greeter.start();
});
I am using a vriation of the Greeter
example as shown on the Typescript
web site, where we simple update a DOM element. There is one thing to be aware
of since I also wanted to use jQuery to pick out the HtmlElement
to
pass to the Greeter
you are then forced into using the Typescript
type any
, as it does not recognize a jquery wrapped
HtmlElement
as a HtmlElement
type.
Anyway here is the code for the Greeter:
Click for larger image
Anyway that is all for now, I hope you enjoyed it, and maybe even learnt
something along the way.