JavaScript Asynchronous Module Definition (AMD) Explained
-
Introduction
Introduction
Take your JavaScript skills to the next level with asynchronous module definition. I'm Kevin Murray and I've made my share of large JavaScript libraries. With each new project I promised myself I would keep my libraries organized into smaller, related functions to make them more manageable. It wasn't until I finally adopted the patterns of AMD that I was able to truly achieve that goal. Large JavaScript libraries become difficult to train, explain, and maintain. I've gained a lot from transitioning to modular patterns and that's why I'm excited to present this information to you. I realize that we're essentially doing a code review with this course. As I switch between the editor and the browser, I'll include informational slides to help explain key points and keep things interesting. The samples in this course are progressive in nature, meaning the later samples build upon the earlier ones. It's possible to skip around, but the course is intended to be watched in chapter order. If you're the type of learner that skims the table of contents and skips to a specific topic, you can still benefit from this course, though there might be some things you want to rewind for. The tools that are needed to follow along with this course are: RequireJS, jQuery, a browser, and a text editor. I also use Jasmine for unit testing the JavaScript code. I'll be using version 2.1.22 of RequireJS for module management. You can download RequireJS from requires.org. I use Notepad++ as my editor, but any text editor will do. If you like what you see in the course and want to try it out, you can find it at notepad-plus-plus.org. The samples use simple HTML and JavaScript files. I use Firefox for demonstration because it will interpret and process files stored locally. Other browsers can also do this, but they may need to be configured to allow this capability. I chose this technique to prevent the complication of deploying to a web server for this course. I've also decided to let a couple of students audit this course. Harold and Doug are some programmers I know pretty well. They'll sit in the back and sometimes jump in with questions or observations. You may find that they ask the very questions that you have. This course is laid out in six chapters. This introduction chapter is used to review traditional web design. I use a simple website to mimic the kind of projects you see every day. We'll use this project as a basis for progressive enhancements. In the second chapter we'll build a very simple AMD module that we'll also make use of throughout the rest of the course. Once we have that module, we'll start converting the legacy code that was introduced in chapter 1. The third chapter looks at using modules to hold static values. I'll also talk about using third-party libraries in a modular manner. I haven't always been a fan of unit testing, but Jasmine changed my mind. So I'll show how to use Jasmine to unit test modules. I even make Jasmine run in a modular manner. Chapter 4 is where I cover configuration options for RequireJS. I don't cover every option. Just some that are commonly used and often misunderstood. The sample project finally gets organized in chapter 5. I integrate a user interface library and show ways to structure a complex project across multiple folders. Chapter 6 is kind of a bonus chapter. I use the RequireJS optimizer to bundle the sample project into a much smaller package suitable for distribution. Let's jump right into our baseline web page.
-
Baseline Project
I want to make this as interesting as possible so I pulled some code from projects I previously worked on. I figured this would be much more engaging than a standard Hello World page or yet another to-do manager. The building blocks are the exact things you already use in your own projects. Once we make sure everything is working with our basic code, we'll quickly make a couple of simple changes to set up for testing the migration to modules. Let's take a look at a typical web page. There are no big surprises here. It's the same kind of code you see every day while working on web projects. We have some CSS files for a toolbar and for the project, which are included first. Then jQuery and the toolbar library are included next. There is some embedded JavaScript on the page that runs once the page is loaded. The $ is a reference to the jQuery library. Now if you're not familiar with jQuery, Pluralsight has plenty of courses to help you gain proficiency with it. The pattern you see here is very common and is a shorthand syntax for a document ready function. Basically, it waits until the document has finished loading into the browser, then this function will process. The only thing we do in this block of code is call a function in the toolbar library and pass it three buttons to show: save, search, and publish. It doesn't matter what these buttons represent. We just want to be able to see something interactive on the screen. After the script, we see the rest of the page, which includes content that describes the expectations of this sample page. We'll continue to use this pattern throughout the course. I'll increment the sample number for each iteration we go through. This will be helpful if you need to rewind the course to a certain spot. Let's see this page in the browser. The toolbar is there. As I move the mouse pointer over the buttons, we see the background color changes for each one. If I hover over a button long enough, we'll see a tool tip appear. Everything seems to be working as we expect. The toolbar library provides default titles for each of the buttons, but we can override this functionality. Let's modify the code and provide our own titles. While this demo isn't about the toolbar, this will serve as a step towards making use of another embedded library. In this code we've added our own tool tip text to each of the buttons. To prove the titles came from this code change, we've added text in parentheses to indicate that the title came from our code, not the toolbar library. The page is now called sample 2 and the page contents have been updated to reflect what we're expecting. When we switch to the browser and run this page we see the same result as before, but now we should see different titles when we hover the mouse over the buttons. This shows that the toolbar library reacts properly to parameter changes. Bear with me for just one more change. We'll add another library that works with the toolbar. In this code we've added a language translation library. The purpose of this library is to translate user-facing text. We're going to make use of it to translate the tool tips we just added. We've included the language library just below the toolbar library. This implies a dependent order, but it's not really clear if there is an actual dependency. We'll take a look at that in a moment. We've got some modifications to each of the tool tips to support translation. The translation library will automatically convert text based on an ID and it looks for a very specific syntax. If none of this makes sense, don't worry. We're not trying to learn about language translation. The point of these changes is, the toolbar title should be in a different language, but only if we tell it what language we want to make use of. We've got one line of code above the toolbar logic to indicate that we want to load the French translations for this web page. We'll take a closer look at the toolbar and language libraries in the next chapter. For now, let's just let them do their job and take a look at the results in our browser. When we run the latest version we see no real difference in the look of the page; however, we should see toolbar tool tips in French. As I hover the mouse, we see that we do in fact get tool tips in French. Okay, great! We've talked about how to do things you already know all about, but let's review what you probably just take for granted. We're using the global namespace. If we took a look at the code for the toolbar and language libraries, we would see that they're loaded in a global namespace called _KSM. This is a snippet from the toolbar library that handles loading in that global namespace. That's one of the things we need to get away from when making use of asynchronous modules. We don't want to have our objects or functions stored globally. Some call this polluting the global namespace. I don't often agree with Harold, but in this case, I do. Remember when I suggested the order of the script tags might be important? Let's test that theory and change the order to include the language library before the toolbar library. Let's see what the toolbar does now. As a matter of fact, the toolbar doesn't display at all. If we used a browser debugging console, we would see that there are two errors in the code. The first one would say the _KSM namespace is not defined. The second error would say that _KSM.language.load is not a function. If there's no _KSM namespace than anything we expect to find within it will not be found. With traditional JavaScript libraries the order they are loaded can be critical. Okay, if you're saying the library shouldn't have that problem, you're absolutely right. They should be written better and not break each other when loaded out of order. How many projects have you worked on that didn't have this problem, though? As libraries become more interdependent, the order in which they're loaded can become problematic. This is something that modules can help address. No matter how much you may try to avoid it, problems can and do arise with large projects that make use of multiple interconnected libraries. New team members may not be correctly trained on the proper sequence to load files. Additions or changes to third-party libraries can cause serious headaches involving dependencies. And sometimes just the evolution of code or even refactoring can cause dependencies to break. Oftentimes even unit testing won't uncover problems of this nature. That's because the unit testing page will load the libraries in the expected order and that order may not match the order of the libraries on the production page.
-
Unit Testing
Speaking of unit testing, let's talk about that for just a bit. There are numerous options available for unit testing your JavaScript code. As an example, and to set the stage for an upcoming learning opportunity, I'll show you some unit tests for the toolbar library. I decided to use Jasmine to create unit tests for this page. If you aren't familiar with Jasmine, don't worry. I'll cover the basics enough to get you through this course. If you're interested in learning Jasmine in greater depth, check the Pluralsight catalog. This is the Jasmine test page I'll use to test the toolbar library. Jasmine uses the term spec when talking about tests. This is called a spec runner page. We include a CSS file for the Jasmine user interface. We then include three files for Jasmine. I'm using version 2.4.1, which is the latest version as of the creation of this course. Next we include the same files we did on our sample page and in the proper order. That means as of right now our unit test page does not match our production code. Remember, we left our code in a state of error. Think about that for just a moment. This means that unit testing can give false results if libraries are loaded in an unexpected order. Finally, we include the spec file or files for all the tests we want to run. In this case I'm just running tests for the toolbar. When we open this page in our browser, we see the results. Green means we passed the test. I like the way Jasmine organizes the test results. Notice how the narrative of the tests actually serves to document the expected behavior of the toolbar library. Every test within the grouping can be read as a single narrative. For instance, the KSM toolbar library when called with button parameters, will create the search button with with the proper default title. It's kind of longwinded, but it leaves no doubt as to what the functionality should be. I'm using a subset of the toolbar library I've actually used in production code. These are just a few of the tests I actually used for my toolbar library. I do want to point out a few things before moving on. Notice how the toolbar is expected to exist in the _KSM namespace. That means the _KSM namespace is expected to already be defined. We're counting on some process to define the global namespace so any JavaScript library can have access to it including our test specs. Again, here's the snippet from the toolbar library that does this. As we move to asynchronous modules, we don't want to use global variables. The whole purpose of the _KSM namespace was to act as a container for related functionality. Modules perform this task very nicely, therefore we'll have no further need of the _KSM global object. There may be numerous references in your legacy code to a global namespace, however. Refactoring code and test specs can take extra effort and potentially introduce new errors. In the next chapter I'll show you some simple techniques to avoid both of these problems and make migrating to modules much easier. Another thing I want to point out is the tests mention coupling the toolbar library to the translation library. Coupling is really just another word for dependency. We haven't done anything to specifically link our libraries. We just load the language library in the proper order and make a call to load the desired language. Dependencies like this are not clear and are a common failure point in projects. As we saw in our sample code, this coupling is fragile. The order of the library inclusion is important. If I change the order of the libraries in our unit testing page, we will actually see an error. Let's move the language library above the toolbar library and verify that. In this case, unit testing exposed the fact that the order in which the libraries are loaded is important. The error message may not be explicit about the problem, and the ton of trace dump messages aren't very explanatory either, but at least the unit tests are pointing to a problem. Fortunately for us, we know what we recently changed, therefore we know how to change it back. There's one final thing I want to mention. Look at this code in our spec runner page. We're including some Jasmine libraries and script tags. Is the order that we load them important, too? We saw that our own libraries must be loaded in a specific order, so what's a fair question? Since we're moving our own code to modules, wouldn't it be nice to have Jasmine loaded as modules, too? That way we wouldn't have to worry about what files need to be included to run Jasmine tests or what order they need to be in. I'll show you how to do that later on, so stay tuned. Thanks for sticking with me through the legacy code. We created a simple website to use for enhancements that are coming up. We were also reminded of the fragility of library dependencies and how a breakage can occur when they're loaded out of order. Unit testing is important. As we migrate our code to use modules we'll have to make sure our unit tests still work. Speaking of using modules, it's now time to write our first AMD module.
-
Creating Modules
Creating a Footer Module
It's time to start writing some modular code. We're going to abandon our sample code that uses the toolbar library for just a moment and use a very simple HTML page design. Our first module will be one that adds a footer to the page automatically. I realize this isn't the most useful example, but we want to start simple and have something that provides a visible result. We'll also be able to use the footer module throughout the rest of this course. Once we have the footer module completed, we'll start refactoring our legacy libraries into modules. We'll convert the language library first. Then we'll convert the toolbar library. There is really no need to understand the code within these libraries for this course. We're just covering techniques used for converting any existing legacy library of JavaScript functions. Let's get going with the footer module. We'll look at the actual code first. The code is very simple and contains just a single statement within a callback function. The statement is a jQuery chain of actions formatted across multiple lines. We're using the require function that's provided by RequireJS. The syntax we're using here can be read as, when jQuery is loaded, run this callback function. In this case, jQuery is the only dependency we have, but we could include as many dependencies as are necessary in the array. The parameters to the callback function will be references to any dependent libraries in the same order as they were specified. Since we only have one dependency, we only have one parameter. I'll talk more about the callback parameters when we make the toolbar into a module. For now we want to keep moving through this first module. I'm using jQuery to do the following tasks that are chained together. Create an in-memory paragraph tag, give it a class of footer, give it some HTML content, then insert it after any elements that contain the page class. In the sample page we're going to use, we will only have one div with the class of page so we should only see one footer. For a more complicated or real life page, it would be more appropriate to use an ID to uniquely identify the div we want to use. That's it. That's our first module. The question now is, how do we include it in our page? Here's our sample page that will make use of the footer module. Notice that we only have one script tag and that loads RequireJS. RequireJS uses a special attribute on the script tag called data-main. This is used to indicate what module should be loaded as the main module to start the processing for the page. In our case, we only have one module, the footer so we include that in the attribute. A more common approach is to have a separate startup module which would then load the footer. I'll talk more about this is in a little bit. Did you notice that there is no other JavaScript on this page? One of the things I like best about using modules is the separation of JavaScript from the HTML page. There's no need for a document-ready function or any code at all in the HTML. RequireJS not only loads our JavaScript, it also gives us the ability to define a callback function that will execute when the dependent file is loaded. Whatever file is specified in the data-main attribute will be loaded and processed once RequireJS is loaded. This behavior replaces the need for the document-ready function in our main HTML page. Let's see the result in the browser. As expected, we don't have a toolbar because we didn't include that library. We do, however, have our new footer. RequireJS loaded our module and we got the footer that we expected. In all the excitement of making the module, one key item may have escaped your attention. Let's look back at the HTML page again. Do you see it? Actually, the better question is what don't you see? Our footer module makes use of jQuery, but we didn't have to include a link to it in the HTML page. There is no script tag that loads jQuery. RequireJS handles loading jQuery for us and only when we need it to. That means we don't have to have a bunch of script tags in our main page that loads all the libraries we may need in the website. RequireJS handles loading what is needed and only when it's needed. Now that we have a footer module, we'll use it in all our future samples. That means it's time to refactor some legacy code.
-
Introduction to RequireJS
We're going to start by refreshing our memory where we left off from chapter 1. Then we'll rewrite the HTML page to make use of modules instead of using embedded script tags. We use RequireJS to call our footer module directly. We'll create a better startup file and make use of that to load our libraries and the footer. Then we'll refactor our legacy code into modules. We'll start with the language library and then do the toolbar library. This gives us a chance to look at some very common issues when refactoring legacy code. When we're all done, we'll see it in action. Let's take a look at where we left off in our last sample from chapter 1. We had three JavaScript libraries loaded by the browser with script tags. jQuery, the toolbar, and the language libraries. We're going to use RequireJS to take over that task for us. Here's our new HTML file that will make use of modules instead of script tags. We replaced the three script tags with a single script that loads RequireJS. Once RequireJS is loaded into the browser, it will inspect the script tag that loaded it and look for the specific attribute called data-main. The data-main attribute specifies the starting script to run for our website. Previously we just loaded the footer directly with this attribute. Now we want to use the attribute to load some startup code. As I said before, the startup script takes the place of the document-ready function that is commonly used in websites. Let's talk about that for a moment. You're probably accustomed to having a document-ready function as your starting point for code, but what is really happening before that executes? The document-ready function isn't called until all code files that are included in script tags have loaded into memory. The browser loads and evaluates the script files in the order they appear on the page. Think about what that means. Based on what's being loaded, the browser may have a lot of processing to accomplish while loading script files. Some JavaScript files load functions that are self-executing so they run as soon as they're loaded. Some just populate variables that are immediately evaluated and ready for subsequent use. And even others implement event handlers that may be triggered in ways that aren't readily apparent. A lot can happen before the document-ready function executes. Some of it may not be necessary until the user interacts with certain elements of the website. Wouldn't it be better to load those scripts only when they're needed or even if they're needed? The data-main attributes lets us move our startup processing into a separate file that is managed by RequireJS. I've instructed RequireJS to use a file called KSM_Start-05. It's a JavaScript file, but I don't have to include the .js extension because it's assumed to be part of the file name. With this in place, we no longer have a document-ready script in this file. Throughout the rest of this course, each startup file will have a number that corresponds to the HTML sample number. We're up to sample 5 so that's why we're using KSM_Start-05. So what's in the startup file? Here it is. Before dealing with the first line, take a look at the body of the code. It's the exact same code that we had in the earlier sample. We load the French translations into the language library and call the toolbar function. It's the same behavior, except now it's wrapped in a module callback function instead of in a jQuery document-ready function. Now let's look at the first line. We're telling RequireJS that this startup module has a dependency on four libraries. jQuery, KSM_ToolbarAMD, KSM_LanguageAMD, and KSM_FooterAMD. Once those have been loaded, a callback function will be processed with three variables for dependent modules. Three variables? I thought we said this code depends on four libraries. It does, but if we have no need for a reference to a dependency, then there is no need to provide a variable for it. The footer module does not return a value that we can use so there's no need to request a reference to it. With the references we do include we'll have access to jQuery through the $ variable as usual and the toolbar library will be referenced by toolbar. Similarly, the language library will be referenced by language. All three of these variables are local to this function. They are not globally accessible. That's worth repeating. References to dependent modules loaded with "require" only have scope within the callback function. Just because a local function variable is used doesn't mean that a global variable doesn't also exist. jQuery is a good example to talk about. In our callback function we use the $ to reference jQuery. We could've called it something else, anything else, like local$. The name we use really doesn't matter. We use the $ due to familiarity and habit. When you consider how JavaScript resolved variable references, it walks up the hierarchy from the line level, through code blocks, function blocks, the current file, then ultimately checks the global namespace. Having the local reference provides rapid resolution of the variable, but it may still actually point to a value that is stored in the global namespace. That's the case with jQuery. Even if we call our local variable local$, it will actually be a reference to the $ variable in the global scope. If this isn't clear to you, that's okay. Just know that when dealing with legacy libraries like jQuery, you may still end up with variables on the global namespace. If you load a true module, however, the local reference will not be available beyond the callback function and will go out of scope when the callback function terminates. Take a look at this file. It defines a fully self-contained module. We have a block of code that is dependent upon four libraries to execute. Once execution of this block of code completes, all three of the function variables will go out of scope. That means that the $ variable will resolve locally instead of globally while in this function. Now that we have this module, will it just work with our existing code? No, but not for the reasons you might think. I'm going to sit on this for a moment and I'll make a confession later on, okay? One thing is for sure, though. The dependency list specifies two files that we haven't created yet, KSM_ToolbarAMD and KSM_LanguageAMD. Those are the modular versions of the legacy libraries. We have two choices. We could refactor the legacy libraries to be modules and this would give us all the benefits of AMD modules that RequireJS provides, or we could configure RequireJS to properly handle the legacy libraries. That gives us the ability to make use of the libraries are they are, but RequireJS will do little more than load them on demand for us. I'll demonstrate the configuration option when I talk about configuring RequireJS. For now, we're going to look at the steps required to refactor a legacy library.
-
Refactoring Language Library
So how do we make changes to jQuery and our personal libraries to work with AMD? Let's talk about jQuery first. Fortunately, jQuery has supported AMD since version 1.7 so we can use that with confidence. The jQuery team has worked hard to make their library play nicely with legacy code as well as AMD code. Although jQuery is still loaded on the global namespace, it works very well with code using RequireJS. You must use version 1.7 of jQuery or later to use it with RequireJS modules. I've had one issue using version 1.7 with modules that required me to upgrade to version 1.8 to resolve. I'll talk about that problem later on. We will have to make some changes to the toolbar and language libraries, though. To avoid file name conflicts, I've renamed the toolbar library to include AMD as a suffix. The language library also has the same suffix. Let's look at the language library first. We start the definition of this module with a function called define. Require? Define? They look like they do the same thing. Why use one over the other? That's a great question and one that deserves considerable discussion. For now you can keep it clear in your mind with this very over-simplified rule of thumb. "Define" is used to establish a module or define it. Once defined, it is retained for future use. The module will be registered internally so that any future code that needs it will have immediate access to it. "Require" is used for code that will only be used once. The code will not be registered internally for future use. The KSM start code, for example, has no need to be called by anything else so we used require. Since we're establishing the language library for use by other code, according to our simplified rule of thumb, we'll use the define function. This tells RequireJS to load the module, register it, and make it available for future use. The language library depends on jQuery so we list that as a dependency. This coding pattern should be familiar to you. We use the same pattern with the startup module and even with the footer module. Just like require can make use of a callback function, define can, too. This callback function behaves in the same manner as we've already seen. It gets a reference to the jQuery library and assigns it to the $ variable. Remember, this variable will go out of scope when the callback function terminates. Since local references will go out of scope when the function terminates, you do not have to worry about any conflicts with globally defined variables. Think about the implications of that for just a moment. One of the first things this allows us to do is isolate new code under test. Let's say I want to see what happens if I use version 3.1 of the jQuery library with the language module. All I have to do is make sure I have the file accessible to RequireJS and reference it in the define function. That's all there is to it. I'm glad you're interested, Harold, but there's nothing to show. When I did this experiment, the toolbar and language libraries performed as expected. jQuery version 3.1.0 worked perfectly with our code. The screen looked and behaved exactly the same as we've seen up to this point so there's nothing to show. Even if an older version of jQuery exists on the global namespace, the locally referenced version will not conflict with it. You can safely test new libraries in this manner without worrying about breaking code in other modules or even traditional JavaScript libraries. I don't know about you, but that's a really big deal to me. Let's change the code back to use the regular version of jQuery before I forget. So we're looking at this new module header, but we're still using our old code in it. Is that a problem? Let's walk through it to see what we've got. The original library made use of a global object called _KSM. That's the first thing we need to discuss. We can't depend on having a global namespace called _KSM anymore. If we have any code that references that object, it will generate an error. The code assumes the namespace exists, but it won't. That's the first thing to consider in our refactoring. Another thing we need to think about is creating some sort of containing object to hold our module contents. RequireJS expects us to return something useable to anything that needs this module. Namely we want to return our library of language functions. In this case I'm using a variable named _KSM. It has the local scope and just happens to have the same name I was using on the global namespace. I could have very easily called this returnValue or library contents, or anything at all. I chose to use the _KSM name because the existing code within this library of functions already references the _KSM namespace. If I decided to use a variable name of returnValue, I would have to make numerous code changes to reference that new name instead of _KSM. I prefer to change legacy code as little as possible so I'm going to use the same name to prevent a bunch of code changes. I know some people don't worry about things like that, but experience has taught me to be very cautious. The first place we reference the _KSM object is right here on the jQuery extend function. The original version of this library used the jQuery extend function to add new properties and functions onto the global _KSM namespace. Remember, that object already existed so this library had to add things to it without erasing what was already there. Since we're using a local variable that starts off as an empty object, there's no real reason to use jQuery extend. We could in fact just define the variable using a single assignment. Why did I use extend instead of using a single assignment? The honest answer is based on my preference of making minimal changes necessary. Even though I'm migrating from the old way to the new modular way, I want to be able to make use of as much existing code as I can. Remember, that code already works. Ideally I can just copy and paste the original code into a new module file. Using the jQuery extend function instead of direct object assignment keeps the modular code more similar to the legacy code. When I work with new things, I prefer to make incremental changes to something that is already working. I'd rather focus on implementing new capabilities instead of debugging an error in something that used to work. Let's scroll through the code a little and take a look at what was pasted in from the old library. You can see why I chose to stay with the _KSM variable name. There's a lot of existing code that calls _KSM.language. By using the same variable name and structure in our local module, we gain the benefit of not having to modify any internal references to this library. Remember, jQuery is resolving these values locally, not against the global scope. If you're writing a module from scratch, you're still going to need a containing object to return as a value to the calling program. You can name it anything you want in that case. Using the same name that used to exist on the global namespace is a very handy technique when migrating legacy code, however. We essentially short circuit the expectation of a global namespace by referencing and object with the local scope. Of course, if your code references window.namespace, that means it's explicitly asking for the global namespace. If you have code that does this, tread carefully. The rest of the code is identical to the function definitions in our legacy library. That bears repeating. We made no code changes to any of the internal functions. We just wrapped the whole library in AMD syntax. The last thing to do is return a reference to the functions so calling programs can access them. I need to mention that you should return a value from a module that uses define. The purpose of having a self-contained module is to allow on-demand usage of the code or objects within. If you don't return something, the calling program will have no way to make use of the module reference. There is a caveat to this, however. I'll cover an alternate syntax for data modules in a little bit. For now, just remember that the purpose of using define is to define a module that returns something. Otherwise just use require. So let's return something. At the bottom of the file, we return a reference to _KSM.language. If you're wondering why I don't just return the _KSM variable, that's a good question. The answer is because all our legacy code references the language portion of the global _KSM object. The calling code doesn't care about the global object; it just needs a reference to the language portion of it. That means all the code that makes use of the legacy language library has to know the organizational structure and location of the library. If we want to change the location of the legacy language library or even rename the _KSM object, all code that references that library would break. Using a module for the language library overcomes these problems. We're making a self-contained module that exists independently of any other code. It can reside anywhere on disk, in memory, or even in a bundle of libraries. As long as RequireJS knows how to find it, our calling program can make use of it without knowledge of structure or location. Let's take a side-by-side look at the before and after versions of this library. This is one of the reasons I like Notepad++ because I can view two files in this manner. I've collapsed the function declarations. As I mentioned, the code is exactly the same in each file. The AMD version is just one line longer since we have to return a variable. The original language lab was wrapped in a self-executing function. It also had a leading semicolon to prevent any problems with code bundlers. Our modular code is contained within a single define function. Now that we're using AMD, we no longer need a starting semicolon to protect our code from bundlers. As you can see, there are really very few differences between the two files. This is a good thing because it makes it easier to migrate legacy libraries to modules, mostly. We'll get into more particulars in a little bit.
-
Refactoring Toolbar Library
Okay, now it's time to look at the toolbar library. We've already seen this pattern in the language module. The key difference here is the fact that this code depends on another module in addition to jQuery. This library has dependencies on jQuery and the language library. They are referenced in the callback function in the same order they are listed in the dependent array. It makes sense to place the reference variables in the same order as the dependencies are listed, but what order should we list the dependencies in? We know that the language library depends on jQuery so should we list jQuery first? The answer is, the order is unimportant. As a matter of fact, due to the whole asynchronous part of the AMD pattern, there is no guarantee that the dependencies will load in any specific order. If both jQuery and the language libraries are dependencies, they can be loaded in any order. Except, we know that the language library also depends on jQuery. When we specify both jQuery and the language library as dependencies, RequireJS will recognize that the language library has a nested dependency on jQuery. If jQuery isn't already loaded, RequireJS will make sure it's loaded before completing the load process for the language library. Complex dependencies can be configured to inform RequireJS about the proper loading sequence. We'll cover that in the chapter on configuring RequireJS. It's easy to get sidetracked when migrating legacy libraries to AMD. You can be so used to a consistent library loading order that you miss implied dependencies when refactoring for modules. Just remember this. The order that asynchronous modules are loaded is unknown unless dependencies are specified. If your libraries depend on being loaded in a certain order, you need to configure those dependencies. Your code might work 99 out of 100 times. Of course, the 100th time will be when you're giving a demonstration to the customer or your boss. RequireJS will load the dependent modules if they're not already in memory and make them available through the variables in the callback function declaration. It's important to pay attention to the order of the variables and the function call and make sure they align to the order that you define in the dependencies. This can be challenging when there are many dependencies. Look at the two references I have here, jQuery and language. There is not much room for confusion there. Imagine though if there were five or six JavaScript libraries that are included. Let's pretend that's the case for just a moment. I'll add three more libraries to illustrate what I'm talking about. Now the list includes routing, expansion, and user values. Never mind what they reference, we're just making a point. Once I add the variable references in the function declaration, the line extends beyond the right edge of the screen. Personally, I prefer to be able to read code by only scrolling up and down. I really dislike having to scroll left and right as well. Most editors have a line wrap feature. I dislike using that with code because where and how it wraps depends on the size of my editor window and my font. So let's move the function declaration down a line and take a look at that. Now at least we can see the libraries and references at the same time. I've seen people align their libraries and variables on tab stops to make it easier to understand the pairing. They format the code something like this. Notice that the variable name can be different than the library name. This should've already been apparent since the jQuery library is referenced by the $ variable. I've discovered great value in consistent naming conventions across multiple modules. While it's not a requirement for the browser, it sure makes code more maintainable. However you format your code, just be sure to match the variable order to the dependency order. That's what I'm really trying to stress for now. Callback function variables are presented in the same order as they're listed in the dependency array. Here's something to ponder. Since the toolbar library depends on the language library and the language library depends on jQuery, do we even need to include jQuery as a dependency of the toolbar library? Shouldn't jQuery already be loaded by the time we get into the code of the toolbar? Even if it's loaded, how do we reference the jQuery library in the callback function? Will the $ variable be accessible? It's a good exercise to try on your own. If you're following along with the exercise files, you might want to give it a try. Okay, enough about that. I'll remove the demonstration module references, then we'll move on. Within the callback function we're creating a local variable called _KSM. This is the same pattern we used in the language library earlier; however, this time instead of defining it as an empty object, we're placing a reference to the language library within. Why? Somewhere within this code file there are references to _KSM.language.functions. Remember that the language module returns a reference to itself as part of the module pattern. RequireJS gives us access to that reference through a callback function parameter. I could very easily modify the code to just use the language variable passed to this function, but that introduces risk into already tested code. In the legacy library there were a few lines of code that made sure the _KSM namespace existed and that it had a default context property. We had the same default context property in this assignment as well. I'm going to show you a good way to handle configuration options like this in a little bit. Just as before, I make use of the jQuery extend function to add the toolbar properties and functions to the locally defined _KSM object. I like to be consistent in my coding practices. This brings up a sideline point. Along with consistent naming conventions, it's also important to adopt repeatable coding patterns. This makes it much easier to move between modules and understand what's going on. Since I made use of the jQuery extend function in the previous module, there is no surprise when we find it here. I don't know about you, but I prefer to keep surprises far away from maintaining code. As we scroll through the functions, notice that there's no need to change any code since we made a locally defined _KSM object. We see many references to _KSM.toolbar and even a reference to _KSM.language. Everything stays just the same as before. Keep in mind though, we may have hidden errors since we can no longer count on configuration settings that used to exist in the global _KSM object. I only see one other reference to the _KSM object that we didn't address. Here's a code block that checks to see if there's an event handler present. If so, it executes some additional code that presumably requires the event handler to exist. We don't have an event handler in our internal object so this code block will never run. Is that a problem? Whether it needs to run is a matter of code requirements. Just because we have a safety net to prevent a code error doesn't mean there isn't some downstream process that depends on this event handler being present. At a minimum this would require a code review to see if we've introduced a fundamental breakage in the behavior of our system. As you move from legacy coding patterns to module patterns, you'll encounter many opportunities to dust off your code and review your expectations. Of course, deadlines and requirements for new functionality make that difficult to accomplish. I did want to pause and talk about this if for no other reason than to let you know that I realize moving to modules isn't just a matter of wrapping legacy code in a modular syntax. Making a new module can be pretty easy. Retrofitting legacy code into a modular pattern can be challenging. It can take some effort to understand how the legacy code is assembled and configured to make sure a new modular pattern doesn't introduce unforeseen consequences. Okay, that's all the module coding we need to do. Our page should behave just like it did before. Let's test it and see it in action. Here we see the output of our sample 5 HTML file. As we hover over the toolbar buttons, we see the French translations for each of the tool tips. Our language module is working perfectly within the toolbar module. Let's request a different language so we can see different behavior. What file should be open to make that change? We want to change the way the HTML page behaves. If you remembered that we're running sample 5 HTML in the browser and you think that's the file to change, I got you. As you move from legacy code to modules, you'll find yourself opening the HTML files by habit. It only takes a few times before you completely disregard the HTML file and reach for the startup file first. When we open the KSM_Start-5 file, we see the reference to French in the language load function. Let's change it to Italian. The code for that is IT. We'll save the file, then refresh our page. As we hover over the toolbar, we see the tool tips are in fact in English, even though we requested Italian. That's because we only have French and English in our language module for this demonstration. If a language is requested that is not recognized, the language translation routine will default to English. The pattern for adding languages is very easy to duplicate so feel free to expand the functions and add a language of your choice as a homework assignment. We were just testing to see if a change in the language reference would affect a change in behavior. It did, so everything is great. Look at all the literal string values in our code. Wouldn't it be great to have those specified in a configuration file somewhere so we can make changes to the configuration without having to open up code? We'll cover that in the next chapter. This chapter was all about creating modules. We created a new module to generate a footer on the page. It was very simple, but fully functional. We covered how to use a startup code file instead of using a jQuery document ready function. Then we modified the language and toolbar libraries into modules and made use of them in the startup code. I'm sure you've got plenty of ideas of how to relate what you've learned to your own legacy libraries. Now it's time to look at even more module patterns, including how to unit test modules.
-
Alternate Module Patterns
Creating Modules
Let's take a look at some alternate modular patterns and do some modular unit testing while we're at it. Another popular use for modules is to use them for common data storage. We'll take a look at that first. Unit tests need to be adjusted when working with modules. We'll use Jasmine to unit test the toolbar module we worked with in the previous chapter. It's no more difficult than using any other modular pattern. What can be challenging, however, is using a legacy library in a modular manner. Since we use Jasmine to unit test our module, we'll also use it as our sample legacy library to be used modularly. Let's take a moment to talk about a different way to use modules. The pattern for our modules thus far is to use define or require to specify a module format. We pass in one or more dependencies in an array and finally include a callback function with variables to reference those dependencies. We can tell RequireJS that we're dealing with data values by just passing an object value as the only parameter to define. In this example we're defining a module that will contain three string literals. When we include this module in the dependency list for other modules, we will have access to an object with each of these values. Notice how we don't have a return value. RequireJS will return the entire data structure when this module is referenced. There are numerous benefits to this approach. Of course, the most obvious is centralized definition of common values. If we have a common value that is used throughout a library or even a group of libraries, it is very handy to be able to define it in one place for easier future changes if necessary. Historically, the global namespace is used to store this kind of information. We don't have to use that technique any more. Another benefit is our code should be more readable if we're using descriptive names for the values. I'm sure you're aware that conditional blocks like if and switch are easier to read and understand when using descriptive constant names instead of hard-coded literals. Code reviews become much less contentious because the code is more readable by others and there is no need for magic numbers. During code reviews, magic numbers are any literal values used in conditionals. To read more about magic numbers, search Wikipedia for Magic_number_programming. Over the years I've heard people complain about JavaScript for numerous reasons. A common complaint is the lack of support for include files or constant values like other languages have. A data module addresses that issue quite nicely. Finally, and probably my personal favorite is that data is consistent between test and production sites. I can't tell you how many times I've seen code behave differently between a developer's machine and a production site. Do you remember in the first chapter? We had a point where our production code was different than our test code because of the different loading order of our JavaScript libraries. Not only does the order of the files need to be the same, but the contents need to be the same, too. When using data modules, the first thing I check is to make sure the production contents are the same as the development contents. Let's create our own data module. Here's a simple data module that includes the hard-coded values that we previously used in the KSM start file. Instead of having the literal strings in the start file, we can just reference values that are contained in this module. Notice how we can use any data type we want, not just strings. The toolbar buttons value is an array of object values. Let's go make use of this module in the start file. We've included the KSM_Config file as the first dependency. As we've already discussed, the order of the dependencies isn't important, as long as any referencing variables are in the same order as the dependencies. We're using a variable name of config to reference the configuration data so now we can specify a language load using config.language abbreviation instead of hard-coding FR for French. This happens to be the only place we're using the language code, but if we were using it within multiple modules, every one of them would automatically change when we updated the configuration file. We call the toolbar function passing the default context and toolbar buttons that are stored in the configuration module. Simple, right? Let's see it in action in the browser. As you can see, everything is still working as we expect, including the toolbar tool tips showing in French. Now we can test our web page by changing one configuration file instead of making actual code changes. Speaking of testing, what about unit testing modules? Let's take a look at that next.
-
Unit Testing Modules
I'm going to continue to use Jasmine for unit testing. I realize this course isn't about Jasmine, but this gives us a chance to talk about a third-party library that isn't written to use modules. If we can make Jasmine work with our own modules then we're better prepared to use modules in an integrated production environment. If you're familiar with a different testing tool, the concepts should be similar. Let's take a look at what the spec runner looks like without modules. This is the file we saw in chapter 1. In order to run tests, we need to do the following things. Load the Jasmine core files, load any dependent libraries, in this case jQuery, load the files to be tested, then load the actual tests, or specs. Jasmine, like many third-party libraries, establishes a global namespace for containment of all related functions and properties. It's one thing to wrap module syntax around our own files; it's altogether different to do it with a third-party library. If you're inclined to try that approach, I encourage you to avoid the temptation. There is another way. It may seem simple to just make a couple of changes to somebody else's library, but that is risky. As soon as you make a change to a third-party library, you assume complete responsibility for its behavior, even any preexisting errors. Let's use Jasmine as an example of a third-party library that expects to be loaded on the global namespace. This also gives us an opportunity to update our test specs to reference our new modules. Although I haven't been showing all the test specs for every sample we've done, you'll find working tests for all chapters in the exercise files. As you migrate existing code to modules, you may encounter the need to have something defined on the global namespace, perhaps as interim step towards refactoring. Let's take a look at an approach to accomplish that and still make use of our new toolbar and language modules. Here's the spec runner file we'll use to test our modules. Notice that we're including Jasmine files just like we did previously. The only difference is we are now using RequireJS to start our processing in the file called Jasmine-Start-03. This is specified by using the data-main attribute on the script tag. While we look at this file, I need to warn you about something. In this HTML page we're using a script tag to load Jasmine as well as a script tag to load RequireJS. This is really not a good idea. We should let RequireJS load all our JavaScript files so they're centrally managed. Often when we refactor code to use modules, we'll use RequireJS to load the new modules through a startup file, but continue to use script tags to load the legacy libraries just like we're doing with Jasmine on this page. If we're not careful, we can get some pretty frustrating errors. One of the most common errors that developers encounter when migrating to modules is the "define not defined" error. At first glance that doesn't seem very helpful at all, does it? I'll explain what's going on. Look at this sample code. We're trying to load the footer module with a script tag before we load RequireJS with another script tag. When the footer file is loaded, the browser tries to evaluate the script within the file. Remember, the footer is a module with all of its code wrapped in a single function called define. The define function is defined in RequireJS, which is loaded after the footer module. That's why we get the message the define is not defined. It's telling us that we're trying to use a function named define, but it hasn't been established in memory anywhere the browser can find. The error message is actually quite accurate. It just sounds funny because of the name of the define function. Another error that we may encounter is "mismatched anonymous define module." Let's take a look at what can cause that. In this code we're loading the footer module after RequireJS, but we're still loading it with a script tag. This will generate the "mismatched anonymous define module" error. In this case, the define function exists because RequireJS has already been loaded, but we're trying to make use of it incorrectly. Remember that define is used to load and register a module for immediate and future use. When RequireJS loads a module, it uses the file name as the default module name to register it for future use. The browser loaded this file though, not RequireJS. That means the footer module is defined, but without a name. In other words, it's an anonymous module that uses define. RequireJS has no way to find that module since it didn't load it. Both error messages may seem useless on first inspection, but it turns out they accurately describe the problems. Fortunately, the solution to both errors is to use RequireJS to load all the modules and libraries that are used by the system. Use RequireJS to load modules and JavaScript libraries to avoid "define not defined" and "mismatched anonymous define module" errors. The files used by Jasmine don't cause an error when loaded with script tags along with RequireJS. We'll come back to this in a little bit and use RequireJS to load the Jasmine scripts as well. For now, RequireJS is just going to load the Jasmine start file. So what does a Jasmine start file look like? It's really very small. All we need to do is get RequireJS to load the toolbar and language modules along with jQuery. If you're wondering about why jQuery isn't listed as a dependency, it's because we're not directly using jQuery in the Jasmine start code. Take a look at again. We're not actually referencing jQuery in this code. We know that the toolbar library has a dependency on jQuery so RequireJS will make sure it's loaded. As we discussed earlier, it will exist on the global namespace so even if we did reference jQuery in this code, it would still work. That answers the homework assignment I gave in chapter 2. Programmers count on implicit dependencies like this all the time. In my opinion, it's generally not a good idea to exploit shortcuts like this. Using jQuery, without listing it as a dependency, makes the code less clear and may require the next programmer to be a detective to figure out what's going on. It also creates an implied expectation or an unstated dependency. What if the toolbar library is refactored to no longer need jQuery? If that dependency changes, and the test code expected jQuery to exist, it would break and it wouldn't be readily apparent why it broke. I recommend listing all dependencies when they are needed. This will help clearly define what you expect to exist when your callback function executes. Since the Jasmine start code doesn't require jQuery directly, we'll leave it off the dependency list. Once we're in the callback function, we set up the _KSM object within the global namespace. We populate it with the toolbar and language modules. This is the interim step I referenced earlier. As you migrate to modules, you'll encounter some cases that still expect a legacy version of your library. The test specs have not been refactored to make use of our new module. They still expect to find the _KSM object in the global namespace and they expect it to already be populated with the values. When RequireJS loads our toolbar library we'll get a reference to it in the callback parameter. We just place a copy of that value in our object that resides in the global namespace to satisfy the expectations of the test code. Notice the explicit use of the window preface. This is the root namespace for the browser. Whether this is a good idea for your specific project is totally up to you. Keep in mind, I'm only doing this because I haven't refactored my test specs to use the new modular library. I'm using this technique as a bridge between creating modules for new code, but making them accessible on the global namespace for legacy code. Next, we use RequireJS to load jQuery and the test spec file. Since this is where jQuery is needed, I've included it in this dependency list. jQuery should already be loaded because the toolbar library depends on it. We've included it here to document the fact that the test spec makes use of jQuery. Notice how we've nested a require function within another require function. This is perfectly legal and in fact, desirable. This allows us to only load files as we need them. If we have conditional logic flow, we can avoid loading unnecessary files or at least avoid loading seldom used files until they're required. Ha! Get it? Until they're required? Oh well. Bad puns aside, just remember this. Modules can be required from within other modules as part of the logic flow. Notice that we've included the location of the test spec in the module name. Since the tests are in a subdirectory, we need to tell RequireJS where to find the files. Finally, once we're in the callback function after the test spec is loaded, we call a function named window.onload. I don't want to go too deep into Jasmine-specific information so I'll just say that window.onload is the part of the Jasmine product that kicks off the test specs. How come we have to specifically kick off the window.onload function? Just based on the name we should expect that it would execute as soon as the browser has loaded all the files, right? If you follow the logic path, the browser loads the HTML file, which has a reference to RequireJS, which has a reference to Jasmine-Start, which has a reference to the test specs file. RequireJS loads files asynchronously. That's the A in asynchronous module definition. By the time all the files are loaded by RequireJS the window.onload event for the HTML page has long since passed. From the browser's point of view, the onload function was ready to fire as soon as RequireJS was loaded into memory, but before the Jasmine start file was loaded. There's a lot more to this story. Page load events and the order they process can take a lot of discussion, but I'll save it for another time. Let's move on and see the page in action. We see that all our tests specs passed. That means our AMD version of the toolbar library passes the same tests that the legacy version did. And that's good news. We're all done, right? Yes, we could be; however, I'm not satisfied with leaving things in this state. If RequireJS is good enough to load our libraries, can we get it to load the Jasmine libraries, too? That would be a great learning opportunity.
-
Using Legacy Library as Module
Can we get RequireJS to load files that we aren't responsible for? In the case of Jasmine the answer is, yes. I'll show you how to make that happen and it's good practice for dealing with third-party libraries that aren't coded as modules. If you're not interested in Jasmine, this segment should still be interesting because I show how to use a third-party library as a module. If you've ever tried to be completely module oriented in your Jasmine testing and couldn't make it work, the following will be very useful. As we previously discussed, Jasmine expects to be loaded in the global namespace. The specs make use of functions like describe and it to process the unit tests. Jasmine is a comprehensive testing library. We definitely don't want to rework Jasmine code or make any internal changes that we have to own from now on. Our first task is to turn our spec file into a module. This is no different than making any of our JavaScript files into a module. We make use of the define function to load the dependent files. In this case our specs define on jQuery as well as the toolbar and language modules. We get our reference to each of them in the callback function just like we've seen before. We also set up an internal object called _KSM. Just like in our previous modules, we want to be able to make use of any existing legacy spec code. That code expects the _KSM object to be defined and contain a toolbar property and language property. We set up that structure right here in the spec file. Everything else is exactly the same as before. If you compare this file to the previous spec file, you'll see that the only difference in the body of the file is that I added an extra indent to each of the lines since everything is contained within a define function. This file is called KSM_toolbar-04_spec. So we need to change the Jasmine_Start-04 file to load this spec. Once we make that change, let's run our test to make sure everything works with our specs defined in a module. As you can see, everything is still working so far, but we still haven't loaded Jasmine into a module. So let's tackle that next. First of all, this is the point where I decided to upgrade jQuery from version 1.7 to version 1.8. As I was putting this segment together I kept getting strange errors in the Jasmine library. Instead of trying to debug Jasmine, I chose the simpler route of just upgrading. Earlier in the course when I mentioned a strange case for version 1.8 of jQuery, this is what I was talking about. Let's look at our new Jasmine start file. Whoa! What's all that code at the top of the file? It's a bunch of configuration properties to tell RequireJS all about Jasmine and its dependencies. Remember, Jasmine is a third-party library that loads itself onto the global namespace. This is a common practice so we won't fault them for that; however, we want to use Jasmine in a modular manner so we need to tell RequireJS a few things about it. The first section tells RequireJS where to find things. There are three files that we originally used script tags to load. Now we need to tell RequireJS where to find these files and what name we want to use to reference them by. These names are not used as callback parameters. These are just the names that RequireJS will register the libraries under. Remember how we said that define is used to tell RequireJS to load and register a module for future use? This name is used in that registration process. The next section tells RequireJS about the dependencies of the files. In this case, we say that the Jasmine HTML file depends on the core Jasmine library. We also specify that the Jasmine boot file depends on the Jasmine HTML file. All of these files expect Jasmine to be loaded on the global namespace. We included them in our original test page in the order of core, html, and then boot. The boot file is the only one we have to list as a dependency in our testing module because RequireJS will make sure to load all the nested dependent files for us, as described in the shim section. I'll talk more about configuration in the next chapter. For now, rest assured that RequireJS will make sure that Jasmine is loaded and ready for use by the time we're in the callback function. Now all we have to do is load jQuery and our test specs and see what happens. As you can see, I added a new test spec to test if Jasmine was loaded by RequireJS. This page wouldn't display if it hadn't loaded, but I wanted something visible in the browser to verify we're using a new spec file. Now we can unit test our modules with a modular unit tester. We're now using Jasmine in a modular manner. In this chapter we used a data module to hold our static configuration values. This is synonymous with an include file in other languages to hold constant values. We stuck with our unit testing and it's all green light so far. We saw how easy it is to unit test modules. We even loaded Jasmine in a modular fashion without having to change any Jasmine code. There was a hint of how to configure RequireJS in this chapter. The next chapter dives deeper into configuration. Let's get to it.
-
Configuring RequireJS
Using 'baseUrl' and 'paths'
In this chapter, we're going to talk about some configuration options for RequireJS. It doesn't take much effort to look up the configuration options for RequireJS on the internet. There are numerous websites that show up in any internet search for the topic. Unfortunately, most of them seem to rehash the same information that is posted on the official RequireJS site. Here's the sample configuration you'll see posted over and over. This is copied directly from the RequireJS documentation, just like almost every website you'll find online. The only difference here is I've removed the comments that are usually included. If you've seen this sample before and you're still left scratching your head, this chapter is for you. We'll use our previous project files as samples to demonstrate how the configuration options work. We got a hint of configuration in the last chapter, now we'll dig in deeper. There are various options available to configure RequireJS. We'll look at a few of the common properties. Once we make use of configuration properties we can better organize our project files instead of having everything located in one folder. We'll take another look at the very first sample from chapter 1 and we'll see how configuring RequireJS would've prevented us from needing to refactor our legacy libraries. We'll finish up by explaining the often misunderstood shim configuration property. So let's get started. RequireJS calls whatever JavaScript file is referenced in the data-main attribute of the script tag. So far, we've used nothing more than a simple module pattern in that file. Here's the start file from the previous project we worked on. We've seen this enough times that it's getting kind of monotonous, right? We'll take a look at the dependencies list one more time, though. We've taken it for granted that RequireJS would load the dependencies because it could find the dependencies. Our code so far requires a very flat structure with the modules, script files, and start file all in the same folder. Everything resides in the same folder so RequireJS can find it. In the last chapter we saw that we can include the folder location in the file name. We use that pattern when asking RequireJS to load our test specs. Keeping most or all of your files in one folder may be fine for rather small projects, but for a real-life production environment, that usually doesn't work out too well. We may also need to arrange certain files in folders with specific permissions applied, perhaps to restrict access to administrative pages or sensitive data. RequireJS can be configured to indicate the location of our files so they can exist in whatever organizational structure we desire, without having to include the folder location with the file name. At the top of the start file, we call require.config with an object structure that describes the options we want to use. It's important that we make this config call before we first make use of define. I'll get back to this in a moment. Within the configuration, the first thing we specify is the baseUrl property to tell RequireJS where to start looking for files. Any relative paths included in the configuration data use this location as the starting point to locate the files. If the baseUrl property is not included, the location of the HTML page that loaded RequireJS will be used. Let's look at some examples. Using scripts/jquery as a file name tells RequireJS to look in the scripts folder for the file name jquery.js. The scripts folder is expected to exist under the folder containing the HTML page that loaded RequireJS. Using ../common/library as a file name tells RequireJS to look for a file called library.js in the folder that is a sibling to the folder containing the HTML page that loaded RequireJS. The ../ tells RequireJS to move up one level in the folder structure and then we look for the folder called common. Whether RequireJS has permission to read files in those folders is a matter of directory security as configured on your server. Up to this point, all our project files have been in a single folder. That's why RequireJS has worked for us so far. Without a baseUrl property configured, RequireJS looked for and found the files in the same location as the HTML page that loaded RequireJS. Without a baseUrl property configured, RequireJS looks for files in the same location as the HTML page that loads RequireJS. We specify that jQuery is in a scripts folder. Based on this configuration, the scripts folder should be located directly under the folder containing the HTML page. Here we see a list of all the modules we've created. These are stored in a components folder that also exists under the folder containing the HTML page. Using configuration data allows us to have much cleaner file structures in our projects. What about the startup file, though? Where should it reside? It's a script file, but it's also a module. Since it's our startup code, should it be in the same folder as the HTML page? The answer is, it's completely up to you. For me, it feels more like a custom component even though it's the main entry point for the website. I chose to place it in the components folder. It could very easily reside in a different location. Wherever it ends up, it's just a good thing to get our files organized. We will have to make sure the data-main attribute of the RequireJS script tag points to the proper file location, though. Keep in mind, we didn't include a folder along with the start file name, so RequireJS expected to find it in the same folder as the HTML page. When we reorganize our files, we need to be aware of what was just working for us before and make sure we specify folder locations whenever necessary. After we configure RequireJS, we define our starting module just like before. We list the dependencies and include parameters in the callback function as references to those dependencies. Everything is just like we had before. Well, there is a slight difference. Good catch, Harold. I'm glad you're paying attention. Instead of listing the footer module as a dependency, I've referenced it with the standalone call to require. This works just fine since we don't need a reference to the footer module. Remember that the footer module just runs some code and then exits. It doesn't have a return value we can make use of. Okay, so other than that, we define our starting module just like before. Let's look at the results of this file in the browser to make sure our configuration properties are working for our new folder structure. Everything seems to be okay. We can breathe a little easier now that our project is better organized. Before we move on, I want to make sure we're perfectly clear on something I mentioned earlier. Notice that we placed the configuration properties at the top of the file. The call to require.config must come before we make our first require or define call, anywhere in the program. Our code starts processing in this file so we can be sure that this is the entry point for RequireJS. In more complicated environments that may call different start files based on some conditional logic, the configuration logic must be processed first. Configuration properties must be set before RequireJS executes the callback function of the startup code.
-
Placement of Configuration Properties
Placing the configuration logic in the top of the start file like we've done here works in most cases. If you have a project that is extremely complicated, there might actually be more than one entry point. Complicated and dynamic AJAX pages can actually have different entry points based on permissions, URL parameters, or some other business need. It's possible that a different entry point will execute before RequireJS gets to the startup code with the configuration settings. If code in an alternate entry point expects RequireJS to be configured, you will encounter errors. This is an unusual circumstance, but it can happen. As an alternate to placing the configuration data in our startup code file, we can place it in our main HTML page. This page demonstrates how to do that. We're using the same configuration object as before, only this time we don't call require.config. That wouldn't work because RequireJS hasn't been loaded yet. That happens right here. Instead of calling a config function, we just define a variable named require. Once the actual RequireJS library is loaded, the previously defined variable called require will be recognized as a configuration object and made use of during initialization. If we place this code block in our master HTML file, we can count on RequireJS being configured properly regardless of which process actually runs first in the application. Remember though, we only have to do this if our project has multiple entry points. I do have one word of warning, though. Be sure to use the pattern of var require = configuration object, instead of using window.require = configuration object. According to the RequireJS documentation, using window.require can cause problems when using Internet Explorer. Hmmm. Let's take a quick look at the HTML file with embedded configuration code. As we can see, it behaves in exactly the same manner. Placing configuration data in the HTML file is helpful if you have a very complex and dynamic website. Otherwise you should keep it in your startup file. For the rest of our samples we'll be using a configuration block in the startup code. Our processes don't have multiple entry points so this pattern will work just fine for us. Look at the key value pairs for each of our modules. The key holds the module reference name. The value holds the actual location of the module. That means we can create an alias for the module by changing the key. We can use a moniker of footer instead of the actual file name of _KSM_FooterAMD as the key. Then any place we want to call the footer we could just use the generic moniker of footer instead of the formal file name. Why is this even worth mentioning? Why not just keep referencing the module by the file name like we have been so far? Imagine getting a new module that needs to be tested. In traditional environments, rolling out a new module would involve either replacing the module file with something new or changing code to reference the new module. Both of these approaches carry significant risks. We're using the footer module as a simple example, but we could have an extensive set of modules that need to be tested in tandem with each other. File swapping and code changing are very risky indeed. If we use an alias of footer for RequireJS to resolve, we can easily point to a new module by just changing the value of our footer key. If our new footer module is located in a different location, even with a different name, we make a simple configuration change and the new footer is loaded instead of the old. Here's the result of our change. We can now see the results of the new footer module. Of course, with this approach, the new footer gets propagated throughout the application. If we're not really sure that we're ready for our users to see this new functionality yet, we can make one more tweak that will let us present it conditionally. We've got some conditional logic here that will set a footer library based on some variable value. Right now the value is hard coded. If we imagine some logic that checks the query string parameters for our web page, we can use that to allow us to specify the new footer module by adjusting the URL for our testing. Once we're satisfied with the result, we can make the new footer permanent by removing the conditional check at the top of the file. The point I'm trying to make is, using a module alias allows for additional abstraction. Whether this is necessary or even useful will depend on your project environment. At least now we know the option exists. I won't bother showing the results of our latest code. It looks exactly the same as the previous result.
-
Configuring for Legacy Libraries
Now it's time to get something straight. Earlier we went through the exercise of converting our legacy toolbar and language libraries into actual AMD modules. Since this course is about making AMD modules, it seemed like the thing to do, right? Well, RequireJS can be configured to load legacy libraries and completely avoid the need for converting them into modules. We saw a sample of this when we used Jasmine in a modular manner. Keep in mind, we learned a lot about converting legacy code so the previous effort wasn't in vain. However, if you have a need to use a traditional JavaScript file in an AMD environment, RequireJS can handle that for you. Essentially, RequireJS just becomes a script loader for your project. That can be quite beneficial all by itself. Let's take a look at how to configure that. We need to go all the way back to the beginning and make use of our first JavaScript sample. Remember what that looked like? It was a simple web page with a toolbar and tool tips in French. Now that it's gone, I kind of miss the footer. Do you remember the HTML file that produced this? We used multiple script tags to load our libraries in a specific order. That order turned out to be important. Now we're going to use RequireJS to load the files. Earlier we refactored the legacy libraries to use them with RequireJS. This time we're not going to change a single line of code in the legacy libraries. Let's see what our new HTML file looks like. It's exactly what we would expect. We have a single script tag that loads RequireJS and a data-main attribute that specifies an entry point as _KSM_Start-11. Notice that we've also specified a scripts folder that contains RequireJS. We've also placed jQuery in the same folder. Here's the _KSM_Start-11 file that gets loaded by RequireJS. We've already talked about the baseUrl and path properties. Please notice that the toolbar and language libraries are not referencing the AMD versions and they each have a shortened alias. We're using the original legacy versions of these libraries. Next we see that we have the shim property. This is used only for legacy JavaScript files that are loaded by RequireJS. Let me emphasize that. The configuration property shim is only required for legacy non-modular libraries. There is no need to use the shim property when using regular modules. We use the deps property to specify any dependencies the legacy libraries have and RequireJS will make sure to load the libraries in the proper order. In this case we're saying that the toolbar library depends on jQuery. We also say the language library depends on jQuery as well as the toolbar library. Even though jQuery will be loaded when the toolbar is loaded, it's still best to include jQuery as a dependency for the language library. I touched on this in the last chapter. We want to be clear about what the language library depends on in case the toolbar library is refactored in the future to no longer need jQuery. This also demonstrates how easy it is to include multiple libraries in the dependency list. Just separate them by commas. Notice that we're referencing the alias names in the shim section and not the actual file names. Once RequireJS creates a reference name for a library, we use that reference name instead of the file name. That's why we're using toolbar here instead of using _KSM_Toolbar. Just like dependencies listed in a define or require call, the order of dependencies in shim cannot be used to imply an actual load order. RequireJS will still load concurrent dependencies in an asynchronous manner. If a library has nested dependencies, a separate entry for each level of nesting must be specified in the shim property. With this configuration, if we list the language library as a dependency in a define or require call, the callback function won't execute until the toolbar library and jQuery are loaded as well. RequireJS will see that the language library depends on the toolbar library and will try to load the toolbar library. It will also see that jQuery is required by both the toolbar and the language libraries. jQuery will actually be loaded first, followed by the toolbar library, then finally the language library will get loaded. RequireJS loaded the nested dependencies properly for us. Look at how we're using this in the startup code. We call require with a single dependency of language. We don't include a callback parameter because we're dealing with a legacy library, not a module. The legacy library does not return a reference object. It places values directly on the global namespace. We're going to have to make use of the global _KSM object, just like all of our legacy code does. We can make use of the _KSM.Language.load to specify French as a translation language and we can use _KSM.Toolbar to set up the toolbar for our page. RequireJS loads the legacy libraries in the proper order so each of them should've made the respective changes to the global _KSM object. Let's run what we have to see if it's working like we expect. The tool tips are in French so we know we have the translation library working as well as the toolbar library. Let's dive a little deeper. Okay, maybe a lot deeper.
-
Understanding the 'shim' Properties
It's important to really understand how RequireJS handles legacy libraries. Most online examples use the same libraries with the same configuration parameters, but fail to explain what's really happening. Here's the sample code that is most often used in online examples. If you're using the backbone and underscore libraries you've got a head start, but there's not much explanation of what is applicable to your own libraries. The foo library used to demonstrate the init function isn't very helpful, either. Although there are embedded comments in the online code, they don't do much to really explain the interaction between the shim properties. There also isn't much discussion on when and why to use them. We're going to make numerous changes to configuration settings to see how each of them affect our website. Once we're done, you will have a better understanding of the shim properties and how to properly use them with your own libraries. We need to have some visible way to monitor changes we're about to make. Of course, if the code stops working, that's pretty visible. Otherwise, unless we go into some debugging console, there isn't any visible way to see what's going on internally. We'll use the list on our web page to give us some visible feedback as we make internal changes. We'll start with a simple task. Let's verify that jQuery is available to our code by making use of the $ variable. Notice that we haven't listed it as a dependency and we haven't included anything as a callback parameter. jQuery should still be available to us on the global namespace. Here we're using jQuery to disable the save button. That will be an immediate clue whether jQuery is working or not. We use a simple selector of .save to specify any object that has a class of save. In our case there should only be one, the save button on the toolbar. Once we have access to the button, we'll add the disabled class to it. The toolbar is smart enough to recognize that class and change the way the button works. Since we've changed an expectation of our page, we use jQuery to append that expectation to our list of things that we show on the page. If you're not familiar with the jQuery code, it just says, find the first unordered list item and append a list item to it. Within that appended list item, set some appropriate text. Let's take a look at that now. Well, the save button is disabled and we see an additional expectation listed on the page. So far, so good. We've shown that jQuery is in fact available to our startup code even though we didn't specifically list it as a dependency for the callback function. Let's take a look at the arguments that RequireJS sends our callback function. As with any JavaScript function, the arguments array is populated with all parameters that are passed to the function, even if we didn't specify variables to reference them. So we can inspect to see if jQuery was actually passed as a parameter to our callback function. I'm not advocating using the arguments array in actual code though, okay? We're just going to use the arguments array to make a point. To keep from scrolling the code, I've collapsed the sections in the configuration settings as well as the toolbar functionality. We've seen this code plenty of times so there's no need to keep looking at it. Besides, we want to focus on the new code. We've got another item in the list. This time it will tell us whether our callback function received any arguments. We're still using jQuery for this processing. We use a JavaScript Boolean shortcut to convert the existence of the first element of the arguments array into a true/false value. If you haven't seen this before, just believe that we're converting the existence of an element in the arguments array to a truthy value, true or false. This technique is very common and produces the result we want, even if the arguments array is defined, but contains a null value. It's also quick and easy to code instead of writing an if block. So the purpose of this entire statement is to append a list item with text that specifies whether RequireJS passes anything as the first parameter to our callback function. Running this in the browser, we see that RequireJS did not pass us anything at all. Is that what we expected? We have a dependency listed, the language library. Didn't we learn earlier that RequireJS returns a reference for each of the dependencies as parameters to the callback function? What's going on? Let's temporarily add an AMD module to the page, say the footer we wrote. Will that give us an element in the arguments array? In this iteration, we've added the footer module as a dependency. We put it as the first parameter since that's the only element in the arguments array that we're checking. To make the output cleaner, I went into the HTML page and commented out the list items dealing with what RequireJS loads. I did this because now we're just interested in our debug information. We know what RequireJS is loading. So the question is, will the addition of the footer module as a dependency give us a value in the arguments array? What do you think? No. Although it's nice to see the footer again, there's still nothing in the arguments array. As I mentioned, some of the previously visible list items have been commented out. Let's work with the code some more. We've taken the footer out and just put a dependency of jQuery in its place. What should happen now? Will we have a value as the first element in the arguments array? Yes. Finally we see that there is an element in the arguments array, even though we didn't actually define a variable to hold it. What's going on? Why did we get a reference for jQuery, but not for the language library or for the footer module? Is RequireJS messing up? No. We are. Okay, fine. I'm messing up, but it's to illustrate a point. Even though jQuery is a legacy library, it's been written to play well with AMD modules so it returns a reference to itself when loaded by a module loader like RequireJS. We'll add another item to our page list and ask for the length of the arguments array instead of just assuming there will be something in the first element. In addition to checking whether the first element is truthy, we're going to actually see the length of the arguments array. Ah! We see that the arguments array has two elements in it. The first is a reference to jQuery and the second must be the reference to the language library. Even though the arguments array has two elements, the reference to the language library is undefined. There's a placeholder for it, but it doesn't point to anything. Let's talk about these results because they set the foundation for what we're about to learn. Our legacy language library was not a module and didn't return a value that RequireJS could pass on to our callback function. The footer code was organized into a module, but it didn't return anything for RequireJS to pass on to our callback function. In both cases the arguments array contained an undefined value as a placeholder for each of those dependencies. RequireJS was able to load the files, but had no idea what to return to the callback function. That's why we got an undefined value as the first element of the array. Although jQuery is a legacy library, it's been written to work nicely in a modular manner. It will actually return a reference to itself when loaded by RequireJS. Normally if a value is placed on the global namespace, we can just reference it directly in our code and we don't need RequireJS to return a value to the callback function. However, there is a way to wire things up to accomplish that if the need arises. We removed jQuery from our dependency list and we're back to having the language library as our only dependency. Let's go back up to the shim property and look at that again. We now have an exports property in the language configuration. In this case we specify that we want to export the _KSM.Language property for the language library. This tells RequireJS to wrap our code and export the language property of the _KSM object in the global namespace. Let's run this to see what happens. Now we see that the first arguments element, element subzero, exists and the length of the arguments array is 1. Let's be clear about what just happened. The exports property is only used when loading legacy libraries. It's how we tell RequireJS what to reference as a callback function parameter. Without an exports property RequireJS will return undefined as a reference. In our case we got a reference to the _KSM.Language object. The _KSM object still resides in the global namespace. RequireJS doesn't alter anything about how the language library loads in the browser. RequireJS is just giving a reference to the language object within the _KSM global object for easier access in the code. Even after the callback function goes out of scope, the _KSM object will still exist on the global namespace. RequireJS can load legacy code without using an exports property. This is something that isn't always made clear in some online discussions. For the exports property it's important to remember, the purpose of the exports property is to tell RequireJS what portion of a legacy library to return to a callback function. You don't have to return a reference to a base level object when loading a legacy library. In our case, we're exporting a reference to the language library that is loaded in the _KSM global object. Most libraries do reside in a single object, however. So things like jQuery, Backbone, Underscore, and numerous other libraries will most commonly be accessed by their root definition. Let's add a few more items to the list to give us more information about what's going on. In our exports property, we're asking RequireJS to return a reference to the language object within the _KSM global object. Let's verify that we do actually get that reference and not a reference to, say, the global _KSM object. This is going to become important in just a moment. These lines display whether arguments element 0 has a toolbar property and a language property and whether arguments element 0 is the language property of the _KSM object. Right now the results of these lines should be false, false, and true. We should get a false value for the line that checks for a toolbar property. The language library doesn't have that. We should also get a false value to the line that checks for a language property. The language library is the property for the _KSM object, but does not contain another property named language. We should get a true value for the line that checks if there is a load function. That's how we're going to check to verify it is in fact the language library and not something else. Let's take a quick look in the browser and see if we get the results we expect. We see that the arguments array has one value and it does not contain a toolbar or a language reference. It does, however, contain a load function. That's what we check to see if it is the language object. Bear with me, I have one more point to make and we'll wrap this up. Let's take a look at another property we can use in the shim object.
-
Using the 'init' Function
There is another option that is barely described in many online examples, that's the init function. We've got an init function added to the language property list. RequireJS will run the init function before running any callback function associated with the dependency of this library. What this means is this code will run before this code. The purpose of the init function is to provide an opportunity to perform any maintenance code associated with legacy libraries prior to executing the callback function. The kind of maintenance that is performed is completely up to your environment. In some of the projects I've worked on, we would extend the functionality of jQuery. For example, we would add plugins, custom selectors, new functions, or even implement deprecated functions from previous versions of jQuery. The init function is a great place to handle this type of code. After jQuery is loaded by RequireJS, the additional features can be added prior to any other code using jQuery. The init function is only executed once so it works perfectly for this type of need. Other libraries might have their own plugins that you could implement in this same manner. Even custom libraries that you maintain may require certain alterations during upgrades or something like that. The point is if you need access to the library after it's loaded, but before it's used, the init function is the place to gain that access and do your thing. In this sample, we're making a copy of the _KSM object that resides on the global namespace. We're placing the _KSM_Copy int the this object, too. What does this point to? RequireJS will set the this property to point to the global namespace. Since we're using a browser, that's the global window object. So we're placing our copy of the _KSM object right next to the original on the global namespace. The last thing we do in the init function is return a value to the _KSM_Copy object, not the _KSM_Language property. We can verify this works by adding another item to our page list. This line just checks for the existence of the _KSM_Copy object on the global namespace. Let's take a look in the browser. I know the list is getting quite long, but I have an idea to clean it up some. We'll do that in the next chapter. At least we've commented out a few items to keep it from getting too long. I still missed the footer, though. Notice that the toolbar and language object do exist in the reference value. Also, we're no longer getting a reference to the language object. These three values used to say false, false, and true. Using the init function somehow changed the behavior of the callback function parameters. I mentioned something earlier, but kind of glossed right over it. In the init function after making a copy of the global _KSM object, we returned a reference to the copied object. Where are we returning that value to, though? We're returning it to the load process used by RequireJS. Once RequireJS loads the legacy code, it checks for the init function in the shim object. If there is an init function it will process it and pass any return value on to the callback function that required the library. That means our callback function will get a reference to the return value from the init function, not the value designated in the exports property. The return value for the init function will supersede the value we specified in the exports property. That's one of the things that's not made very clear in some online samples. As a matter of fact, if you have an init function, there is no need for an exports property. Stated another way, the exports property will not be used when an init function is in place. Look at the exports property. We've specified that the language library exports a reference to itself. Now in the init function, we're saying to return a reference to the _KSM_Copy object. That should contain toolbar and language properties. Why would we return that value? Perhaps we have a typo or someone new made this code change. In either case, there's a discrepancy in what we've asked for as a return value and what we're trying to export. That's why our browser values changed to true, true, and false instead of the previous false, false, and true. Our callback function is getting a reference to the _KSM_Copy object, not the language property within it. Let's see if we can get some clarification to all of this. RequireJS can act as a script loader for legacy JavaScript libraries. Legacy libraries do not need to be converted to modules for RequireJS to make use of them. Dependencies for legacy code can easily be specified with the shim property. By default, RequireJS will not return a reference to anything in legacy libraries. The shim property is only used with legacy libraries. Dependencies to other libraries are specified within the shim property. To request a return object from a legacy library, we can use the exports property within shim. The exports property is used to tell RequireJS what portion of the legacy library to return to the callback function. This is usually a reference to the root object stored in the global namespace, but it doesn't have to be. The exports property is ignored when an init function is present. If maintenance is needed after loading a library, but prior to a callback function, the init function can be used to perform that maintenance. The return value from the init function supersedes the exports property. Even if we don't return a value from init, RequireJS still won't use the exports property and nothing will be returned to the callback function for the library reference. We've covered just a few of the configuration options available to us. We focused on some of the confusing properties associated with using legacy libraries. RequireJS allows numerous options to support bundling multiple modules together into a single file or multiple files. We'll talk about optimization in the final chapter. To learn more about these advanced options, visit the RequireJS website or search the Pluralsight catalog for additional courses. This chapter was all about configuration. We started by looking at ways to configure RequireJS in our startup code as well as the HTML page. Configuration options allow us to place files in folder structures instead of having a flat file structure. RequireJS can be configured to load legacy JavaScript libraries and even manage their dependencies. We covered the shim property and discussed the often misunderstood exports property and init function. Now that we've learned how to configure RequireJS, let's put it all together in some real-world examples.
-
Real-world Concepts
Integrating a UI library
In this chapter we'll take a look at using modules for more than just a static page. I've decided to spruce up the page we've been using. As we do this we'll encounter some real-world situations that occur on projects all the time. First, we'll integrate a user interface library. There are many options out there. We'll use jQWidgets as our guinea pig. So far we've been using a fairly static HTML page. We'll simulate an AJAX process to dynamically add menu items to the list. Segmenting code into modules is great, but what happens when we need to pass values from one module to another? Placing data on the global namespace is not an option, right? We'll look at a better way. We're going to cover a lot of concepts as we look through multiple code files so get ready. Remember our last sample page? Here's what it looked like before we commented out the items to make room for our debug information. The list was getting quite long. That's often the case with actual projects, too. What starts out simple can become complex very quickly. As users make more demands of our websites, we end up adding more buttons, links, and controls. Project complexity can grow with even the simplest request from a user. Let's make our website look better and provide better functionality at the same time. Instead of having a long list of menu items, let's make it an interactive collapsed list. This will make the screen less cluttered. The end result will be that the user won't be bombarded with more information than they can consume. The user interface library we'll use is jQWidgets. It's available for free under the Creative Commons Attribution - Non-Commercial 2.0 license. That's a mouthful, just to say it's free to use for personal use. Making use of this library will give us a chance to mimic a real-life need to integrate user interface libraries into our modular design. It will also serve as a stepping stone to our next enhancements. Although jQWidgets has a pretty long list of user interface controls to choose from, we'll just focus on one: the list menu. This will allow us to present the list items on our page in an expandable and collapsible menu structure that we can interact with to see more or less information. Here's the HTML file that we'll be using for our sample page. We have fewer list items than before, but we still have some nested lists. We've added two style sheets to support jQWidgets. One is used to stylize the controls, the other sets the color scheme or theme for the controls. The plan is to make the user interaction more intuitive and let the user decide what information, if any, they want to consume. Here's the start file we'll use. We've got a reference to the jQWidgets library in the configuration properties. We're using a file that includes all the widgets, but that may not be the best choice in all instances. As with many of the user interface libraries, jQWidgets allows us to load files specifically for the user controls we want to use. For the sake of simplicity we're loading the entire library. After our standard toolbar logic, we use require to load the jQWidgets library. I chose not to include the jQWidgets library in the module dependency list because there's no need to delay toolbar and footer processing while waiting for jQWidgets to load. Since the start page is the main entry point for our website, I want to give the most rapid response possible. The first time this page loads, there could be a delay. Once the widget library is loaded, any future requests for it will be served up immediately from the cache. We're going to make use of jQWidgets, but I'm going to dive very deeply into how to use it. I'll explain what's going on as we encounter options, but I'll leave it up to you to either learn more about jQWidgets or apply the concepts to the user interface library of your choice. Within the callback function for the jQWidgets dependency set we walk through any unordered list elements in the page and add a list menu attribute to them. This is an attribute that jQWidgets needs to properly set up the list menu. Any elements with this attribute will participate in the list menu control. Once all the unordered list have the list menu attribute, we find the first unordered list on the page, and tell jQWidgets to use it as the master list menu item. We pass two parameters to the jQWidgets library. The first one indicates the theme we want to use. This matches the CSS file we loaded in the HTML page. The next parameter tells jQWidgets to disable scrolling. This means our page will expand to accommodate as many list items as we have instead of making the user scroll through the list menu. Let's take a look at this in the browser. After a brief pause, we see the HTML list elements are converted into a more pleasant list menu that we can interact with. This arrow indicates there is a sublevel. It's pretty intuitive to just click on the item to expand it. We can drill down to see the items for AMD modules and then navigate back up to the root level by clicking the back button. The page works well, but I don't like the brief flicker before the HTML list items are converted to a jQWidgets list menu. Let's refresh the page and watch that flicker again. It's short, but it's distracting. Let's address that before moving on, okay? How should we hide the original HTML unordered list items until they are converted to a jQWidgets list menu? The obvious answer is to use CSS to hide the HTML list, then use jQuery to make it visible once the list menu has been created. We'll look at another idea in a moment, but this is a good way to ensure that there is no flicker of the display when it converts from one format to another. In our HTML page, all we have here is add a hidden class to the master unordered list. I use this class all the time in my own projects. Within the CSS file there's a style called hidden that specifies a display type of none. Any tag that has the hidden class will not be shown in the browser. Now all we have to do is get jQuery to remove the hidden class from the list, once jQWidgets has finished turning our unordered list into a list menu. We're using a chaining technique to continue working with the same element that we used to make the list menu. Once the jQWidgets library is finished converting the list to a list menu we'll get a reference back that we can use to remove the hidden class. Let's bring this up in the browser and watch for that flicker. There was no flicker. I assure you, it's not a trick of video editing. I'll refresh the page by pressing F5. Ready? Here it comes. Excellent! Now we can refresh the page as many times as we want and we'll never see a flicker. All of this wasn't in vain.
-
Loading Dynamic Content
Let's review what we've done. We've integrated a non-modular library into our website and used it to decorate the user interface. We had to make adjustments to our code and HTML to provide for the asynchronous manner in which our script files are loaded and processed. That's a key point to consider so let's talk about that next. Using RequireJS as a script loader means our scripts can be loaded in any order and at various times. If we have any dependencies to consider we must either configure RequireJS to be aware of those dependencies or alter our programming logic flow. We're using the second option with the jQWidgets library. We placed our decoration code within the callback function that executes once the library is loaded. Let's imagine for a moment that the actual content of the page is dynamic instead of having static HTML on the page. What if we used AJAX of some asynchronous process to dynamically load content on the page? How would we decorate content that arrives in that manner? Let's take a quick look at that. Again, this is a step further along our path of discovery, hang in there. Instead of having a static HTML page, we're going to load the page contents from data. The first thing we need to do is get some data. To that end we have a data proxy module that will provide the list items for our page. This is intended to approximate an AJAX call to a web service or some other asynchronous data feed. We're not going to get into how the data proxy module works. If you're interested, the code in the exercise files is heavily commented to explain what's going on. Feel free to peruse it to see how it's constructed. For our purposes we only need to know that our data proxy provides an interface to get data that we will use on our page. Scrolling down through the file we see some default data laid out with titles and children properties. The last item in the default data array describes that dynamic data is being used. We should see that in our browser page. The final thing we need to notice is the method called loadAsObject. That's the method we will use to load the data for use in the web page. The setup for our main entry point is the same as we've seen before, only now we have an additional module to configure and we also have some additional logic that loads the data as an object. We'll reference the data module object with the callback parameter value called data. We'll make use of the data object by accessing the loadAsObject method we noticed earlier. As a final step, we hide the data object. This is to eliminate the flicker that we previously talked about. The loadAsObject method will return a jQuery object containing a master unordered list with embedded list items as appropriate. All we have to do is append it some place in the document. We do that right here. We're finding the parent of the one and only h4 tag on our page and appending this object to the end of that. Let's take a look at our HTML page. We see that we have no unordered lists in our static page. Our content will be completely supplied by data. I could've used any number of selectors to tell jQuery where to place the list items. I chose the h4 selector for no particular reason. Let's take a look at it in the browser. Our page looks and behaves just as it has before, which is exactly what we want. If we expand the first list menu item we do see that the last item in the list indicates that the data was loaded dynamically. Our list menu is being loaded from a data source and is still being decorated properly by the jQWidgets library. Excellent! Now what? All this time we've been staring at the toolbar and hovering our mouse, but it hasn't been used for anything. Let's change that and make use of one of the buttons for some user interaction. Here's some code that will listen for one and only one click event on the publish button. When that happens, we'll add an item to the list from code. Let's test this in the browser. When we click the publish button, we see that a list item is indeed added to the list menu, but it's not decorated. That's because the data was added after the decoration logic was called. We requested the new item in a click event, but imagine if the data was coming from an RSS feed or some long-running asynchronous process that periodically provides data? We need to tell the list menu to refresh itself whenever new data arrives. The obvious place to do this is in the same place that adds the list item. In this case it's the click event. We'll add one more line to tell the list menu to refresh and everything should work properly now. This additional command tells jQWidgets to refresh the list menu. This will cause it to redraw the list menu with all subordinate elements properly decorated. Let's see how it works. When we click the publish button now we see that we get a new decorated list menu item. What we have so far is great. We get a new data item when the user clicks the toolbar button. Let's take another look at what we really have, though.
-
Mixing Code
So far we've been so focused on getting modules to work and getting the user interaction to work that we mixed our user interaction with the data that's presented on the page. Look at this block of code. We're setting up some logic for a user interaction, namely the publish button click event. When that happens we begin working on the user interface and append some data to it. We then make sure the list menu displays properly with the refresh command. That's a mixture of logic that affects user interaction, data retrieval, and the user interface. We've got all the logic used by this page in a single file, but what about reusability? That's a big aspect of modular programming. One of the primary purposes of modular programming is reusability. The click event processing and data retrieval may be useful on other pages, too. Perhaps other pages will present the data in a different user control. In the spirit of modular programming, let's take a step toward modular logic by separating that logic flow into related chunks. As a first step, here's the same logic broken into two different segments. We are still listening for a user click event, but this time we're calling our data proxy module to retrieve a new data item to display. Once we have the new list menu item, we use jQuery to trigger an event on the default context to let the website know that new data has arrived. We attach the new menu item as a property of the event for some other process to handle. The other process is defined next, although it doesn't necessarily need to be. For this example I chose to keep them side by side. This second process could be in a different file or even in a module. We'll get that working in a moment. We're using jQuery to set up an event listener. We use the default context that's defined in our configuration module as the listener. This was also used as the trigger point in the code above. Think about that for a second. By using the configuration module we can change the event listener easily in one place and the rest of the website will respond appropriately. When this event is processed, we use jQuery to find the first unordered list on the page and append the item that was passed along with the event. Now this is a very rudimentary example and will not work properly if we get an element other than a list item. If this were real production code we'd need to verify the new item before we blindly attach it to a user interface element. Let's look at this in the browser as it is, though. All we're looking for is what happens when we click the publish button. We see that when it's clicked, we do in fact get a new list item in the page and it is properly decorated. This is a very simplified example of how we can make use of events to overcome timing issues related to segregated code. If we need one module to notify another module about the arrival of data, modification of data, or really any type of event at all, we can make use of events to keep the modules decoupled, but still able to interact. The tendency for legacy programmers it to place all the code relating to a page in a single file such as we've done so far. That's fine in many cases, but falls short when the complexity of the page grows or we ask the page to present multiple options to the user. The concept of a page ultimately morphs into a single wrapper around a variety of capabilities. Those capabilities may have no direct relation to each other, except for the fact that they all reside within the same presentation for the user to see. Separating code into smaller chunks of related logic is what modular programming is all about. Let's refactor what we have so far and make use of modules to handle each of the separate areas of concern.
-
Refactoring Footer Module
The first thing we're going to do is take a look at another way to make use of modules. Let's look at our pattern so far. The configuration module does not return a value; it just defines a static object with numerous key value pairs. The toolbar module follows a different pattern. There's a callback function that RequireJS invokes after the module is loaded. We use that callback function to do some initialization, build an object, then return an object for RequireJS to pass along to the calling program. What if we need to pass parameters to the initialization process for the object? Is there a way to pass parameters to the callback function that RequireJS uses? As of the publication of this course the answer is, no. RequireJS is designed to pass references to dependent modules to the callback function. There is no provision to pass initialization parameters. That isn't nearly as inhibiting as it sounds, though. Let's refactor our footer module to demonstrate how to satisfy the need of passing parameters to initialization function. Here's the footer module as we left it from very early in the course. It's using four different string literals that just beg to be parameterized. At a minimum, wouldn't it be useful to let the calling program override the text that's displayed in the footer? While we're dealing with this topic, where would be a better place to store those literal values? If your answer was in the configuration module, good for you. You should reward yourself with some ice cream or something. Here's an updated configuration module with four new properties for use by the footer module. They are the same values we had before, but now they exist in the a central configuration module that is easy to modify without breaking program logic. We've got the footer options wrapped in an object by that name and our four values are named wrapperTag, footerClass, insertAfter, and footerText. Now let's take a look at how we can refactor the footer module. Wow! There's a lot more to the footer module now. Let's take it from the top. Our footer has a dependency on the configuration module and we're getting a reference to it with the config parameter to the callback function. This allows us to access the values we just added to the configuration module. We'll use those values to control the behavior of the footer. We also set up some default values within this module, just in case. Although we have values that are identical to the ones we placed in the configuration file, they don't have to be. These are fallback values to make this module operable even if there are no footer values in the configuration module. There are a few situations where these values would come into play. Imagine a configuration file that's dynamically created, say, during a build process. If something happened in that process to eliminate the presence of our footer values, we would still have default values to work with. How about if a later version of our website changed the way the configuration file is used or even removed it completely? Our footer would still have a basic display capability. It's even possible that some future developer may rearrange or split the config file into multiple segments. In any of these cases, our footer would not operate properly without the default values. This is an example of defensive programming. To me, it's worth the extra effort to keep future changes from breaking the code. The flip side of this is that it may not be readily apparent that some future modification actually changed the behavior of the footer. Since the footer is not part of the page content, very few people even pay attention to it. The text of the footer could change and it might be months before anyone even notices, if anyone ever notices at all. Okay, back to the code. In addition to the internal properties, we have an init function that accepts a single parameter called options. Keep in mind, however, that this function is not the same as the init function we used in the previous chapter. This is an initialization function that is only used by this module, not by the RequireJS configuration routine. The intent is to allow an object containing a group of options to be passed to this function. This allows our init function to receive a single parameter instead of having a list of parameters for each new option we want to support. I prefer to code this way because it makes future changes much easier. When defining a function that needs parameters, I find it easier to allow a single object that contains multiple properties instead of having a function with multiple parameters. Functional parameters are positional and are difficult to omit or skip over. Future programmers need to know the order and meaning of each parameter to make use of the function. Using an object allows new values to be passed to the function without breaking the legacy function calls. I did a segment in a previous Pluralsight course on this topic. Feel free to check out that course for more information on this coding pattern. There is some special processing going on to allow a string value to be passed instead of an object. If we get a parameter of type string, we assume the calling program is just passing a footer text value. We'll use that string to populate the footer text property of a new options object. If we get something other than an object for the options parameter we'll just pretend the calling program didn't pass us any options at all and we'll end up with the default values. We use jQuery to merge the default properties with the options passed to the init function. Values in the options object will take priority over the default values. We use jQuery again to store the options value onto this footer object. This isn't strictly required for this type of module, but it's a habit I formed a long time ago. I've always found it useful to have the initialization properties stored for future use within the module. We don't have any getter or setter functions, but they would be very easy to include if we needed access to these local values. Finally, we see the same code we used to have, only this time we are using the values stored in the footer object instead of string literals. Let's take a look at how to make use of this new pattern.
-
Using New Footer Module Pattern
Up until now we've only asked RequireJS to load the footer. Since we haven't been returning anything, there was no need for a callback function. Now that we're returning a reference to the footer, we want to include a callback function with a reference to the footer as a parameter. We use that reference to call the init function and pass a value. We're taking advantage of the fact that a string value can be passed instead of an object so we're passing a new string to use as the footer text. Let's see this in the browser. We see the footer has changed to the text value we set in code. It may not seem like much reward for the effort, but this pattern of module definition and use sets the stage for our final project. Before we get there I have a question for you to ponder. What happens to legacy code now that we've changed our footer module? None of our legacy projects ever called the init function. Is there a way that we can have our new module work with legacy code as well as future code that uses the init function? Yes, there is, but we'll need to make a couple of tweaks. Let's do that really quickly, then we'll move on. Let's revert our codes the way we used to call the footer module. I've just commented out the new way for now. Let's take a look at that in the browser and verify that the footer is broken for legacy code. As expected, our new footer module is no longer compatible with older code; we're not getting a footer. We can fix that very easily, however. A simple answer is to automatically call the init function before returning a reference to the footer object. Our legacy code isn't expecting a return value, but it won't break if one is passed. By adding this line we have provided backward compatibility for legacy code. Let's check it in the browser now. Alright, we're getting a footer again. We're done, right? Not quite. What did this new change do to our new code that knows about the init function and makes a call to it? If our module calls the init function and the calling program calls the init function, what will happen? Let's comment out the legacy call and uncomment the new init pattern. We're back to using the init function of our new footer module. What do you think will happen? Yes, we get two footer entries. That's definitely not what we want. There are a few ways to approach this, but I prefer a very simple solution. Within the init function we can just use jQuery to remove any existing footer before we process a new one. If jQuery doesn't find a footer as will be the case for legacy code, nothing happens. If there is a footer, as will be the case for new code, the default footer added by the automatic init call will be removed and a footer using the new logic supporting parameters will be displayed. Let's see the result of our latest changes. Now we're back to a single footer. We have a module that will work with legacy code as well as newly written modules. Our simple footer module has evolved into something that we can mimic in other modules. It maintains local properties. While the values we are using are very simplistic, other modules may require more complicated data structures. The footer module also contains an initialization method. This method is used to resolve parameters passed as an object, as well as options stored in a configuration module. That's another key point. The values that control the behavior of the footer module can be managed externally by changing configuration properties. The footer module is self-initializing. It will make use of default values to support legacy code and allows new code to make use of the init method to alter that behavior. The footer module provides new capabilities while still supporting legacy code. That's a pattern worth repeating. Yes, there is a lot going on with this module. If there's anything in the footer module discussion that isn't quite clicking for you, I encourage you to rewind and go through it again or better yet, open up the exercise files on your own machine and play with the code. Our next topic will build upon what we just did to the footer module.
-
Final Project
This is going to be our final project setup and it will be the most complicated one we've done so far. From outward appearances we won't see much difference than what we've seen up to this point. We will, however, make significant changes to the code. Take a look at the last startup file we used. We have configuration code, user interface setup, data loading code, and then more user interface code. We finish up with some event handling code. All this code evolved throughout this course. That's just what happens in real life, too. Why is all this code in this one file? Does it really all belong in the startup file? We're just dealing with the toolbar, a single list menu, and a simple footer, and we have this much code. It started out very simple and has grown in complexity and length. Modular programming is supposed to help with both of these problems, right? Let's take a look at what we have with a fresh set of eyes. We have various modules that could be better organized. We already have a components folder that holds all of these modules. That flat structure makes it more difficult to maintain as more modules are added in the future. Here's the configuration object we're going to use for our startup code. Notice how easy it is to discern the organization of the files by the grouping we've used. The user interface modules are grouped together and we see they all reside in the UI folder. The same is true for the toolbar language and data modules. The configuration module is also placed in its own folder. We'll talk about the loader modules in a moment. As we scroll down to the see the startup code, we see that we just have a single line. We require KSM_UI and don't even have a callback function. This pattern helps keep the focus on the startup configuration and demonstrates that all our previous code was related to the user interface in some manner. For other projects we might need to initialize security, database connections, local storage management, or any number of other tasks that aren't directly related to the user interface. Those aspects of the app could have separate modules responsible for each of those areas and be initiated with a single line require function call just like we're doing with the user interface. So what does the KSM_UI module look like? If you're thinking we just copied the code from the original startup logic to a new UI module, that's a bit short-sighted. If that's all we're going to do, we might as well leave all that logic in the startup file. Here's what the UI module looks like. It has a dependency on jQuery and the configuration module. We're using the pattern for the footer that we recently established. We have a new concept for the toolbar and language libraries called a loader. We'll take a look at that in a moment. We're also using a DataLoader, but in this case we've got a callback function. Why? The reason is because RequireJS loads modules in an asynchronous manner. Even though I've said that numerous times it still bears repeating. We can't be sure that the modules will load in the order they are listed here. It's completely possible that the footer could load after the data. It's very unlikely, but it's possible. Since we have processing that is dependent upon the loading of data, we need to use the callback function to encapsulate that dependent logic. Encapsulate dependent logic in a callback function. The rest of the logic in this module resides in the callback function because it is all dependent upon the data being loaded. The data we're loading is what is used to populate the list menu. We use this as an approximation of a web service call. We call an init function on the DataLoader module reference returned to us by RequireJS. Then we require a new module called UI_Decorations. Before digging into the UI decorator let's look at the DataLoader module.
-
Data Loader Module
The DataLoader module is new for this project. We haven't used it before now. It's responsible for loading the DataProxy module and providing a wrapper around it. Why would we need to add this extra layer to the data proxy module? Why not just use it directly as we have before? When we used the data proxy module before we were calling it from within the startup code. We loaded the data and manipulated the web page directly in the startup code. That created a tight coupling between the startup code and the data module. A change to either one could break the logic flow. Using the DataLoader provides a loose connection that can be used between UI logic and data logic. The init function of the DataLoader takes a single parameter that specifies what element on the web page the data should be appended to. This is the only connection to the UI module or modules that need to interact with the data. This data module makes only one assumption about the user interface. It assumes the appendToTag has a parent that is used to append the data to. For our purposes this works, but the code could be even more decoupled to remove even this minor assumption. Another function this DataLoader module provides is the singleItemListener. This function when invoked will set up an even listener on the default context for our web page. When it receives an addItem event, it will get a single record from the data proxy object and trigger a subsequent event on the default context called appendRefresh. The single data record will be included as a property of the event call. Think about what we're doing here. This single function acts as a listener as well as a trigger. It listens for a request for a new record, retrieves that record, then passes it along to anything else that is listening for the appendRefresh event. This allows us to keep the data retrieval isolated from the user interface module. The user interface module just sends a message asking for data and listens for the result in an asynchronous manner. There is no tight coupling between the two modules. The data proxy object is exactly as it was before, except for the addition of the loadOneItem function that we just saw. Since we're approximating a web service with the data proxy module, this function just returns a hard-coded list item value. Now that we've looked at the DataLoader module, let's take a look at the UI decorations module.
-
UI Decorations Module
In this context the term decorations refers to the way we are transforming our unordered list into a prettier list menu with jQWidgets. The reason we have a separate decorations module is to allow us to swap out the jQWidgets library more easily in the future. Perhaps we'd rather use jQuery UI. Placing all decorations of user interface elements in one place makes migrating to the next great user control library much easier. Also, if we decide to make this website mobile aware, we may choose to decorate the user interface with a completely different library, or maybe not decorate the controls at all. Perhaps mobile CSS rules would sufficient. One more reason. If we integrate the decorations into the UI code, that creates a tight coupling to the jQWidgets library. Are you starting to see a theme here? Proper modular programming allows for decoupled connections. The UI decorator module uses the same pattern as before. We are creating a return object with an init function for initialization. It also maintains a reference to the list control that we're decorating. If we were decorating numerous user interface controls they could each be retained for future reference. The init function performs the same logic we've seen before to decorate the unordered list as a list menu. Additionally, it sets up an event listener on the default context to respond to the appendRefresh event. When that is received, it calls the locally defined appendRefresh function. The reason there's a separate append and refresh function is to allow external access to this logic. In my experience, if I code for only one way to add data, the users will expose the need for three other ways to add data. The separate append and refresh function provides an externally available method for adding an item to the list without having to make a trip through the data module. It's coded this way just due to my own battle scars. I'll come back to this point with a simple demonstration in a little bit. Back in the user interface module the last step is to load a listener's module and call the init function for it. Let's take a quick look at that module. We only have one listener here. Before Harold or Doug jump in to question why, I'll explain. For our simple example here we could very easily have the click listener directly in the UI module. It is after all a user interface event. What about listening for system generated events? Perhaps SignalR is used to receive events from a server. Maybe events are attached to file objects, in memory objects, local storage, or any number of other non-user interface objects. Where would you place handlers for those events? We have the listeners module for that purpose. Even though the only event we currently have is related to the user interface. Again, it's the result of my own battle scars. Every project is different, that's for sure. For me, organizing code into meaningful groups that can be easily maintained, trained, and explained is very important. The programmer that has to make changes to the code in 6 months may be me. I want to give that future me a fighting chance. One last thing that's worth mentioning is the configuration file. Maybe you've noticed that that we're referencing the config file throughout most of the modules for access to event types, footer options, and other literal values. The behavior of the code can be changed easily by making simple configuration changes. To me that's much better than having to crack open code to make changes. Unit testing is usually sufficient to validate simple configuration changes. Full regression testing is advisable whenever actual code is changed. Even though a code change may be simple, you never know when a programmer may introduce an extra semicolon, parentheses, or a comma that inadvertently changes the actual logic flow. Configuration changes generally introduce less risk than actual code changes. Shall we take a look at the result in the browser? It looks and behaves just like it has before so there doesn't seem to be much to get excited about. We can click the publish button and get a decorated item added to the list menu, but we had that capability before. What's the point of all the separation of modules then? The point is, we have decoupled the modules by using events to manage the interactions instead of hard-coding logic-based connections. This project is ready to expand to make use of additional pages, user controls, event types, data sources, or even be ported to a mobile environment without breaking the architecture. Various team members could work concurrently on different modules. The event rules would need to be agreed upon, then different programmers could code to the event rules without regard for how other modules are actually put together. The modular pattern offers plenty of benefits to outweigh any additional effort required to implement it. Since you've made it this far into the course, I'm probably not convincing you of anything you don't already believe so I'll move on.
-
Using appendAndRefresh
Earlier I said I'd demonstrate how a user might request functionality that would warrant a separate append and refresh function in the declaration module. Let's do that and wrap up this project. While we're at it, let's turn it into a learning opportunity. Here we have a new click event added to our UI module. Yes, yes, it could be in the listeners, but I need a reference to the decorator module and the listener module doesn't currently have that reference. Instead of making a change to the listener module by adding a reference to the decorator object, I'm placing the code here for a simple example. This brings up a very important point, though. Modules can be modified to reference each other. That's easy. Just add a dependency. The question is though, should that be done? Do we really want the listener module to depend on the decorator module? What if we choose not to do decoration? That coupling may not be consistent with the needs for mobile support. It's worth some deliberation before automatically adding module dependencies. Okay, we had our learning moment. Let's see what the event does. We're listening for the click event on the save button. This literal value should ultimately be placed in the configuration module, but we're just doing a quick demonstration. Up until this point we've only had interaction with the publish button. This will be new behavior. Within the event handler we call the append and refresh method on the decorator object and pass it another literal string value. Let's see this in action. We can now click both the publish and the save buttons. The save button doesn't get disabled even though we only allow a single click. That's because we didn't specifically disable it. The save button is making use of the DataLoader module to gain access to a single list item. Without being coupled to the DataLoader and without accessing the underlying data module. The button click just sends out an event that causes a new item to be added to the list. Let's quickly review all the aspects of this page as a reminder and also for those who like to skip to the end of the course to see the final result. The toolbar is a module that works with the translation module to provide tool tips in a language other than English, but currently only French. The footer is a module that has grown to include an initialization function that allows us to specify alternate behavior for the footer, namely the text that is displayed. The list items are loaded from a data module and the DataLoader module is used to decouple the data from the user interface. The user interface is decorated with a third-party library through a decorations module. That module keeps the third-party library decoupled from the user interface logic. The data is loaded in such a manner to prevent flickering when decorations happen. We saw that there is no flicker when we refresh the page. We covered a lot of ground in this module. If anything isn't clear, run through the examples and try them on your own machine. Rewind and watch the chapter again if you need to. We integrated jQWidgets into our web page. This could've been any number of other libraries that exist to enhance the user interface. It just served as a guinea pig for our needs. We also provided content to the page dynamically as if from an AJAX process and used jQWidgets to decorate the dynamic data, too. We made use of an initialization function on a few modules to allow us to pass values to our modules. This is a much better pattern than placing values on the global namespace for shared access. We discussed decoupling modules and keeping code grouped by function. Even though there is sometimes overlap, it's usually pretty clear what code should be together. Remember, write your code so that it can be easily maintained, trained, and explained. In our next and final chapter we'll talk about optimizing the modules for easier distribution to client machines.
-
Optimizing Modules for Distribution
Introduction
This final chapter looks at optimizing modules for easier distribution to clients. Once you start using modules, the number of files that are associated with your project goes up dramatically. Browsers typically limit the number of files that can be delivered concurrently. That's why there are tools available to consolidate JavaScript files for easier delivery to the browser. RequireJS provides an optimizer that can be used to consolidate the various code files together into a single file for download. The optimizer can be executed using Node, Java, or even a browser. The browser has some limitations, however. We're going to use the Node version in this chapter. I'm not going to cover installation of Node. If you need information on how to do that, visit the Node website. Once Node is set up, we need to download the optimizer from the RequireJS website. The file to download is called r.js. It can be run directly from a command line with Node and it accepts numerous command line options. We could spend an entire course on RequireJS optimization so we'll just skim through a few options that will be particularly helpful in your projects. Our project has grown to include over 450 files located in 26 different folders. I know! I'm as surprised as you are. The reason for so many files is because the jQWidgets library accounts for almost 420 of them. That number could be reduced to include only the user controls we want to use in our project. We also have all the possible CSS themes included in the CSS folder. We could reduce the files there as well. So for our actual project needs we actually have about 35 files spread across numerous folders. Still, that's quite a footprint for a very simple page, right? What happens when we deploy this website and our users actually have to download all those files? We've been using the page locally on my computer so we've had immediate access to the files. Once they must be downloaded the user will be subject to bandwidth and browser limitations for downloading so many files. Modular design is great for the programmers that use it, but it can cause some download issues for the clients. Fortunately, RequireJS has an optimizer that will combine all the modules together for deployment and distribution. Let's take a look at how to do that as our final real-life example.
-
Using the Optimizer
Our project has grown to include many files spread throughout multiple folders. Maintaining the code is easier because the files are smaller and grouped by functionality, but we need to make it easier for the client to download all our modules. We're running the Node version of the optimizer, which runs on the command line. It accepts command line parameters to control the optimization process. Although you can theoretically pass as many optimizer options as you want on the command line, you really don't want to try more than a couple. Trying to manage more than a couple of options through command line switches is quite cumbersome. Fortunately, all command line options can be specified in a build configuration file. Let's look at our first pass as a build file. We start with a base URL property that is just like we've seen before. And then we have a paths property that is also just like we've seen before. As a matter of fact, this is a direct copy and paste from the last startup file we used. The final two properties are out and name. The out property specifies the output file name that will contain all the optimized code. We're calling it KSM_Optimized. The name property tells the optimizer where the starting point of our modular code is. This is all that is needed to run the optimizer. Let's see this in action from the command line. Since we're using the Node version of the optimizer we first invoke Node and pass it the name of the optimizer, r.js. The only option we pass is a reference to our build file. Currently we're using build-1. The optimizer traces through the dependencies of our starting module and even uglifies the code for us. That means we'll have short, meaningless variable names with no extra white space or comments in the resulting code. We see that it only worked with four files, though. jQuery and KSM_Congig were listed as dependencies on the module definition. KSM_UI was listed as a dependency in the embedded call to require. The KSM_Start file is used as our entry point. Where are the other modules? Toolbar, language, footer, and all the others that we created are missing, even though we included them all in the paths property of our configuration object. By default the RequireJS optimizer only inspects the dependencies it can find in the startup code. That's why it included the start file and the three dependencies it found there. Let's go back into the build file. We can use the property name findNestedDependencies to instruct the optimizer to walk through the dependent modules and find and include all dependencies. This looks like what we want, right? So we'll enter this command in the command line now, referencing the build-2 file. When we run this command we may start to wonder if our machine has frozen. To save your sanity and mine, I'll pause the recording, go get some coffee, and resume the recording when I get back. There. Now it's all done. It looks like the optimizer did find all the modules in our code. In fact, it minified jQuery and the jQWidgets library, too. Working with jQuery and jQWidgets seems to be what was taking so long. It wasn't a delay in finding dependencies, it was a delay in trying to optimize two very large JavaScript libraries. We probably don't want to bundle third-party libraries in our own code. jQuery can be served up from a content delivery network or CDN so there's really no need to include that in our optimized code. We may choose to do something similar for jQWidgets, too, or we may decide to keep third-party scripts in a separate folder and serve them up to the browser as needed. In either case, we need to find a way to exclude them from optimization. If you're thinking there's a simple exclude option or something similar, think again. There is no property that lets you specify one or more files to exclude from optimization. There is, however, a way that isn't quite as intuitive as an exclude property would be, but it gives us what we want. The paths property tells the optimizer where to find files that are referenced in the code. jQuery is in the scripts folder and jQWidgets is in the scripts/jQWidgets folder. We can replace the actual location of the files with a special keyword of empty followed by a colon. This tells the optimizer to exclude the module from the output file. Don't forget the colon after the word empty. If the colon is missing, the optimizer will expect to find a file named empty.js and will generate an error when it can't be found. With these changes in place, let's try the optimizer again. Our command line will use the build-3 file. Ah! Immediate processing and a rapid return. Excluding jQuery and jQWidgets did the trick. Now we see only the modules that we created in the output. One more thing bothers me though and it bothers me greatly.
-
Using a Single Configuration Object
Remember when I said the paths property was a direct copy and paste from our startup code? That's what bothers me. Why should we have to maintain identical path properties in both the startup and build files? There must be a way to use only one object so a change made in one will automatically be reflected in the other. Thankfully, since the version of 2.1.10, the optimizer has allowed for this. We can specify a property named mainConfigFile. This lets us specify the name of our startup module that contains the paths used by our code. It will then make use of configuration properties it finds there. That lets us use one master path's property. There is a problem with that, however. Our code needs a pointer to jQuery and jQWidgets, but our optimization needs an empty reference to those libraries to exclude them from the optimization output. The optimizer lets us specify an additional path's property in the build file that will override what was found in the startup module. We just specify the files we want to exclude by specifying empty: and we should get the same result as before, but this time without having to duplicate the configuration properties in our build file. Here's one more command line using the build-4 file. As we see, it produced the same result. However, from now on, any time we add a module to our code, the optimizer will be able to locate it without any further modifications. So we've got all our code optimized. How do we use it? Here's the HTML file that makes use of the optimized code. We still load RequireJS in the same manner as before. Now the data-main attribute points to our optimized code instead of our startup code. We still don't need to use script tags to load jQuery or jQWidgets. Those will be loaded as needed just as before. The references to them still reside in the optimized code. The architecture of our website has not changed. We still have independent modules. As far as the browser is concerned, they all happen to reside in the same JavaScript file. Let's see the page in the browser. The page loads and looks just like before. We can still click the save and publish buttons since that's how we last left the code. The optimization tool can be used to optimize CSS files as well as create module groups instead of one large file with everything in it. To learn more about it, visit the RequireJS optimization page. We've come a long way from our very first sample page with the legacy toolbar. We saw how to refactor legacy libraries into modules when necessary. We also saw that it's not always necessary to do so, thanks to the configuration options in RequireJS. Our simple footer module really grew up into a configurable module that can serve as a template for other modules to follow. We dug deep into the shim options and learned how they truly affect our projects that use legacy libraries. The jQWidgets library served as a sample user interface library. Once we had that in place we changed the architecture of our project to use decoupled modules that communicated via system events. Our final task was to bundle the modules into a single distribution file for easier client delivery. I've truly enjoyed presenting this course and I wish you great success with AMD in your own projects. Thank you for your time and your attention. As always, happy coding.