Repository navigation
Require
#Require
Require JS provides several benefits:
- It relieves us of needing to define SCRIPT tags in our HTML files and worrying about the order in which they are loaded. If you have a relatively small project, and you wrote or know all the modules in it, then it is fairly easy to define the order in which to list SCRIPT tags in your HTML file. In large projects, or in cases where you don't know the JavaScript files used in a project, there is often no easy way to discover what dependencies exist in your project. Does ModuleA depend on ModuleB? Or is it that ModuleB depends on ModuleA? What about modules C, D, E and F? A primary goal of require is to relieve you of all these issues.
- require allows us to decide which modules will be loaded when. In large applications, we don't want to load all our files at one time, as some might not be needed at all, and some might not be needed when the app first loads. For instance, if users can optionally load movies using a complex and heavy weight set of JavaScript files, then it would make sense to load those heavy files only when the user requested them, and not before.
- A third benefit is that require, like the modular pattern, helps you protect the global namespace. In fact, require has a version of the modular pattern written in to it. This allows you to use the modular pattern without the peculiar syntax that some find intimidating. Furthermore, there is no need to specify a global that points at objects defined in the modular pattern.
When should you use require? The main use case is for large projects that loads lots of files where either or both of the following is true:
- Files depend on one another
- Some files are not needed at load time but could be loaded later
In small projects that use only one or two JavaScript files, require is usually not needed. However, I think it is good idea to use require as often as possible, just so that you can become familiar with it's syntax.
The first step is to load require and data-main, which is generally a file called Main.js.
<script data-main="javascripts/Main" src="javascripts/require.js"></script>
The real work, discussed in the next section, occurs in Main.js.
Here is a relatively simple example set of files. The first is called Main.js:
require.config({
baseUrl: '.',
paths: {
"jquery": 'jquery-2.1.1',
"Control": 'Control'
}
});
require(['jquery', 'Control'],
function(jq, Control) {
'use strict';
$(document).ready(function() {
var control = new Control();
});
});
The require.config section defines the location of the files that require will load. The baseUrl specifies the default place that require will look for files. The path section defines the location for files or directories that are not directly under baseUrl.
The require section seen after require.config helps bootstrap your application.
For the files that are going to be loaded with requirejs, you should use a method called define that is part of require. The function takes a list of dependencies in square brackets, and then a callback the defines the methods or objects that you want to host in this module.
So Control might look like this:
define([], function(require) {
var Control = (function() {
return Control;
}());
return Control;
});
One of the most important lines is return Control. This specifies that the code in module Control can be accessed through constructor called Control. To fully understand the power of this syntax, you need to understand closures. In this case, closures ensure that any other code in the Control module that the constructor can access will be accessible from outside the module via the constructor. (I apologize if that sounds confusing, this just isn't the place to get into closures.)
Take a look at Require Boat.
This is a very simple example showing how to get up to speed with require.
In index.html we link in Main and require.js:
<script data-main="Source/Main" src="Source/require.js"> </script>
Main then configures jquery:
require.config({
paths: {
"jquery": "http://code.jquery.com/jquery-1.11.0.min",
}
});
Then we create a simple module called Boat which is found in Boat.js:
define(['jquery'], function() { 'use strict';
function describe() {
$("#list").append("<li>I'm a boat.</li>");
};
return {describe: describe};
});
This code says that our Boat module requires jquery. It then defines a simple function which adds an item to a list. Finally we return a link to the function:
return {describe: describe};
Back in Main, we can now link in Boat and call Boat.describe:
require(["Boat"], function(boat) {
boat.describe();
});
The first Boat, the one in the array, links in the Boat.js module. For this we get a reference to a boat which is passed as a parameter to our anonymous function. Finally, we use the reference to call the describe method:
boat.describe();
##Configure Require Paths
Consider this code:
require.config({
paths : {
"jquery" : "jquery-1.11.1.min",
"Markdown" : "Markdown/Converter",
"Editor" : "Markdown/Editor"
}
});
require(['jquery', "MarkShow"],function(jq, MarkShow){
var markShow= new MarkShow();
});
Compare it to this:
require.config({
paths : {
"jquery" :"jquery-1.11.1.min",
"MarkShow" : "MarkShow",
"Markdown" : "Markdown/Converter",
"Editor" : "Markdown/Editor"
}
});
require(['jquery', "MarkShow"], function(jq, MarkShow){
var markShow = new MarkShow();
});
The first puts MarkShow only in the call to require, while the second puts it in both the require.config statement and the call to require. What is the difference?
I don't believe there is any difference at all. We use the paths property in require.config to help us work with complex paths or complex names. It's easier to write jquery than jquery-1-11.1.min so we configure require so that it understands that when we say jquery, we really mean jquery-1-11-1.min. The main purpose of paths, of course, is just to let us define the paths to modules that are not in the current directory:
"Markdown" : "Markdown/Converter",
In our example, MarkShow is in the current directory, so there is no reason to configure it. We can already reference it just by writing require["MarkShow"], function(MarkShow). As a result, there is no point in putting it in the paths property of require.config.
It is worth noting that you can do other things with require.config besides just setting up a path. In particular, you can use the shim section to help load files that are not set up to use requirejs.
##More
In answer to the first question, it is okay to create only one factory if you wish. The goal is to write code that you think is cleanest and easiest to use. These are judgement calls, so try using one factory, and let's see what happens. I'm honestly not sure which is best. I see arguments on both sides.
I don't know a good shortcut to defining the paths to your files when they appear in multiple directories. There might be one, but I don't know it. For now I think it is best to define the paths to all your files in main.js, and not in other files. That way you will not have to search among your files looking for the place where you defined the path to be used when it comes time to load them.
Having said this, we might in the future want to place a file in each directory that is responsible for loading the files in that directory. That way we create a "package" as it were. Here is a directory called Readers, and in it is a file like Main.js, that knows how to load all the files in that directory. That way we can load Readers/Main.js and it will load the files in that "package" or "name space." But frankly, I'm a little fuzzy on how all that might work. I'll figure it out, just not today.
I would ask that for the midterm, you define the paths to all your files in require.config for Main (public/javascripts), MainTest (Tests), and TestMain (the karma require file). That way I will know where to look if there is trouble loading files for your project. I feel like we have to move beyond this state of affairs, and soon, but for now, I think this is best.
If anyone is reading this, and is confused. I'll give an example. In public/javascripts/Main.js, there is a section like this:
require.config({
paths : {
"jquery" : "jquery-2.1.1",
"DefaultReader" : "./Readers/DefaultReader",
"JsonReader" : "./Readers/JsonReader",
"MarkdownReader" : "./Readers/MarkdownReader"
}
});
I want you to define the paths to all the files in your project in the paths object shown above. The only exception would be for files that are already in public/javascript. They don't need to be called out explicitly since they can just be referenced by name.
One you have defined the path for a file, then anywhere in your project that you need the file, you can just point to it by the name you defined in the paths object shown above. You point to it like this:
require(["jquery", "DefaultReader", "JsonReader", "MarkdownReader"],
function(jq, DefaultReader, JsonReader, MarkdownReader) {'use strict';
Note that we don't have to specify the path to DefaultReader. Just say its name, and behind the scenes require uses the path from the require.config in Main.js.
In the require array above, use the same case as you used when defining the variable in the path object. That should usually be the same case as you used for the file name. For instance, BridgeReader.js would be 'BridgeReader' and test.js would be 'test'. In the function callback that follows, capitalize objects that need to be called with new, and use camel case for those that do not. For instance, consider this:
require(["jquery", "Foo", "Gorp", "Gar"],
function(jq, foo, gorp, Gar) {'use strict';
The assumption here is that you do not need to call new on foo or gorp but you do on Gar. (These are just used for illustrations, you probably want to use the modular pattern for most of the objects in your project, so you probably usually want to capitalize the names in the parameter list to the function callback in a require statement. At this stage, you don't have to use the modular pattern, however.)