Thoughts on Require.js and a simpler approach to loading JavaScript

Require.js is trying to solve the problem of dealing with lots of JavaScript. I have used it with Backbone.js, underscore, Dojo, and jQuery. It works well, but it does take some effort to get it configured and running within your project.

“When a project reaches a certain size, managing the script modules for a project starts to get tricky. You need to be sure to sequence the scripts in the right order, and you need to start seriously thinking about combining scripts together into a bundle for deployment, so that only one or a very small number of requests are made to load the scripts.” – from the Require.js home page

I completely agree with this.  An application’s JavaScript architecture (yes that is a term now) is something to be aware of and plan for from day one.

No matter what, it is WORK to get JavaScript loading right.  What I optimize for: first user experience, second developer experience. Require.js has potential to go against the grain at times on both these points if you are not careful. I have a simpler approach, which I demonstrate below.

Let’s talk about user experience first:

Require.js can delay UI behaviors and encourages asynchronous loading which is error prone.

“You may also want to load code on the fly, after page load.” – from the Require.js home page

What? No!!!!  Don’t do that!  Never take the network for granted. What if the wifi drops packets? Full stack developers know – you can’t guarantee the newly requested JavaScript code will actually load.  This is why nested dependencies don’t sit well with me, especially ones that are setup to load on the fly.

The way a ‘software engineer’ would handle this is to write a lot of try/catch and timeout code to re-attempt the download. If it ultimately fails, you’d need to deal with alerting the user – “sorry, we lost connectivity and now half the script is loaded, but you should probably refresh now to fix it.”  What a pain for everyone.  It is not easy to replicate bugs like that, but they will happen.

My approach is to simply avoid loading JavaScript asynchronously.  Look at all the bugs I fixed and the code I don’t have to write now!

Something going wrong network wise is a bit of a corner case. More importantly – think twice before making your user wait for extra libraries to load when they click a button! On your local development machine it will be instant. On a smart phone out in the woods, it might take 30 seconds – enough to drive someone nuts.

My solution is to treat a dependency as all or nothing proposition. Either library X becomes a script tag in the page, or its not included in the page. Library X could be a bundle of items specific to that page or feature.

To be fair – the Require.js framework provides a module called the Optimizer that bundles up scripts into one file.  You will need to explicitly configure it to avoid this situation. It can be done, but that brings us to our second point -developer experience:

Require.js fights the global nature of JavaScript:

The boiler plate nature of the method declaration in Require.js started to bug me. It reminds me of dependency injection on steroids.   It looks safe when you have one or two dependencies, but beyond that it starts to get tedious.  With four dependencies this is starting to get ugly:

require(["helper/utilA", "someUtilB", "anotherUtilC", "path/to/utilD"], function(utilA, utilB, utilC, utilD) { ...

Require.js takes dependency injection to the extreme. For example, dependency injecting jQuery into a method just doesn’t make sense to me. Same goes for underscore.js or any other base library. Yes you could do a mix of globals with Require.js, and maybe that is the best approach, but that isn’t how their documentation reads.

Loading jQuery in the global scope is okay with me since it is used almost everywhere in a global nature – as it was designed to be.

If you are worried about unit tests, don’t be.  Since JavaScript isn’t a strongly typed language there is nothing stopping you from loading a different global $ object and using that as a mock in a unit test. No reason to apply Java techniques to JavaScript just because.

 

What I do to organize my JavaScript is very simple:

My preferred way is to have a build script that combines and compresses the JavaScript for production mode. That way, there is less to download when the site is live, and I know exactly what is being loaded. In development mode, the site template loads the uncompressed JavaScript so it is easy to debug. This is the best of both worlds. I have not ran into a situation where this approach becomes problematic or limiting. The approach is minimalistic, serves the end user very well in terms of super fast page load times, and it doesn’t get in my way at all as a developer.

My latest web based project has over 50 different JavaScript dependencies (mostly custom classes that power Canvas based rendering logic). They are loaded in the correct order and everything works just fine.

Here is a basic example. Using bash, the .js files get combined and compressed using YUI compressor into combined.min.js.

#!/bin/bash

# This script generates the following:
#   Combined and minified javascript file for use in the app 

echo "Combining JavaScript into temp files."

# temp.js is the uncompressed file with everything combined
cat ./app/js/jquery.js \
./app/js/file1.js \
./app/js/scriptX.js \
./app/js/xyz.js \
> ./app/js/temp.js

echo "Compressing Combined JavaScript."
java -jar yuicompressor-2.4.7.jar --type js -o ./app/js/temp.js --charset utf-8 ./app/js/combined.min.js

echo "Cleaning up temporary files."

rm ./app/js/temp.js

How the page loads the scripts:

<? if(ENVIRONMENT == "production") { ?>
  <script type="text/javascript" src="js/combined.min.js"></script> 
<? } else { ?>
  <script type="text/javascript" src="js/jquery.js"></script> 
  <script type="text/javascript" src="js/file1.js"></script> 
  <script type="text/javascript" src="js/scriptX.js"></script> 
  <script type="text/javascript" src="js/xyz.js"></script> 
<? } ?>

If a script is a dependency, why not just load the dependency up front? There are cases where you would want to load something special – like a spreadsheet widget, but only on certain pages. In that case, great, just include the script tag in the necessary places in the app, and program the build script to generate it.

 

 

This entry was posted in Application Development and tagged , , , , . Bookmark the permalink.

Comments are closed.