Welcome to this review of the Pluralsight course RequireJS: JavaScript Dependency Injection and Module Loading by Jeff “coding with spike” Valore.
Jeff has over 15 years of experience in software development including Java, C#, JavaScript, CoffeeScript, and TypeScript.
His belief that clean, well-organized code is key to making software maintainable has led him to focus on unit testing and solid programming practices and principles as a cornerstone of everyday coding.
Getting Started
Getting Started With RequireJS
This course assumes an understanding of JavaScript, but does not assume any previous experious with Require JS.
We take a high level quick look at the contents of the course, and a brief look at the SOLID principles and how Require helps achieve the Single Responsibility Principle, and the Dependency Inversion Principle
We see that Require JS works in the following browsers:
- IE6+
- Safari 3.2+
- Chrome 3+
- Firefox 2+
- Opera 10+
Introduction to the Sample Project
In this course we’re using a task list sample project. We start of with one html file and one big tasks.js file
This project uses jQuery.
Jeff recognizes that the first 3 functions are for manipulating the DOM elements.
Downloading and Including RequireJS
We save the file into our project, add a reference to require.js in our HTML file, and use data-main to point to our new main.js
Defining and Requiring Modules
Using External Libraries as Dependencies
We begin with separate script references for jQuery and require, but Jeff shows how to load jQuery from require using require.config
Jeff also introduces us to the require function, which specifies which modules must be loaded before the containing code can run.
After this refactoring, we see that our app continues to work as before.
Asynchronous Module Definition
An explanation of AMD, CommonJS and CommonJS Transport/C
For more information see:
- http://requirejs.org/docs/whyamd.html
- https://github.com/amdjs/amdjs-api/blob/master/AMD.md
- http://wiki.commonjs.org/wiki/CommonJS
Defining AMD Modules
We put our data access code into its own AMD called “taskData” using the define function, and Jeff explains how this works.
We also updare our require function to let it know that we are now dependent on “taskData”
We also see how we can rename our functions as we like.
The Module Design Pattern
This clip covers the module pattern and the revealing module pattern.
We see that the revealing module pattern has advantages, but at the cost of more verbosity,
and that AMD modules using require are adaptations of the Revelaing Module Pattern.
For more information on these, see Dan Wahlin’s course Structuring JavaScript code.
Define the Remaining Modules
Jeff refactors the rednering section of code into its own module, taskRenderer again using the define function.
We see that we can define an app module for additional separation of concerns. In here Jeff puts the registerEventHandlers() function, and we call app.init to register all of our event handlers.
We also learn that it is not necessary to wrap code in $(function() {}), and how we can further refactor the code.
Again we see that nothing has been broken by our changes.
Simplified CommonJS Wrapper
The simplified CommonJS wrapper syntax is for the purpose of avoiding the define function wrapping across multiple lines when there are many modules to import.
It has three arguments inside a function: require, exports and module.
This is a little closer to the syntax that ES2015 uses.
Loading Remote Modules
Introduction
Our objective is to split out big bulky main.js files into smaller ones such as tasks.js, taskData.js and taskRenderer.js
Define Remote Modules
We see a flow chart illustrating some of the tasks that the require framework performs. The first step is checking it’s internal cache of modules.
If the module is already loaded then that is what it returns. If not, it loads the module from the server.
Jeff shows an example of this in action, and we see that we don’t need to specify a module name if that name matches the filename.
Again, this is closer to how ES2015 works, and is a good habit to get into.
Using Subdirectories
Jeff adds new “data” and “renderers” subdirectories and updates the references. He warns that beginning the references with “../” won’t work,
but says that a workaround for this will be covered later in the course.
Performance Implications of Remote Modules
Remotely loaded AMD modules come with a performance penalty, because each file is retrieved via a separate request to the server. So the next module will look at the r.js optimizer
Optimization
Introduction
We learn that our objective for this module is one file: main-optimized.min.js
Build Environment and Running r.js
Download r.js at http://requirejs.org/docs/download.html
Jeff talks about build environments in JavaScript and compares it with builds in languages such as C# and Java.
This course uses NodeJS for our build environment, and we run r.js using the nodeJS command prompt
We see how to build both minified and non-minified versions of main-optimized, and learn that we must remember to update the data-main property in our script reference to this newly created file.
Optimization Result
Jeff shows us the non minified result, and we see that apart from it all being in one file it hasn’t much. Jeff explains why.
We also see how much smaller the minified version is.
Debugging and Source Maps
Jeff introduces a bug in the code and shows that it is very hard to debug minified code.
He explains two options to ease debugging:
1. Use un-optimized files in development
2. Use source maps
We see this steps that we can perform to generate source maps. I found that there is no longer any need to explicitly set uglify2 as the optimizer
Build Profiles
If the previous steps seemed like a lot of work, you’ll be relieved to know that you can create a build profile with the necessary configuration, and then run your configuration file from node.
Jeff shows us how to do this.
Configuration Options
Introduction
This module covers the 7 most commonly used configurations used in require.config
Descriptions for every configuration can be found at http://requirejs.org/docs/api.html#config
We learn a couple of alternatives to putting require.config in the main.js file,
but in this course, we continue with the approach we’ve already taken.
BaseUrl
We learn that we can load files from a different host altogether e.g. a CDN
Paths
A discussion of some of the different paths you can specify.
Here we learn our workaround for specifying files above our base directory.
Shim
Here we learn how to use Require JS with Underscore.
I have found that lodash does not need any shim, but bootstrap needs a shim similar to the one we see here.
Config
Jeff says that he’s never used config within his config. It seems to be quite complex to me as well.
Jeff prefers to have a separate module definition is a config.js file if needed.
waitSeconds
This is the number of seconds to wait before giving up on loading a script. The default is 7 seconds.
Deps and Callback
These are more options that Jeff has never needed to use, and he explains an alternative implementation which keeps your config cleaner.
UrlArgs
These are extra query string arguments appended to URLs that RequireJS uses to fetch resources.
This is most commonly used for cache busting.
Plugins
Introduction
Require JS’s default functionality can be extended with plugins. Each plugin is an AMD module.
We see an example of defining a plugin module as a dependency. We look at a couple of popular ones:
Text Plugin
Jeff believes the Text plugin to be the most widely used RequireJS plugin. It’s an official plugin available from https://github.com/requirejs/text
This is used for storing text such as HTML in Require modules. This makes your HTML easier to maintain, giving you syntax highlighting.
To use this we specify a templates directory in require.config paths.
Then we move our JavaScript HTML string into taskTemplate.html, and marvel and how much easier to read it has become.
We also need to update our taskRenderer module definition, specifying a dependency on “text!templates/taskTemplate.html”
and adding taskTemplate as a callback function parameter.
It’s important to be aware that you webpage must be served from a web server if you are using the text plugin, unless you are using the r.js optimizer.
Jeff explains this is due to the need to make an XHR request.
Handlebars Plugin
This plugin is written by Alex Sexton. Jeff shows us how to download this with NPM. This plugin automatically appends the .hbs extension
so our dependency is just “hbs!templates/taskTemplate”
We must add the path to our hbs directory in our config.
Now we can replace our jQuery code with handlebars code.
Custom Plugin
The plugin API allows you to create your own plugin, and Jeff shows us how to do that here, creating a CoffeeScript plugin.
Unit Testing RequireJS Modules
Introduction
Jeff says RequireJS significantly complicates unit testing for two reasons:
1. Module Loading is asynchronous
2. RequireJS caches modules between tests
Jasmine
Jeff introduces the Jasmine Testing Framework and demonstrates writing a couple of tests in it.
For more information see Testing Clientside JavaScript.
Testing RequireJS Modules
In my opinion the content in this clip is the most crucial lesson in this course.
Make sure that you understand this by watching the clip slowly and carefully.
We create tasksModule.spec.js and update SpecRunner.html
We use the require function inside our it function, to load our tasks module.
The problem that we have is each of our modules have their own dependencies,
but we don’t want to load up scripts that we are not testing.
So we define a stubbed module, and use a Jasmine Spy to spyOn it:
spyOn(taskRenderer, “renderNew”);
We get an error trying to run our tasks module. This is due to the path changing due to no baseUrl being specified, and RequireJS using the SpecRunner.html directory as this.
Jeff adds a require-config.js file and references it from index.html and SpecRunner.html
He also specifies our baseUrl to “src/js” in SpecRunner.html
The other problem we have is the asynchronous loading of modules.
First we need to add a done function parameter to our test function that was passed into the it function.
Next we call done(); after our expect statements, at the end of our require function.
Our test now passes, but Jeff warns that there is a huge problem.
To demonstrate this problem he writes a “thing should exist” test, and a “thing should not exist” test.
The second test fails due to RequireJS caching modules between tests
Jeff shows us how to clear out the internal RequireJS module cache between each test, using beforeEach and afterEach.
Instead of using the require function in our tests, we now use testRequire.
We see how to use require.s here, but the next clip shows a better way.
Squire
You can find and download the Squire tool at https://github.com/iammerrick/Squire.js
Jeff says we only really need the one file Squire.js
We can now replace our ugly beforeEach and afterEach code with something simpler:
var injector;
beforeEach(function (done) {
require([“Squire”], function (Squire) {
injector = new Squire();
done();
});
});
afterEach(function () {
injector.remove();
});
Jeff shows how we can now replace our define stubbed module with injector.mock
Instead of using testRequire we use injector.require
Course Verdict
I first watched this more than a year ago when the course first came out. I had heard some good things about Require JS over the Internet but never used it or seen anyone else using it. After watching the course, I decided not to use it for two main reasons.
Firstly, the team was already using ASP.NET Bundling and minification for our JavaScript files in our projects.
Secondly, I had only just introduced the team to JavaScript testing and I didn’t want to make the practice more difficult by introducing more complexity with Require JS.
A lot has changed since then. What was known as ES6 then has become the ES 2015 standard, and a lot of people are using ES6 modules now with Babel.
What does Babel JS compile ES6 modules to? Try it out for yourself in the Babel REPL.
For example type:
import ‘test’
It’s a good idea to understand how it work the code that you’re actually shipping works, not just the pre build code that you write. This looks like require JS code but actually isn’t. Keep reading to find out more.
Also, bundling and minification in .NET is on its way out. JavaScript is where it’s at.
I had forgotten most of the course, only remembering that it was good, so I watched it again. I am still very new to Require JS, but already this course has been a massive help to me. I also learned more by watching Jeff’s talk on You Tube:
You Tube Stir Trek Conference Talk
If you don’t have a Pluralsight subscription, or if you want to learn about how RequireJS compares with alternative module loading solutions, you can watching Jeff Valore’s talk JavaScript Modules: RequireJS vs Browserify.
In this talk he explains the need for module loaders, introduces Common JS and then 15 mins in discusses Browserify, and at 22 mins explains that your apparently global code is wrapped in a function scope to create a module. We learn 26 mins in that Browserify needs relative paths, and things will start to break of you move your files around.
28 mins in, Jeff explains where Browserify looks for npm installed packages e.g. named ‘jquery’. Summary of Browserify at 32 mins.
Require JS introduced at 33 mins. We see at 44 mins the Require JS is more configuration centric than Browserify. r.js is introduced at 47:45.
We see a comparison table at 52 mins.
Browserify Plus Points
- Less configuration
- Simple syntax
- Using of sharing code with Node.js
Require JS
- More flexibility
- Need dynamic loading at runtime
Both do source maps, and have a plugin system.
Jeff also has a Pluralsight course on Browserify: Creating JavaScript Modules with Browserify.
The best solution for modules is for support to be added in the language itself, which is why modules have been added to ES 2015 (a.k.a. ES6). However it will be years before all of your users are on browsers that support these modules.
In the meantime the best that you can do is transpile your code to ES5 code. Babel JS is by far the most popular tool for doing this, and it transpiles it to CommonJS code (not Require JS code). For this reason, I don’t believe Require JS should be the module loading tool of choice anymore.
I will be looking at Browserify and Webpack next.