What do you want to learn?
Leverged
jhuang@tampa.cgsinc.com
Skip to main content
Pluralsight uses cookies.Learn more about your privacy
RequireJS: JavaScript Dependency Injection and Module Loading
by Jeff Valore
This course introduces RequireJS, a module loader and dependency injection framework for JavaScript.
Start CourseBookmarkAdd to Channel
Table of contents
Description
Transcript
Exercise files
Discussion
Learning Check
Recommended
Getting Started
Getting Started With RequireJS
Hello. My name is Jeff Valore, and welcome to the course, RequireJS: JavaScript Dependency Injection and Module Loading. In this course, we'll be looking at RequireJS and how you can use it in your project as a dependency injection framework and to load files remotely from the server. The course assumes you have a basic working understanding of JavaScript but don't know much about RequireJS. In the later modules, we'll also look at some of the common configuration options used in RequireJS and also how to get into unit testing your modules. The RequireJS website simply defines RequireJS as a JavaScript file and module loader. It also states that using a modular script loader like RequireJS will improve the speed and quality of your code. When I use RequireJS, I'm often thinking about code quality--specifically adhering to the five principles of SOLID. with RequireJS, I'm specifically looking for the D in SOLID, which is the dependency inversion principle. You'll often hear this referred to as simply dependency injection or inversion of control. Either way, the goal here is to break your scripts and different pieces of your code into separate modules. Often in RequireJS, a module will be a separate JavaScript file, as we'll see later on in this course. And breaking things into separate modules allows us to adhere to the S in the SOLID principles, which is the single responsibility principle. This is simply putting all the code that deals with a specific responsibility into a single module with the overall goal, of course, being to create more maintainable and easier to read JavaScript. If you're new to RequireJS, you might be wondering if this is a library you can really depend on and what kind of support there is for it currently. RequireJS has actually been around for quite a while. If you look at its GitHub repo located at hithub.com/jrburke/requirejs, it's actually been in GitHub since 2009 and has fairly consistent commits up until current. It also has browser support going all the way back to IE6. So RequireJS has been around quite a while, and you can certainly depend on it for all of your production projects.
Introduction to the Sample Project
During this course, we're going to be using RequireJS to build a task list sample project, which you can see here. In this project, you can create new tasks, give them a name, mark them as complete, add several other tasks. There's a Save button at the bottom that will save our work, which will add messages to a log at the bottom. You can delete tasks and clear all of your task list. And each time you save or delete something, it adds a message to the log at the bottom. Now, let's get started creating our sample project. Since the goal of this course is really to talk about RequireJS and not go through all the steps of making a web application, I've gone ahead and created the HTML, CSS, and basic JavaScript for us. These files can be found in the exercise files for this course from Pluralsight.com. Here you can see the basic HTML layout for the page. And over in the tasks.js file, this is all the JavaScript that we have for creating and managing our tasks. You can see here that it's just one big JavaScript file, which isn't really good code. Things aren't really broken out into separate responsibilities, and it's going to be difficult to read and maintain as this application gets bigger and bigger. The rest of this course will go through refactoring this code and turning it into something a little bit nicer using RequireJS. Let's take a little more in depth look at our sample code so you can be familiar with what we're going to be working with throughout the rest of the course. First off in the HTML, you can see that we are using jQuery. We're going to be doing this for a lot of the DOM manipulation and selecting particular elements and click event handlers for our buttons. We're also currently only including one piece of JavaScript, which is our tasks.js file. In the tasks.js file, we have some comments in our code to break apart different sections. This is almost always a good sign that refactoring is needed, but we'll get into that more as we move through the course. Our first few functions all deal with manipulating the DOM elements and adding and removing tasks from the list. As we scroll down, we have more functions dealing with saving and loading data. In our application, we're going to be saving and loading data to the browser local storage, so there's no backing database for this application. And as we move down further, there're more functions for actually managing the tasks. These are really going to be our button click handlers for adding, removing, saving, and reverting our changes. And, finally, at the bottom, we have an area where all of our event handlers are registered and a simple function that runs when the page is finished loading to simply initialize our application. The one thing that's missing here is all the code dealing with adding our log messages. At the end of this course, it's going to be an exercise for you, the listener, to go back and add that logging. That way, you can put your newfound RequireJS skills to the test. Early on in the course, we're going to simply be using a file:// URL pointing to index.html to view our sample application.
Downloading and Including RequireJS
The first step to converting our sample project to use RequireJS is to download and include RequireJS itself. You can download RequireJS from the website, which is RequireJS.org. On the left side, there's a download link. Then you can choose require.js and download the minified version. Here, we'll just save it into our project directory along with our other JavaScript files. Now that we've downloaded RequireJS, we have to include it into our HTML page. To do that, we're going to no longer load tasks.js here. Instead, we'll load require. We're also going to add a data-main attribute and set that to our tasks.js file. The data-main attribute tells RequireJS which file it should go ahead and load immediately after it's done initializing itself. So, you can think of it as essentially the entry point into the application or the first JavaScript file of yours that's going to get run. Now, by convention, this is usually called main.js. So, instead of tasks.js, let's call it main, and we'll go ahead and rename our existing tasks.js file to main. Once we've included RequireJS in our HTML page and given it our data-main JavaScript file, we can go back to the browser and reload this and see that we still have a fully functional Web page. We can still add our tasks, delete our tasks, and we can tell in the Network tab in the browser tools that RequireJS was loaded. Then immediately after that, main.js was loaded. And we can tell that main.js was loaded by RequireJS because in the Initiator column, we can see that RequireJS is listed as the JavaScript that requested that this file be loaded.
Summary
In this module, we talked a little bit about what RequireJS is, gave some reasons why we should use it, mostly revolving around cleaning up our code, making it easier to understand, more maintainable, breaking pieces into their own JavaScript files, and loading those files dynamically at runtime. We then downloaded RequireJS and included it into our sample project's index.html page. In the next module, we'll talk about defining our own modules in JavaScript and setting up all the dependencies.
Defining and Requiring Modules
Introduction
In this module, we will discuss defining and requiring modules in our JavaScript. Modules are the building blocks of RequireJS, and modules can depend on other modules, which makes up our dependency hierarchy. We will also learn about the require and define functions that are provided by RequireJS. We'll then introduce the Asynchronous Module Definition, also known as simply AMD, CommonJS, and Some common module design patterns used in JavaScript that fit nicely with our modules. We'll do this by continuing to refactor our sample task list web application. If you'd like to follow along, the source code for the sample application that I'm working with in this module is available in the course materials on Pluralsight.com. So, let's get started.
Using External Libraries as Dependencies
When we left our sample project, we were including RequireJS in the script tag and had a main.js file that included our big monolithic code for the to-do list. You'll remember that we were still loading jQuery using a script tag in the index.html file. jQuery is one of many libraries that is RequireJS aware, meaning that you can import it normally with a script tag, but you can also import it using RequireJS, and jQuery will recognize that RequireJS is loaded and register itself as a module that other modules can then be dependent upon. To load jQuery using require, we can remove the script tag from the HTML. Then we will need to tell RequireJS that we depend on jQuery. That way, it can go and load it for us. To get this magic to work, we need to add a call to require.config at the top of our main.js file. We're going to discuss configuration of RequireJS in depth in a later module. But for now, just know that jQuery registers itself as a module named jquery, all lower case. Here, we are telling RequireJS what file to load when we ask for the jQuery module. By default, RequireJS will add a .js extension, so we set the path to jquery-2.1.1.min since that is the name of the file without the extension. Again, you can just accept this as is for now. We'll come back to it in a later module and discuss it in much more detail in the module on configuring RequireJS. To specify what modules we're dependent on, RequireJS adds a function named require to the global scope. The require function is used to tell RequireJS that we need a set of dependencies before we can proceed. The first parameter to the function is an array of dependencies that we need loaded. These are specified as strings. Each string is the name of the module that we're dependent upon. The second parameter is a callback function. Since the module loading in RequireJS is asynchronous, we give RequireJS this function, and it will execute it once it is finished loading all of our dependencies. The callback function needs to have the same number of parameters as we have dependencies, but they don't have to have the same name. So, here we can depend on jQuery but pass it in as a parameter that is just the familiar dollar sign. RequireJS will call this callback function once it's done downloading all of our dependent modules, so the rest of our existing code just gets wrapped into this function. Here's the syntax for the require function that we just discussed and added to our main.js file. There are other parameters that we can pass to this function, but we'll discuss those later on in the course. Back in the web browser again, if we reload our application, you can see that it still functions properly. We're still able to add and remove tasks. You can also see in the Network tab of the dev tools that RequireJS now loaded jQuery for us. So, we're now loading jQuery as a dependency and injecting it into our application. However, we still have a lot of mixed responsibilities in our own code that need to be cleaned up. So, next, we need to properly break apart our own code into modules.
Asynchronous Module Definition
Throughout this course, I keep referring to modules. Modules are the basic building blocks of RequireJS. Each module can be dependent on other modules. This concept of modules and even the function names and syntax that RequireJS uses are not actually specific to require. RequireJS is what is known as an AMD module loader. AMD stands for Asynchronous Module Definition. The AMD API started as part of the CommonJS API, which was also in the process of defining module pattern. However, CommonJS was largely concerned with module loading outside the browser. In fact, CommonJS modules are what NodeJS currently uses to define its modules. The CommonJS API was difficult to implement in a browser environment, and so the CommonJS Transport/C proposal was introduced as a way to solve this. Eventually, CommonJS Transport/C became the AMD module API and is now maintained separate from CommonJS. The important thing to remember here is that we are defining AMD modules. This means that the modules you define for use in RequireJS can be loaded by other module loaders that are AMD compatible. For example, Curl.js is a popular alternative to Require. If you'd like to know more about the AMD specification, please see the URLs listed on this slide.
Defining AMD Modules
Now let's start breaking up our own code into AMD modules. By the single responsibility principle, we should create divisions in our code based on common responsibilities. So, let's take a chunk of our JavaScript that has a common responsibility and turn it into an AMD module. The most obvious one is this set of functions dealing with data access. We'll start by moving this block of code up to the top here. We define a module using the function define. The define function is added to the global JavaScript code by RequireJS. The first parameter to define is the name of the module. Every AMD module has a name, which is simply a string. This name is used to identify this module to other modules when they need to include it as a dependency. Let's name this module taskData. The second parameter is a list of dependencies that this module needs to have available to it. Dependencies are listed as an array of module names as strings. The third parameter is a callback function. This function is called with all of our dependencies after they've been loaded. Right now, we're defining all of our modules in this single file still for simplicity, but later on in the course, we'll talk about loading these modules from separate files. For now, just remember that module loading is asynchronous, and that's why we're using a callback function. The callback function can just be an anonymous inline function as done here, but it should have the same number of parameters as there were dependencies. In other words, any dependencies listed in the dependency array will be passed to the function, and the parameters should line up. We will see an example of this soon, but since the module has no dependencies, we can just use a function with no parameters. Now, if you're like me, then you're a fan of the strict mode in JavaScript. If you don't know what this means, I encourage you to do a search on it and check it out. Let's just say you should probably be using it. One way to enable strict mode is to include the string use strict as the first line of the function. Module callbacks are functions, so we can simply put this as the first line in any of our modules. Then we can paste in all the code that we cut before, all the functions that deal with our task data loading, into this define function. Finally, we need to return an object out of the define function. The object that is returned from the define function is what is passed into other modules when they depend on this module. In other words, when a later module depends on the taskData module, it'll get this object that we're defining here as the return value. Here's the syntax for the define function that we just used. Note that this syntax is used when we define modules inline with other code. That's what we're doing right now with the sample project. But later on, we'll be moving these modules off to separate files, which will have a slightly different syntax. Now we have our taskData module defined. If we tried to run this code right now, we'd get a few JavaScript errors. And that's because these three functions for loading, saving, and clearing data are on longer accessible where they were being called before since they're no longer defined in global scope. We need to tell RequireJS that the rest of the code down in the require function is now dependent on the taskData module. Just like we did with jQuery, we can add taskData to both the dependency array and to the parameters into the callback function. So, what's going to happen here is that when the require function is called, and RequireJS sees that this is dependent upon the taskData module, it's going to take the object returned by the taskData module and pass it into the callback function of require. Now, anywhere that those taskData functions were called, we need to call them off of the taskData module object. So, for example here saveTaskData was being called. We now need to call taskData.saveTaskData. Now, to me the call taskData.saveTaskData seems a little repetitive. So, let's change this to just taskData.save. That means we also need to change our return object up in the module to match. And we'll go ahead and do the same for the loadTaskData and clearTaskData functions. Since we've now created our first AMD module, this is another good place to stop and make sure the application still works in the browser. So, we can still create tasks, still delete tasks, still clear our tasks, and there're no error messages in the console. So, everything's looking good.
The Module Design Pattern
This brings us to the next part of making our JavaScript cleaner and more maintainable, and that is through use of a design pattern known as the module pattern or the revealing module pattern. They're both variations on the same idea. Since JavaScript doesn't actually have a concept of public and private members, the module pattern seeks to isolate things that are to be kept private inside of a self-executing function. Let's look at an example of this. Let's start with the module pattern. I've laid out some pseudo-code for the module pattern here, but you can see that it's inside a self-executing function. At the top, we declare things that are meant to be private, and at the bottom, we return an object of all the things that should be public. The things that should be kept private and are declared within the function can no longer be accessed outside of this function. So, for example, if we are making a logger module, we would define it like this. It could have a messages array that's kept private, and we could return two functions to add and clear things from that list. Let's compare that to the revealing module pattern with the pseudo-code here. The big difference is that all of the members are declared at the top, and then the ones that should be public are defined in the return object, but they're just referenced instead of being declared there. So, again, if we're using our logger as an example, here's how that module would be defined. A common criticism of the revealing module pattern is that it's awfully repetitive to list the same things at the top and twice at the bottom. And also sometimes when everything's defined at the top, you can't really tell what's supposed to be public and what's supposed to be private. So, sometimes by convention, I'll prefix things that are supposed to be private with an underscore. This makes it a lot easier when you're looking at the code later to be able to tell which ones are public and private right away. If you now look at our taskData module the way that we defined it and the revealing module pattern next to each other, you should be able to see that our module is an adaptation of that design pattern. The difference is that instead of using a self-executing function, it's in a callback function that's executed by Require. Be sure to keep this in mind as you define your AMD modules, that you're basically just using the module pattern, and you can hide things that should remain private inside of your module.
Define the Remaining Modules
In this section of the course, I'm just going to go ahead and break apart the rest of our existing sample application code into more modules. So, if you feel like you already have a good grasp on what we're doing with the modules and how Require is loading them, you can go ahead and skip this section. Otherwise, you can listen to me talk through refactoring some more of this code. So, here I have a set of things that work with creating DOM elements. That looks like a common responsibility, so I'm going to move those off to another module. I'll use my define function to define a module. I'll give it a name. Let's call it taskRenderer. I'll list anything that it depends upon, which for the moment is nothing, and give it a callback function that then takes no arguments since I'm depending on no other modules. We can get rid of this comment that no longer matters. And using the revealing module pattern, I can specify a return object here that will become my module. So, here again, I'm going to just use the revealing module pattern and return the functions that are defined above in an object. That module now needs to be added to the rest of our code just like we did with the taskData module by passing the module name to the dependency array list and to the function parameters. And then anything that was calling those functions before needs to now call them from the taskRenderer module. So, now we have our second module defined. Next, we have a lot of code here dealing with task management. So, let's also move those out of here. I'm just going to call this module tasks, and all the code that I just removed actually used taskData and taskRenderer, so this module is now going to have to depend on taskData and taskRenderer. That also means the code down here in require no longer depends on taskData and taskRenderer because nothing in here actually calls any of those anymore. So, we can remove those. However, it will need to depend on tasks. So, we can add that module as a dependency. And I'll once again use the revealing module pattern here to expose this module's public members. And once again we need to fix the call to our function since it moved by changing the call to render to tasks.render. Finally, we have this last bit of code here, which just initializes some event handlers. I would consider this to be application initialization as a responsibility. So, what I generally do is to define a module that's named app, and usually that is my main application module. You can do this if you want to. If you don't like it, then there's really no need to. I like it just because it separates my main.js file and an app.js file where main really sets up require and app really sets up my application-specific components. As we separate the code into different files, it might make a little bit more sense. But for right now, I'm just going to define app, and we're going to put my event registration in there. And here I'm going to use the regular module pattern and define an init function that's just going to call registerEventHandlers. And since that function is now private within this module, I'm going to prefix it with an underscore. So, now down here I can clean this up a little bit more. And instead of taking in tasks, this module can now take in tasks. And my main require down here can now just call app.init, and we'll pass the app module into here. And that'll take care of the call to registerEventHandlers. With RequireJS, this call to jQuery to pass it a callback function to run when the page is finished loading is not necessary at all, so we could have removed that a long time ago. And then there's just the tasks.render, which we need to call. So, we'll just move that out of here and put it into our app.init. So, now our main require callback is just very simply calling an init function for the rest of our app module. Actually, nothing in here uses jQuery either anymore, so we can take jQuery out. This does use jQuery, so we need to add jQuery here---oops, not there---in this array. And I have a tendency to put third-party libraries first, so jQuery usually ends up as the first thing in my array of dependencies, and then my own modules tend to go last. That's just the way I do it. You can do whatever makes the most sense to you. And I'm sure some of these other modules use jQuery. This one, obviously, has some calls to jQuery in it. So, let's add it there as well. This module also has calls to jQuery. But our taskData did not. And finally, the last thing that I had to do was that, in the app module here, all these event handlers used to be calling functions that have now been moved off to other modules, so we need to add all these functions. And since these functions are being assigned to function handler callbacks, I'm going to give them names that are a bit more representative of what buttons they're tied to. This kind of thing isn't too uncommon when dealing with any kind of dependency injection framework and, really, any language since we're moving things off to different pieces, maybe their modules or classes or whatever. Sometimes, you just have to wrapper up calls that pass through to the next layer. Once again, testing in the browser, we can see that we can still add new tasks, delete the tasks, delete all, and there're no errors in the console. So, our refactoring has gone well. If we were to map out the hierarchy of our modules we've defined, it would look like this. TaskRenderer and taskData get loaded by the tasks module. The tasks module gets loaded by the app module. And the main require function loads the app module.
Simplified CommonJS Wrapper
So far, we've been defining all of our AMD modules by listing a set of dependent modules in an array of strings and then passing those in as the parameters to the callback function. This is really the standard AMD module syntax for setting up module dependencies. However, there's an alternative to this that we should also discuss. You can see here that we're currently only importing two modules, but there can certainly be the case where you're importing six or seven or eight modules and things get a bit cluttered. They tend to run off the right side of the screen pretty quickly. Sometimes, it's a little bit easier to organize this by putting the callback function on a second line and just adding spaces so that the parameters line up. But still if you have a lot of things being imported here, this can still run off the right side of the screen pretty quickly. So, let's look at an alternative that RequireJS makes available that is called the simplified CommonJS wrapper syntax. To use this syntax, instead of specifying this list of imported modules, we just pass a function, and it has to have exactly the parameters require, exports, and module. Module is actually optional, but these need to be here in exactly this order with exactly these names. When that's included, RequireJS will allow you to import other modules using the passed-in require function. So, for importing jQuery, we could say var $ = require jQuery. The string here, again, being the module name, the same as we had in our array earlier. And then we can import our tasks module the same way. Now, also instead of returning an object that becomes our module, we need to do things a little bit differently. We no longer need to return anything here. But we use the exports object that was passed into our function and assign all the things that we want to make public to that exports object. So, in this case, to make my init function available, I would say exports.init =, and set it to the function. It's a bit different syntax, but it follows along with the original CommonJS syntax. And this is actually pretty useful in the case where you have a lot of things being imported into your module because instead of it running off the right side of your screen in a big array list of strings, you can set it up vertically as a set of imports instead. And so here, again, is our list of parameters for the define function now with the simplified CommonJS wrapper syntax added to it.
Summary
In this module, we took a look at the define function and how it's used to define our modules using both the standard AMD syntax and the simplified CommonJS syntax. We also looked at the require function and how it's used to initially load all of our modules. We learned that each module is defined by a string name, and we also learned that RequireJS is one of many implementations of an AMD module loader and that AMD stands for Asynchronous Module Definition. We learned about the module and revealing module patterns and how they fit well with the AMD modules. We also refactored our sample application and broke out all of our individual AMD modules. however, this isn't exactly how we want to structure our code during development. So, in the next module, we're going to learn how to load our AMD modules off the server and separate all of our code into separate js files instead of one large file.
Loading Remote Modules
Introduction
In this module, we will continue to refactor our to-do list application by moving all of our RequireJS AMD modules to separate files. We will then look at some performance implications of this approach and begin to see what we can do to eliminate these issues. Up until this point, we have been working in one large main.js file making it bigger and bigger as we need to add new modules. When developing a real application, you would typically not do this as it would make your code bulky and hard to read, which is one of the original problems we were trying to get away from when using RequireJS. Instead, we want to be able to put each of our AMD modules, which each deal with a single responsibility, into its own file. This lets us organize things nicely in our project during development. Let's begin by looking at our existing sample project.
Define Remote Modules
When we defined each AMD module directly in our main.js file, we gave the module a name. For example, this module is named taskData. When we require or depend on a module, we specify the module's name, as done here in the array of dependencies. When RequireJS needs to load a module, it checks its internal cache of defined modules. If a module with a matching name is already loaded, then it is returned. This is why defining our AMD modules in the same main.js file before the call to require at the end works. However, if a module with a matched name is not found, then RequireJS uses the name as a URL and attempts to load the file. To understand how this works, it is important to know that internally RequireJS keeps track of what is called a base path that is used to load modules from. The module names are considered a URL relative to the base path. By default, the base path is set to the directory that the main.js file is located in. In our sample project, the main.js file is located in the js directory, so all other modules would be loaded relative to this path. Later in this course, in the module on configuring RequireJS, we will see how we can change the location of this base path. As we continue refactoring our sample to-do list application, the next step is to move each of our modules to its own file. Let's start by moving our taskData module to its own file. When building a URL to load a module from, RequireJS will automatically add the .js extension to the requested module name. The file should be placed relative to the base path, which is the /js directory. This means that we want to create a file that is the combination of base path plus the module name plus the .js extension. So, we want to save a new file to js/taskData.js. We then move our code to define the module into this new file. In addition to just moving the code, we also no longer need to specify a module name. This is because RequireJS will use the module name that was requested originally. In other words, because our tasks module is dependent on the module named taskData, RequireJS loaded a file name with the path js/taskData.js and cached the resulting module object with the module name of taskData. The module itself no longer has to tell RequireJS what its name is. This gives us another possible syntax for the define function that we use when defining a module that will be loaded from a remote file. This syntax just has two parameters--the array of dependencies and the callback function that creates the module. We can now do the same thing with each of our other modules.
Using Subdirectories
You can see here that we have created a file for each of our defined AMD modules. However, the project is still lacking some structure. It would be nice if we could further separate these js files into subdirectories. Fortunately, we can do just that. Let's make a new directory to hold our js files related to data saving and loading. We will name the folder data and move the taskData.js module into this directory. Now, we need to fix any references to the taskData module and include the data subdirectory. The tasks module is the only module that depends on taskData, so in this file, instead of depending on taskData, we can now depend on data/taskData. This will cause RequireJS to load this module from js/data/taskData.js, which is the correct path for our taskData module. Now let's do the same with our taskRenderer module. We'll move it into a new directory named renderers and change the reference in the tasks module to include the new subdirectory. This ability to add subfolders is a powerful tool in allowing us to better organize scripts in our project helping all the developers on the project to more easily navigate through the code. However, there is one limitation with subdirectories that you should be aware of. If you decide to put a module in a subdirectory, then it needs to be underneath the base path. If you try to depend on the module and you start its name with .. to try to go up one directory, this will not work. However, we can see a workaround for this issue later in the course in the module on configuring RequireJS.
Performance Implications of Remote Modules
When we now load our sample project in the browser, we can see that everything still appears to be working. However, there's actually a performance problem due to the use of the remotely loaded AMD modules. If we look at the browser dev tools in the Network tab, we can see that RequireJS is making a call to the server for each and every one of our AMD modules. This is going to cause more requests to and more load on our server, and it will also increase the page load time for the user. This also isn't really what we want to do. We have improved our code dramatically at development time making it easier to read, organize, and navigate, but done it at the detriment of performance at the client, which is going to lead to a worse user experience. In the next module, we're going to talk about the r.js optimizer for RequireJS, which will take care of this problem for us and allow us to have a good experience both at development time and at runtime.
Summary
In this module, we learned that we can have RequireJS load modules from remote files. We learned that the syntax for the define function changes, no longer specifying a module name when a module is defined in its own file. Subdirectories are used to better organize our files, improving maintainability and organization of our code. However, we also found that when loading modules remotely, a server request is made for each file causing a reduction in performance on page load. In the next module, we will learn how to overcome this issue using the RequireJS optimizer.
Optimization
Introduction
In the previous module, we separated our code into multiple files and used subdirectories to organize our code to make it easier to read and maintain. This had great development time benefits because we can have smaller files, avoid spaghetti code, and respect the single responsibility and dependency inversion principles. However, this caused a runtime issue for the end user because each file now has to be loaded from the server. This caused more requests to the server and increased the load time of the client. In this module, we will finally find the right balance between deployment ease and runtime efficiency by using the RequireJS optimizer named r.js to eliminate this problem. The desired outcome of this optimization step is to take all of our individual files, main.js, tasks.js, etc., and combine them into a single file, main-optimized.js. This will reduce the number of server requests the browser has to make. We then want our file to be minified, which will reduce the overall size of the file that needs to be transferred. To accomplish this, we are going to need to set up a build environment for our project.
Build Environment and Running r.js
Let's start setting up our build environment by downloading the RequireJS optimizer, r.js, from requirejs.org. Then use the downloads link on the left, then download r.js. In the sample project, I've been putting all the files that make up our application into an SRC directory, short for source. When we optimize our files with r.js at build time, the r.js file will not be deployed. Because of this, I'm going to make a new folder for files that we are just using during development that don't get deployed. I'll name it build for this example and save r.js into this directory. One detail you may have caught there is that I mentioned build time. Traditionally, JavaScript doesn't have a build or compile step like other languages, including C, C#, and java. But, consider how all these other languages work. You would write your code in several different files, for example .cs files for C#, using subdirectories and namespaces to keep your code organized. Then you would compile your code into a single DLL or exe file for deployment. The same is true for Java. You would write your code in multiple .java files, then compile them to .class files, and finally combine them into a .jar file for deployment. Why can't we do the same thing for JavaScript? Well, it's time to start thinking of JavaScript like these other languages including a build process in your projects. For this example, we're going to keep things simple. The r.js optimizer can be run wherever JavaScript can be run. So, in our case, we will use Node.js. If you aren't familiar with Node.js, it is basically the Chrome JavaScript engine running from the command line instead of in the browser. This allows us to run JavaScript, including the r.js optimizer, from the command line outside the browser. To install Node, download it from nodejs.org. Once we have NodeJS installed and r.js downloaded, we can execute it by simply opening a command prompt or shell and running the node command and pass the path of the JavaScript that we want to run, which in this case is build\r.js. However, before we do, let's discuss what it is we want to optimize. Remember that when RequireJS gets loaded in our web app, we point it to a main.js as the main entry point for our application. When we optimize our code, we also want to point r.js to main.js. The optimizer will start at that file and traverse through all of the required dependencies. So, in our sample project, r.js will look at the main module. It will see that main depends on app and include that file for optimization. It will then see that app depends on tasks and so forth until it collects up all the module files that it needs to resolve the entire dependency tree. It will then take all those files and concatenate them into a single file and then minify that file. Since we now know we want to optimize the main.js file, we specify the -o option and pass main.js on the command line. The command line parameters to r.js are usually specified by the property name, then an equal sign, then the property value. To specify the primary file to optimize, we use the name option. Note that the name option is the name of a module, not a file path, so the .js extension is not needed at the end. In the previous module, we also talked about the base URL or base path that is used to locate modules. For r.js, we need to set the base URL option to the location of our main module, just as it would be when loaded from the browser. We also need to let r.js know where our RequireJS configuration is using the main Config file option. In our case, we can reuse the configuration that is in the main.js file here. If you wanted to have different configuration between optimized and non-optimized versions of your code, it could be done by specifying a different file as the main config file. In our case, we will just use source/js/main.js. Note that this is a path to the file relative to the working directory, not relative to the base URL. We also want to specify our output file using the out option. So, let's output our optimized code to main-optimized.min.js. Normally, our optimized file would also be minified, which is why I'm including the .min in the output file name, but just for a moment, so we can get a better look at what's going on, let's disable minification. We can do this by setting the optimized option to none. Now, we can execute this command, and our optimized file will be built. In the output, you can see the list of modules that were included into our output file. As a final step, we can now also change our index.html file to use main-optimized.min.js instead of just main.js by changing the data-main attribute on the script element that loads RequireJS.
Optimization Result
Let's open our optimized file and see what the r.js optimizer actually did. Remember that I turned off minification so that this file would be more readable. Back in an earlier module in this course, our first step in using RequireJS was to simply define all our modules in one big file. So, let's open the optimized files side by side with that older version of our code. The optimized version looks remarkably similar to what we originally did. It took all of our modules that were in separate files and put them back into a single file. It also included the module names again that we had removed when we moved the code to separate files. The result of this is that when the JavaScript is executed at runtime, all these modules will get defined in RequireJS's module cache. Then when they need to be loaded, RequireJS will not have to load them from remote files since they are already defined. Now let's see what the effect of our optimization is at runtime. First, let's load our old, un-optimized code from the previous module into the web browser and take a look at the Network tab in the developer tools. You can see that our page needed nine requests to the server and loaded 104 KB of data taking 197 ms. Next, let's re-optimize our code one more time but include minification. Now that we have our optimized file, let's load the page in the browser and see what happens. with the optimized file, you can see that we only make four server requests for 101 KB of data taking 185 ms. Normally, we would expect to see a larger difference in the amount of data transferred, but in this case, most of the code is jQuery, which is already minified, so we aren't really saving a lot of data from our minification since this is a relatively small project. You can see that the optimized version is better even for the small example. In reality, even if you are working on a large single-page application, you could easily have dozens of JavaScript files. Loading all of these individually without optimizing them could add seconds to the load time of your page.
Debugging and Source Maps
So, optimization of your files is sounding pretty good right now, isn't it? Well, let's accidentally introduce an error into our code. Perhaps try to call a function that doesn't exist. This will throw an error in the browser when it executes. We then need to rerun the optimizer to rebuild our optimized file. Now when we run this in the browser, we can see on the console that we have an error. So, let's debug this error. That's right, our optimized code is running here, and it's minified. So, not only can we not tell what line of the original code this is, but we can't even tell what file it was. Now, it's really hard to debug our code. There are two ways to solve this. One, which I usually do, is to just use the optimized code for production releases but keep using the un-optimized main.js for development. However, that means you need to have a way to change what JavaScript file is loaded by RequireJS in the index.html file. The second option is to use source maps. Source maps are a secondary file deployed alongside your optimized file that tells the browser where lines of JavaScript originally existed before optimization. If you have done any work with CoffeeScript, TypeScript, LESS, or SASS, then you may already be familiar with source maps. We can have the r.js optimizer build source maps by setting the generateSourceMaps option to true. If we try to execute this command, we get an error stating that we also need to set preserved.LicenseComments to false. So, let's also add that option. Now, we get another error stating that uglify does not support the generateSourceMaps option. By default, r.js uses version 1 of the uglify NodeJS library to minify code. However, that version does not support source maps. We need to tell r.js to use version 2 of the uglify library instead. To do this, we use the option optimize=uglify2. Let's reload our page on the browser and see what we get now. When we debug into the source, you can now see the code as it was originally before optimization. Looking at the Network tab, we can see that another file with the same name as our optimized file but with a .map extension was also loaded. This is the generated source map. This can really ease debugging of minified code. So, if you continue to use the optimized version of your application in development, you will likely want to keep this option enabled.
Build Profiles
So far, we've been putting all of our options to r.js on a command line. Since we have a lot of parameters on our command line now, there's no way we're going to remember all these. Instead, we can make a JavaScript file called a build profile to hold all the options for us. To do this, I'm going to create a new file in our build directory and name it build.config.js. In this file, we put a JavaScript object definition inside a pair of parentheses. This object can then contain each of our config options from the command line. Now, instead of passing all these command line options, we can simply run node build\r.js-o, and then the path to our build profile, which is build\build.config.js. But we get an error. The problem is that r.js considers the paths in its options to be relative to the build profile, which is now the build directory, so we need to add ..\ to each of the paths. We can now rerun the command, and it will build our optimized file and source map the same as before. This greatly simplifies our build command and also provides a way to set up multiple build profiles if needed by our project.
Summary
At this point, what we've done so far should all make sense. We decided that maintaining all of our code in one file was difficult at development time, so we broke it into separate files. Since separate files is not optimal for runtime, we used the r.js optimizer during a build step in our deployment to create a single deployable JavaScript file, much as you would do with other languages like C# or Java. We saw how the r.js optimizer can be run using Node.js from the command line and can optionally minify the output files. To ease debugging, source maps can be generated for the optimized minified code. This lets us see the original source files and line numbers in the browser debugger. Finally, we saw how a build profile can be used to put our optimizer configuration options into their own file. I encourage you to read over the documentation to learn more about these various options, but what I've demonstrated here is probably the most common use case. Simply optimizing your code starting from the main.js file and optimizing everything that it references. In the next module, we're going to learn more about the advanced configuration options that RequireJS provides to control how modules are referenced and loaded at runtime.
Configuration Options
Introduction
In this module, we're going to discuss some of the most common RequireJS configuration options. At the time of this recording, there are 17 primary configuration options that you can specify when calling require.config. Some of these are seldom used or only for special cases. So, in this module, we'll discuss seven of those options, which from my experience are the most commonly used, and talk about some cases where each of the options would make sense to use. A full list of the configuration options, including the ones not covered here, can be found in the API documentation on the RequireJS website. The URL is at the top of this slide. There are three common ways to specify your configuration. The first and the one that I've seen used the least frequently is to define a global variable named require that is set to an object that contains your configuration options. Then load the RequireJS file. This will use the configuration from the require variable, then change the require variable to no longer be your config object but, instead, be RequireJS itself. I find this approach the most awkward because the value of the require variable gets changed when RequireJS loads. The second option is to load RequireJS first, then call require.config after it has been loaded. I find this option to be more understandable than the first one because of the explicit call to require.config. The third option, which is the one that I usually use and is the one that we are using in our sample task list project, is to call require.config in the beginning of your main JavaScript file. This is the file that was specified in the data-main attribute of the script tag that loaded RequireJS. Now that we know how to specify the configuration options, let's look at some of the most commonly used options starting with the baseUrl option.
BaseUrl
The baseUrl option is used as the default root path for module lookups. As discussed earlier in this course, RequireJS resolves remote modules by appending baseUrl plus module name plus the .js extension. By default, the baseUrl is set to the HTML page that loaded RequireJS. However, if the data-main attribute is used, as we did in our sample project, then the base URL becomes the directory containing the main file instead. That default can be overridden by setting the baseUrl option directly. One interesting use of the baseUrl is to load files from a different host altogether. For example, if you had all your JavaScript deployed to a separate server or a CDN, you could set the baseUrl to that server, and your modules would be resolved from that remote server instead. In my experience with RequireJS, I seldom need to set the baseUrl in configuration and, instead, choose to put my main.js file in a location that makes sense to be the baseUrl in relation to my other scripts.
Paths
The paths option can be used to override the default paths used for module lookup. To use it, you set the paths option to an object. That object will get a property with a name that matches the path to map from and be set to a string that is the path to map to. There are two ways that this is commonly used. One is to use a simpler name for a module whose file has a complex name. This is exactly what we did in our sample project when loading jQuery. The name of the jQuery file was jquery-2.1.1.min.js. It would be awkward if in each module where we depended on jQuery, we had to reference it by its actual file name jquery-2.1.1.min. To simplify things, we can use a path to tell RequireJS that when we ask for jQuery, it should actually use jquery-2.1.1.min. In this case, we were having the path match an entire module name, but we can also just have it match part of a path. For example, if you want to specify a path in a module name, for example scripts/sample, then it would normally load this module with the URL scripts/sample.js. However, we can specify a different path for scripts that changes it to app/js. In this case, the module script/sample would now be loaded from the URL app/js/sample.js. There's one more really useful case for the paths options. If you remember earlier in this course when we moved our modules into separate files, I mentioned that the files and directories had to be underneath the baseUrl. You could not start a module name with ../ to move up a directory. However, you can start a path with ../, so you could load modules that are up a directory in relation to the baseUrl by specifying in the configuration scripts maps to ../scripts. In this case, the sample module would now be loaded using the URL ../scripts/sample.
Shim
The shim option is what you'll use when you want to depend on a third-party library that does not register itself with RequireJS on its own. The example I most run into is underscore.js. Normally, if you include underscore.js using a script element in the head of your HTML page, underscore will set the underscore character in browser global scope to be the underscore library's main object. If you wanted to have an AMD module of your own code that lists underscore as a dependency, then what you will need to do is tell RequireJS that there is a module for underscore and that when you ask for that module, require should actually pull a variable from global scope. We can do this by specifying a shim. The shim option should be set to an object. The property names in the object will become the names of the modules being shimmed in. So, for this example, let's make a shim named underscore. The underscore property then also gets set to an object. Each shim object should specify an exports property. The value of this property is a string that matches the name of the variable in global scope that we want to use as the module. For our underscore example, we want the underscore module to use the window.underscore character variable. Normally when you define an AMD module, you can specify an array of other modules that this one would depend on. With the shimmed module, we can use the deps property to include an array of other modules. Underscore doesn't have any other dependencies. But just as an example, let's tell RequireJS that underscore depends on jQuery. We do this by adding the jQuery module to the deps array. If this doesn't quite make sense, let's think about it another way. This shim config is roughly equivalent to this code that defines a normal AMD module named underscore. You can also combine the shim and paths configuration options. For example, if our underscore file was named underscore-1.0.min.js, but we wanted the module to be named underscore, then we could use this configuration. Here, RequireJS will look for the underscore module. It will build the URL to the file using the value in paths, so underscore-1.0.min.js will be loaded. After the script is finished loading, RequireJS will get the underscore character variable from the window object and finally pass that value to the require function callback. The shim option is used very frequently since most apps you write will depend on some third-party JavaScript libraries.
Config
The config option gives us a way to pass configuration options down to a module. For example, let's say that we have a URL to a set of web services that we want to have in the global configuration for all modules to use. We could specify the RequireJS config option and set it to an object that contains our URL. Now, when a module needs these config options, it can depend on a special module named module. Module will have a function named config that we can call to get our config object that was specified back in the configuration. This is a nice way to specify your global app configuration in one place and have several modules use it. So, now that I've explained how config works, I'll tell you that I've actually never once used it. When I have configuration that I need to share across modules, I like for it to be in its own file. After all, part of the reason for using RequireJS is to have the ability to better organize our code. Typically, I just make a remote module names config.js to hold my configuration. Then my other modules can just depend on my config module to get the configuration options that they need. This to me looks a lot nicer, but it's up to you.
WaitSeconds
The waitSeconds option is the number of seconds to wait for a module to finish loading before throwing an error. Remember that module loading is asynchronous. There can certainly be times that we try to load a module remotely from a server, and the server fails to respond. The default is seven seconds. If you run into the situation where a module takes a long time to load, then you may need to adjust this setting. You can also disable the timeout by setting the value to 0.
Deps and Callback
The deps and callback options are used together. So, let's talk about them at the same time. The deps option can be set to an array of module names. These module names will then be loaded as soon as RequireJS starts up. Note that these modules are still loaded asynchronously, and any calls to require that you have in your code can still be called before these modules are finished loading. It is really just there to specify some modules that you want to load right away. The callback option can be set to a function that will be called by RequireJS when all the dependencies in the deps array are done loading. This is another option that I've really never had to use since it would normally just include all the modules I want to load right away in the first call to the require function. These two pieces of code do basically the same thing.
UrlArgs
The urlArgs option can be used to provide extra query string options to the URLs that RequireJS uses to load modules. The option can be set to a string, and that string will be appended to the URLs as the query string. You do not need to include the question mark in the setting. The most common use case for this option is for cache busting. By appending a changing string to the query string, the browser is prevented from caching the result. For example, when RequireJS loads module one here, the URL that it uses will be moduleOne?bust= and then a number representing the current time when this page was loaded. Unfortunately, this config option is all or nothing. You cannot add a urlArgs to some module loads but not others.
Summary
That was a quick overview of what I think are the most commonly used RequireJS options. Again, there are a few more options, but I've never really seen them used. If you want to explore the full list of possible options, please visit requirejs.org and check the API documentation. In the next module, we're going to discuss RequireJS's plugin framework.
Plugins
Introduction
One of the really nice features of RequireJS is that it provides a plugin framework that can be used to extend its default functionality. In this module, we will discuss this plugin framework by taking a look at a couple of commonly used plugins, as well as make a custom plugin of our own. First, you should understand what plugins are. Plugins are used to load individual modules and load them from outside the normal method performed by RequireJS. In other words, instead of using require's built-in mechanism for loading a certain module, the plugin loads the module, then hands it back to require. To specify that a plugin should load the module, you use the syntax plugin name, then an exclamation mark, then the module name as usual anywhere the dependent module is specified. Interestingly, RequireJS plugins are actually AMD modules themselves. Let's start by looking at a simple example. We have a module that has this definition. When RequireJS loads this module, it will see that it first needs to load two other modules. First, it will request moduleOne as it would normally with the URL moduleOne.js. Next, RequireJS will see that the next module uses the superPlugin plugin, as represented in front of the exclamation mark. It will load the module superPlugin using the URL superPlugin.js just as it would any other module. This also means that superPlugin.js is loaded relative to the base URL. However, as we learned in the previous module on configuration options, you could use the path option to tell RequireJS to load superPlugin from somewhere else. Next, it will tell superPlugin to load moduleTwo and return the result. This means that RequireJS will not go and fetch the URL moduleTwo.js. It will be entirely up to superPlugin to deal with loading moduleTwo and handing off the result to RequireJS. If this doesn't make complete sense yet, don't worry. We're going to look at two RequireJS plugins starting with the text plugin.
Text Plugin
In my experience, the text plugin is the single most used plugin for RequireJS. The text plugin is maintained as one of five official plugins supported by James Burke, who's also the primary maintainer of RequireJS. Normally, Require loads only JavaScript files, and those files are executed as soon as they're loaded. The text plugin, on the other hand, loads files as plain text and returns that text as a string to the module. This plugin is really handy for loading HTML files as plain text. To demonstrate this, let's go back to our task list sample application. In the renderers/taskRenderer module, we have a string of HTML here that's used as a template for each task in the list. Having HTML in your JavaScript is a little ugly and hard to maintain because you don't get any syntax highlighting or any benefits that your text editor would provide if you actually viewed the string as HTML. In fact, since JavaScript doesn't even support multiline string literals, we had to put it all on one line here, so you can't even read it without scrolling to the right. Wouldn't it be great if we could just put this task template into an HTML file instead of JavaScript? Well, with the text plugin, we can do just that. Let's start by downloading the text plugin. The text plugin is available from GitHub at this URL. Save the text.js file into the js directory. Next, let's make a new directory to hold our templates. Source/templates looks like a good spot. Note that I didn't create the template directory under the js directory, because these files will be HTML, not JavaScript. If you remember from the last module, this directory placement will be an issue because it is above the base URL, which is the js directory. That means that to get a template in this directory, we need to go up one directory from the base URL. We learned in the last module that the paths option can be used to do this. So, in my main.js, let's add a path for the templates directory. We'll do this by just mapping templates to ../templates. Now, let's make a new file in the templates directory named taskTemplate.html. Next, we can remove the HTML elements from the JavaScript file and put them into taskTemplate.html. Notice that now we can format our HTML nicely and have syntax highlighting. This is so much easier to read and maintain than having the HTML in a JavaScript file. Finally, we need to load this HTML file in the taskRenderer module. We do this by making a dependency on the module, text!templates/taskTemplate.html, and adding this to the list of callback function parameters. This will tell the text plugin that it needs to load ../templates/taskTemplate.html. The text plugin will load this file as a string and pass that string into the taskRenderer module. There are two important things to note about using the text plugin. First, you will notice that we had to specify the .html file extension. Normally, RequireJS would add a .js extension to the module that you load, but the text plugin does not make any assumptions about the file extension since you could be loading any type of file. Second, if you now tried to run the application by opening index.html from the file system so the browser would use a file:// URL, you would find that the text plugin is actually unable to load its files. In order for the text plugin to work correctly, you have to be serving your files from an actual web server. This is a limitation that is specific to the text plugin because it uses an XHR request internally to load the files. The default security policy for most browsers will not allow files to be loaded from the file:// URLs using an XHR request. So, on my system here to demonstrate the application, I added a website to IIS to serve the task list application. However, if you are using the r.js optimizer, then the file contents of taskTemplate.html will automatically be loaded at build time and inserted into the main-optimize.min.js file. Then, no XHR request needs to be made at runtime, so the application can still be used by opening it directory from the file system using a file:// URL.
Handlebars Plugin
Being able to move our HTML out of our JavaScript and load it from an HTML file with a text plugin is a huge step in cleaning up our code. Looking at our taskRenderer module, we are using jQuery here to set the complete checkbox and textbox to the data in our loaded tasks for display to the user. Instead of doing this with jQuery, what if we used a handlebars template instead? If you are unfamiliar with handlebars, just know that handlebars is a popular templating engine for HTML and JavaScript. Fortunately, there is already a handlebars plugin for RequireJS written by Alex Sexton. Where the text plugin was an officially supported plugin, there are many plugins like the handlebars plugin that are provided and supported by the community instead. First, let's get the handlebars plugin. The easiest way to do this would actually be to use Bower, which is a web package management system built on top of Node. But Bower is quite outside the scope of this course. However, we did talk earlier about using NodeJS in the module on optimization. Node comes with the Node Package Manager, or NPM for short. We can download the handlebars plugin using NPM. Open a command prompt to the directory containing your sample project and run the command NPM install require-handlebars-plugin. This will download the plugin into a special folder named node_modules, which is where NPM puts all the packages that it downloads. Next, let's change our taskRenderer module to no longer use the text plugin and instead use HBS, which is the module name for the handlebars plugin. We can also remove the .html extension from the template name. The handlebars plugin will add a .hbs file extension on its own. Next, we need to rename the task.html file to task.hbs. Finally, in main.js, we need to let require know where to find the hbs.js file since that is the file it will attempt to load for the plugin. For this example, I will be using the r.js optimizer so I can point it to the node_modules directory where NPM installed it. If I was going to deploy this to a real web server without optimization, I would have to move the files for handlebars plugin to a file that gets deployed with my project. Since the template will now be in handlebars, we no longer need to set the checkbox and textbox values with jQuery. We can, instead, just pass the task variable into the handlebars template that was returned by the plugin. The object returned by the HBS plugin into the taskTemplate variable is actually a function containing the template from our task.hbs file. We call this function passing an object to run the template against, and it will return the resulting string. Then, we can remove the jQuery code to set up the values. Over in the HBS template file, we can now use handlebars to set the checked attribute on the checkbox and set the value of the textbox itself. Then, we can run our r.js optimizer against this code. And, finally, test the application in the browser. Let's add a couple of tasks and give them a description and mark one complete. Then click Save and reload the page. And you can see that the tasks render with our saved values. This is all happening through the handlebars template.
Custom Plugin
There are dozens of plugins available for RequireJS. And you can also create your own plugins for your own needs. The plugin API is relatively straightforward. Let's make a custom plugin of our own that can load and compile CoffeeScript files. Here, I've started a new sample project that has some existing files. The node_modules directory contains a copy of RequireJS and the r.js optimizer that I installed using the Node Package Manager, much like I had done with the handlebars plugin in the last section. The index.html file is very simple. It just loads RequireJS and sets main.js as our main file. The main.js file is currently empty, but this will later be the entry point for the application. Build.js is a configuration file that will be used for the r.js optimizer. Coffee-script.js is the CoffeeScript compiler, which we're going to need to compile a module that we're going to add in CoffeeScript in a moment. And, finally, require.js and text.js are also being used here. Let's start by making a new module that we want to load. Let's name it test.coffee since this will be a CoffeeScript file. Then we define a module that just returns an object. That object contains a single property named out and is set to the string hi. This test module has no other dependencies. Next, let's load this test module from main.js. We do this with a call to require and specify the dependency test. We know that the test module has an out property, so let's write its value to the console. The result of this should be that the string hi is printed on the console in the web browser. Since test is a CoffeeScript file, require can't import it on its own. Let's specify the name of our custom plugin to use to load the module instead. Since our module's going to be a CoffeeScript loader, let's name it coffee and include an exclamation mark to separate it from the module name. Now, let's make a new file named coffee.js. This will contain our custom plugin. Remember, I said that plugins are themselves AMD modules, so we still need to start with a call to define just like any other module. We know that we're going to need a CoffeeScript compiler in this module somewhere, so let's go ahead and add a dependency on CoffeeScript now. Now, because this is a plugin, the object that is returned needs to contain the methods that make up the RequireJS plugin API. The first one and the only one you really need is named load and takes in four parameters. The name parameter is the name of the module that require wants this plugin to load. ParentRequire is a reference to the RequireJS module loader so we can use it to load additional modules as needed. OnLoad is a function that we can call when we're done loading the requested module. Remember that module loading can be asynchronous, so we couldn't just return a loaded module from this load function. Instead, we need to call the onload callback to let it know that we have finished loading. Finally, the config parameter is any configuration information passed in from require. Now, it's time to come up with a plan for loading our CoffeeScript. I'm going to take the easy route and just have the text plugin load it for me as plain text. This is an example where we can actually chain some plugins together. I do this by making a call to the parentRequire function with an array of additional modules to load. Since I have the requested module name in the name parameter, I can just prepend text! to this, and require will ask the text plugin to load the module. Then we also need to specify a callback to be called when the modules have been loaded. I'm going to specify this callback function up above instead of inline because it's going to help us with something later on in the future. Now what we get back as coffeeText here is a string containing the contents of test.coffee as loaded by the text plugin. We can call CoffeeScript.compile and pass in the string containing the CoffeeScript. The compiled variable will be set to a new string containing the compiled code, which is now JavaScript. Finally, we can call onload and tell it we're done. But in this case, we'll use the fromText function to tell require that the thing we're giving it isn't a fully initialized module as JavaScript---it's not a JavaScript object. It is, instead, a string containing the JavaScript and it still needs to be evaluated. You could instead just use JavaScript eval function to turn the string into JavaScript, but I think it's better to let require handle it for us. So, if we went and ran this right now in the browser, it should actually work. However, there's another part of the plugin API that we should also implement, and that is the write function. Write is called by the r.js optimizer after calling the load function and gives the plugin the chance to write any optimized output into the resulting optimized file. The write function gets three parameters. PluginName is the name of the plugin, which is this plugin. Since in main.js, we told it to load using a plugin named coffee, the plugin name here will be coffee. ModuleName is again the name of the requested module, much like name in the load function. Finally, write is used to output our optimized code back to require. The load function would have already been called before write, so we will already have our compiled CoffeeScript code available when write is called. Let's just call write.asModule and first pass in the full name of the module being loaded. This needs to include the plugin name and will look just like it did in main.js when we requested the test module. We build this full module name by appending the plugin name and the module names that were passed in with an exclamation mark separator. Second, we can pass the string to write into the optimized file for this module. In our case, that will be the loaded compiled code from the load function. Here, I'm going to reference the built collection and pull out an item matching our module name that was requested. The built variable doesn't exist yet, so let's add it now. It will just be a hash of key value pairs, the key being the module name and the value being the compiled script. In the load function, we need to add the compiled script to the built hash but only if we're actually running an r.js optimization build. Otherwise, we would just be wasting memory by hanging onto the loaded string for no reason. You can check config.isBuild, which will be true if we are being called from the r.js optimizer during a build. It's a little weird to wrap your head around this part, but the load function could be called when running un-optimized code, in which case it would be running in a browser environment. It could also be called during the r.js optimization process, in which case it is running in NodeJS instead. In this plugin, we actually do care because we're using the text plugin to load our module for us. However, if we're running in NodeJS during a build, we don't need the text plugin to make an XHR request because the file isn't on the web server where it can be loaded. Instead, we can use the file system directly in Node to load the requested module. First, I check if we're running in NodeJS using this set of criteria. Now, once we know we're in Node, we can load NodeJS modules, which are common js modules, in case you forgot, using the require.nodeRequire function. The fs module is used to retrieve the file system module. The readfileSync function can then be used to synchronously load the requested module directly from the file system. This means that we no longer need to make a call to parentRequire and use the text plugin when running in Node. Our custom plugin is now complete. Let's run the r.js optimizer to build our optimized file. You can see that the optimizer pulled in the CoffeeScript compiler, our custom coffee plugin, and the test module. Now, if we load the index.html page in the browser, we can see that hi gets printed to the console. Remember that this string was defined in the test.coffee file originally, but that was loaded and compiled to regular JavaScript at optimization time. If this whole custom module thing was a little too confusing, don't worry about it. I've been using Require for over a year, and I've only written a custom plugin once, well, twice now that we just did this one here. In fact, the only plugin I've really found I needed was the text plugin. So, as long as you understand that one, you'll be just fine.
Summary
In this module, we used the text and handlebars plugins to further improve our task list sample project. Our HTML is now no longer in a JavaScript string, so we get the advantage of formatting and syntax highlighting in our editor. This improvement was facilitated by RequireJS's ability to support plugins allowing us to build upon Require's built-in abilities. There are many community contributed plugins to support many file and template formats. The list of known plugins is maintained at requirejs.org. If you run into a situation where you need to extend Require's functionality, you could also design your own plugin to accomplish the task. In the next module, we'll see how unit testing is affected by the use of RequireJS.
Unit Testing RequireJS Modules
Introduction
In this module, we will discuss unit testing of our AMD modules and how RequireJS plays into testing. In general, the nice thing about breaking our code into smaller modules and using a dependency injection framework like Require is that it makes our code more testable. We now have smaller units to test and can inject mocks to test with. However, use of Require actually significantly complicates unit testing. This is due to two issues. First is that module loading is asynchronous, so we need to use a test framework that can deal with asynchronous testing. Second is that Require caches loaded modules. This means that when a module is loaded for one test, the modules will still be cached in Require for the next test. But, don't worry too much about this yet. We'll get to that later. First, let's go over some unit testing basics and set up a test environment.
Jasmine
For this example, we're going to use the Jasmine test framework. Jasmine is a behavior-driven testing framework for JavaScript. First, we'll download and set up Jasmine. To keep things simple, we're going to download Jasmine manually from the website without the help of any package managers or test runners. You should know, however, that if you want to use Jasmine in real projects, there are a few other ways to download and run Jasmine. There are test runners for integrating Jasmine with Grunt, Gulp, directly in NodeJS. If you're doing Ruby-on-Rails development, you can integrate Jasmine with Rake. If you're doing Python development, you can download Jasmine using PIP. Basically, anywhere you might be doing web or JavaScript development, you can use Jasmine. All these other environments will have text-based command line runners. However, when you directly download Jasmine, you instead use an HTML page in your browser to run the tests. This means that you have the advantage of having graphical results and also full dev tool support. So you can debug your script errors when you have them. Let's start by downloading one of the stand-alone Jasmine builds. These are available from github.com/jasmine/jasmine/releases. You want to make sure you're running a 2.x version of Jasmine if you plan to use RequireJS because Jasmine 2 made big improvements for asynchronous testing. I'm going to download jasmine-standalone-2.1.1.zip and extract it to a folder. In the extracted files, you will find this set of files and directories. To integrate Jasmine into our sample task list application, we want to take the /lib directory and SpecRunner.html and copy them into our sample project directory. Also, let's make a new directory here and name it spec. This directory is where we'll save our tests. In Jasmine, test files are called specifications or just specs for short. You can really name this directory whatever you want, but spec is want Jasmine uses by default. The reason we didn't just copy the /spec directory from the Jasmine download is because it already contains some example tests that we would just end up having to delete anyway. Now, let's edit SpecRunner.html. This is the file that will run our tests for us and display the results. Since this is just like a normal Web page, we need to import all of our JavaScript files. Here the Jasmine test framework is loaded into the page. And here we can import our spec files and any other third-party libraries that we depend on. The imports that are referenced here are for some sample tests that ship with Jasmine. For the moment, let's just remove these script imports. Now, let's make our first test file. Start by making a new file in the spec directory. By convention, Jasmine test files end with .spec.js. So, let's name this file first.spec.js. Back in the SpecRunner.html file, we can now import this test file. Defining test specifications in Jasmine is done with two different functions. The first one is named describe. The describe function is used to group a block of tests together. Describe takes two parameters. The first is the string name of this grouping of tests. The second parameter is a function that will be called to run the group of tests. So, for example, we can make a grouping of expect in our first.spec.js file with this JavaScript. In the Jasmine test runner or SpecRunner.html file that we'll be using, tests that are grouped into a describe will appear grouped together under a heading that has the name that was passed in as the first parameter. Interestingly, describes can be nested to further group sets of tests like this. So far, we haven't added any tests, just made a grouping to hold our tests. The second function that we use for defining our test specifications is the it function. It is used to describe individual tests within a describe. Like describe, it also takes two parameters. The first is a string name of the test, and the second is a function that Jasmine will run to execute the test. When Jasmine runs our tests, it will prepend all of the described names to the test name. So, this test will be named Expect true to equal true. This is how we describe our tests in Jasmine, but the test itself still isn't doing anything. In Jasmine, we use a function named expect to indicate the value to test. This would be equivalent to asserts in many other unit testing frameworks. We then chain a second method onto the end of the expect call to indicate to Jasmine what the value should be compared to. For this test, we want to test that true equals true, so we can use .toEqual. Now, we can run our test by loading SpecRunner.html in the browser. Congratulations! You've just written your first Jasmine test. You can see here that all the tests are passing. Now go back to our test and change the value in the expect function to be false instead of true. And then reload it in the browser. Now you can see that our test fails. Jasmine also gives us the value that we expected, which is true, and the value it actually got, which is now false. Not only can you compare values, but you can also check to see if functions are called. Jasmine calls this concept spies. You spy on a function to see how many times it was called and with what parameters. Let's make a new describe section in our first.spec.js file and call it awesome function and then add a test named to be called. Now, let's describe an object that contains our awesome function. Now we want to spy on the awesome function so that Jasmine will replace it with a proxy and can track calls to the function. We do this using the spyOn function, which takes two parameters. First, we pass the object that contains the function that we want to spy on. Second, we pass the string name of the function that we want to spy on. Internally, Jasmine will now replace this function with a proxy of its own. Now, let's add our expect expression to check that this function was called. Great, now we can reload the SpecRunner.html file in the browser, and you will see that this test fails. That's because we never actually called the awesome function. So, let's edit the test and do that. Finally, we can reload the SpecRunner again, and now the test passes. This was just intended to be a quick introduction to Jasmine in case you weren't familiar with it. There are many other comparisons you can perform, many of which are listed here. To learn more about Jasmine, please consult the documentation at jasmine.github.io.
Testing RequireJS Modules
Now that we know enough about Jasmine to be dangerous, let's jump back to our to-do task list and test one of our AMD modules. The tasks module looks like a good place to start. The very first function here, add, calls taskRenderer.renderNew. Okay great, so it has a dependency on another module and makes a call to that module. We can test that with a Jasmine spy, right? Let's start by making a new test spec file for this module. We can name the file tasksModule.spec.js and save it into the spec directory. Also remember to edit SpecRunner.html and include this spec.js file in the JavaScript includes. We also need to include RequireJS itself. Now, let's add a describe for this module and also for the thing we want to test, which is the add function. And, finally, we'll add the test. Now we need to call add on the tasks module. But, wait. We don't have the tasks module here anywhere, do we? We need to have RequireJS load the module for us before we can actually test it. You know how to have Require load a module, right? If you guessed with a require function, then you're correct. Now we need to use a spy to ensure that the renderNew function was called on the taskRenderer module, but we don't have that module here either. Well, we could just add it as a module to import in the call to require, but really we don't care about the actual implementation of taskRenderer. We aren't testing that module, so we shouldn't have to load the whole thing. Instead, we really just want a mock implementation of taskRenderer that we can hand to the tasks module. Remember how RequireJS resolves modules. If a module by a given name is already defined in Require's module cache, then it will just use that cached module instead of reloading it. So, we can just define a taskRenderer module before we call Require to load the tasks module. When the tasks module then tells RequireJS that it depends on taskRenderer, RequireJS will hand it the one that we defined in our test. You will notice that in tasks.js, the module name it depends on is renderer/taskRenderer, so let's define that module. Do you remember how to do that? We use the define function with three parameters, the first one being the module name. I added a renderNew function here because we want the tasks module to have something to call, and we also need to spy on it. So, now the tasks module will get loaded, and RequireJS will give it our mocked implementation of the taskRenderer module. Now, let's set up a spy on the renderNew function and test that it was called. To do that, we can also have RequireJS hand us back the mocked taskRenderer module by adding it to the dependency array in the call to require. If we try to run this test, we'll get an error trying to load the tasks module. Remember how RequireJS builds a URL to load a module. If no base URL is specified in the configuration, it will use the path of the HTML file that was loading RequireJS. In this case, that file is our SpecRunner.html file. This means that our base URL will not be the js directory as it was when we normally run the application from index.html. We need to pass some configuration to RequireJS that is specific to our tests. We previously had all our configuration in the main.js file. It would be nice to be able to reuse this configuration for testing too. Instead of putting the configuration here, let's move it into its own file where it can be referenced by both the real application and the tests. To do this, let's make a new file named require-config.js and move the call to require.config into here. We then also need to edit index.html and SpecRunner.html to now reference this file. We can now append more config options with another call to require.config in the SpecRunner.html file and set our base URL to source/js. Well, that should do it, right? Not so fast. Remember what I said about RequireJS complicating testing? One of the reasons was because of the asynchronous loading of modules. The call to require actually returns right away before the modules are done loading. Then the it function would return before the real meat of our test is ever run. Fortunately, Jasmine has a built-in way to handle asynchronous testing. All we need to do is add a parameter named done to the test function that was passed into the it function. When Jasmine sees this done parameter being used, it will pass in a function as the done parameter and then hold the test open until done is called. So, within our require callback after we're done calling our expect statements, we can call done. If done is not called within a few seconds, Jasmine will fail the test automatically. There's one more detail to cover here, and that is what happens when RequireJS fails to load the module. There's actually an optional third parameter that we can pass to require, which is another function. This one will be called when there's a module load error instead of the callback function past the second parameter. When this function is called, we need to tell Jasmine that the test fails. We can do this by calling done.error and passing in the reason for the failure, which will be reported back in the SpecRunner. Great! Now we can load the SpecRunner.html file in the browser and make sure our test passes. So far, so good. But there's actually a huge problem here. Remember what I said the second big hurtle is to overcome when testing with Require? Let's add a different sets of tests, and it should make it obvious. What we'll do here is have one test that defines a module named thing and pass if that module then exists. Then we have a second test after it that does not define a module named thing and expects it to be undefined. So, before you reload the SpecRunner to get this test, do you think it will pass or will it fail? The should exist test is fairly obvious because we define the module then get it right back from require. In the second should not exist, we don't define the thing module, but require tries to load it anyway. So, that should pass, right? The test actually fails. It expects the module to not be defined, but it is defined. Why? Well, it is actually still defined and cached in RequireJS's module cache from when it ran the first test. This means that our tests are actually leaking state, which is really bad. Our tests have become interdependent and order-dependent, so one test can affect another. What we need to do is clear out the internal RequireJS module cache between each test. In Jasmine, we can call the functions named beforeEach and afterEach inside any describe function. The functions passed in will be called before and after each individual test. So, this is how we can perform some test setup and cleanup for each test. Alright, now this is where things get a little more complicated. RequireJS actually has another configuration option that you can pass to require.config that I didn't cover back in the previous module. This config setting is named context, and it should be set to a string. This is a special config setting that you can pass to require.config that will tell it to make a new context with a given name. Each context loads modules into that context's cache. The call to config returns the require function that we can use to request modules, but it is wired up to request modules from this new context. So, what we want to do in our beforeEach function is to set up a new context for our modules. Then our test can use the testRequire function instead of require. So far, so good. But there's another issue here. When we call require.config with a new context name, it does not pick up any existing configuration options from the default context. We need to manually copy all the options over to this new context. This means that we need to get to the default context. Require exposes a property named s where you can get to some of the internal stuff of RequireJS. Require.s.contexts is an object that contains all the contexts. The default context is named underscore. That means we can get to the default contexts configuration with requirejs.s.contexts._.config. Then we can copy over every property to the new contexts config. Alright, that's one more obstacle overcome. Just one left to go. Now we can clean up this new context. We can do this by just deleting it from the context from require.s.contexts in the afterEach function and setting our testRequire function to undefined. Now, we can finally run our tests and see that they've passed. This is a lot of hassle to go through to test with Require. I went through all these steps just so you could get a clear understanding of what RequireJS is doing internally with different contexts. In a real project, I wouldn't go through setting all this up on my own. Instead, I would use another tool called Squire to do this setup and teardown for me.
Squire
Squire is an open-source tool for managing RequireJS's contexts and injecting mocks into the context for us. It will replace all the code in the beforeEach and afterEach that we just added. You can download Squire from GitHub at github.com/iammerrick/Squire.js. You really only need to download one file, and that's in the source directory and is called Squire.js. I'm going to save this file into the lib directory of our task list project, but you could save it to wherever it makes sense to you and fits the layout of your project. Squire itself is actually an AMD module itself, so we will load it into our test spec using Require. Wherever you save Squire.js to, it is unlikely to be in the base URL directory, so let's add a path to the require configuration so that it knows where to load Squire from. In our case, that would be ../../lib/Squire. Now in the beforeEach that runs before each of our tests, we want to make a new instance of Squire. Since Squire is an AMD module, we need to use RequireJS to load it by making a call to require. Remember that this loading is asynchronous. So, to handle that, we add the parameter done to the functions passed to the beforeEach and call done after Squire has been loaded and we create an instance. We will save this instance into a variable named injector. The Squire constructor will create a new RequireJS context for us and also copy all the configuration options from the default context. This replaces all the setup code we had to do ourselves previously with just this one line. We will also need to tell Squire to clean up the RequireJS context when the test is complete. So, in the afterEach function, we call injector.remove. This will delete the RequireJS context that was created for the test. Previously, to set up a mock version of the taskRenderer module, we had to make a call to define. When using Squire, it provides a function named mock that we can use instead. Injector.mock takes two arguments. The first is the string name of the module. The second is the mock object instance to return when this module is required. This code will define the renderers/taskRenderer module in the RequireJS context that was created by Squire. Finally, instead of using the require function from the global scope to load our modules for the test, we instead use injector.require. This gives us the special require function that is bound to our test context that contains the mocked objects. Now, we can run the SpecRunner.html file again in the browser and verify that our test still passes. Using Squire is a lot easier than setting up and managing RequireJS contexts ourselves. There're some other nice features provided by Squire, so I encourage you to check out the documentation on GitHub.
Summary
In this module, we focused on unit testing our application. We had a brief introduction to the Jasmine test framework and discovered that Jasmine 2 has support for asynchronous testing. We also learned that internally RequireJS can manage several contexts. Each context maintains its own configuration settings and module cache. Contexts are created using the special context setting when calling require.config. AMD modules are a nice way to isolate code into single testable responsibilities, but RequireJS makes testing a little more difficult. This is due to asynchronous module loading, which we overcame with Jasmine 2's asynch support using the done function, and also due to RequireJS caching loaded modules, which we overcame with RequireJS contexts and Squire. Setting up a proper test environment with Jasmine, Require, and Squire can be a bit of a hassle, but in the end, it's well worth it to be able to test your code. I encourage you to go through the exercise of setting up a test environment at least once and save it off somewhere so that you can replicate it later. This would save you the time of going through all these steps each time you make a new project.
Conclusion
By now, you should feel comfortable enough with RequireJS to start using it in your own projects. It's a small library and really isn't all that complicated once you learn the basics. There're really only three functions you typically use, which are define, require, and config. The hardest part is setting up the configuration correctly and understanding where RequireJS will attempt to load modules from. It is definitely worth the benefits of having smaller code modules, allowing you to adhere to the single responsibility principle, and managing your dependencies, allowing you to adhere to the dependency inversion principle. In practice, you'll almost always use remote modules since this allows us to break things into multiple files at development time, then combine them into a single file using the r.js optimizer for production, resulting in faster load times and less server requests at runtime. As you get more comfortable with RequireJS, you can make use of the plugins, especially the text plugin, to load more than just JavaScript. RequireJS when combined with the r.js optimizer is great for single-page applications and loading views and templates dynamically. And of course I encourage you to unit test your code. I know it was the last module in this course, but it should really be one of the first steps you take in setting up a new project. Back at the very beginning of this course, I mentioned that the task list sample application could also log actions that the user took. As an exercise for you to get more comfortable with RequireJS, I encourage you to download the sample code from Pluralsight.com and try to add the logging features to the application yourself. To give you a hint, the output of the log HTML elements is a lot like the task list HTML elements. So you can make a logRenderer module that acts just like the taskRenderer. I hope you enjoyed this course on RequireJS and see the benefits of using it in your next application. My name is Jeff Valore, and you can follow me on Twitter at CodingWithSpike. Thanks for watching.
Course author
Jeff Valore
Jeff has over 15 years of experience in software development using Java, C#, JavaScript, CoffeeScript, and TypeScript. His belief that clean, well organized code is key to making software...
Course info
LevelIntermediate
Rating
(419)
My rating
Duration1h 57m
Released13 Jan 2015
Share course