Sunday, January 26, 2014

Requirejs: edge cases


I have already written about requirejs in this post.
In this new post I'm going to explain something you should know using require in complex environments.

You definitely should use the optimizer

Not using the optimizer in a production environment is highly discouraged.

To explain why, it can be useful thinking about how requirejs works:
All the modules together form a dependency tree. The process of loading this tree is not completely parallel because requirejs is aware of specific dependencies only after loading a module.
Requirejs optimizes this process executing a module only once and caching its result.
The optimizer simply merges a group of modules in a single file and this makes the process much faster.

Inline scripts

Sometimes you'll need to add online scripts to the HTML. Doing this you should consider that your module could be not loaded yet. To fix this problem you can "require" your bundle as a dependency before running the code:

require(["app"], function (){ // app is the name of the bundle
    require(["yourdependency"], function (){
        //inline script
    });
});

But there is another problem!
Many examples shows to put your requirejs configuration inside the first module.
Sometimes you'll need a specific configuration to load your module (for example the baseUrl). But the configuration could not be available in your inline scripts (it's async!).

There is a trick for this: requirejs allows to load the configuration in a synchronous way. You just need to define a global object "require" with the configuration right before loading requirejs script.

<script>
var require = {
    "baseUrl": "http://example.com/js/lib",
    "paths": {
      "app": "main/app"
    },
    "shim": {
        ...
    }
};
</script>
<script data-main="scripts/main.js" src="scripts/require.js"></script>

Mixing synchronous and asynchronous scripts

Performance wise all the scripts should be loaded asynchronously but real world applications need some compromise.
A typical case is using the infamous document.write that wipes out the page when executed asynchronously.

In this slideshow @slicknet explains why loading scripts in different positions.

But mixing synchronous and asynchronous script can be tricky. How can do it safely?

First of all be careful where you load the requirejs script in the page. Many libraries check for the presence of the "define" global variable to be loaded asynchronously (using the UMD pattern):

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['b'], factory);
    } else {
        // Browser globals
        root.amdWeb = factory(root.b);
    }
}(this, function (b) {
    //use b in some fashion.

    // Just return a value to define the module export.
    // This example returns an object, but the module
    // can return a function as the exported value.
    return {};
}));


Jquery for example is AMD compatible so, if you load requirejs before jquery, this will behave in a different way.
My suggestion is to concatenate all the synchronous files of your application in a single script, putting requirejs in the last position.

Second problem: what happen if you are requiring a module and, in this particular page, this module is already loaded synchronously ?
Requirejs will download the module again, this time as a module. This is not ideal so you need to trick requirejs prepending to our main module something like this:

define('jquery', [], function() { // jquery is loaded synchronously before this code
    return jQuery;
});

With the previous code requirejs will use the global jquery instead of requiring a new jquery module to the server.
I am not advocating to load jQuery syncronously. This is just an example!
Another issue: the optimizer will include all the scripts that in your particular case are synchronous.
You need to exclude these files from the optimization (this is an example using the grunt-contrib-requirejs task):

    requirejs: {
      dev: {
        options: {
        "appDir": "www",
        "baseUrl": "js/lib",
        ... other options ...
        "modules": [
            {
                "name": "app",
                "exclude": [
                    "jquery",
                    ... other exclusions ...
                ]
            },
            ... other bundles ...
          ]      
        }
      },

Loading dynamically

In some case you'll want to download a different group of modules in a more dynamic way. Based on a certain configurations or user event (like in this example)
For doing this you can create one or more different bundles (with the optimizer).
You need to remember to exclude from the bundles the resource that should be already loaded in the page (use the previous option).

Scripts not bundled

In some case you don't want to include your file in a bundle because is served by a CDN or directly from your application (for example socket.io). The requirejs optimizator uses the "empty:" option to manage this:

    requirejs: {
      compile: {
        options: {
          mainConfigFile: "static/js/main.js",
          baseUrl: "static/js",
          name: "main",
          paths: {
            'socketio': 'empty:',
            'backboneio': 'empty:'
          },
          out: "static/js/main-built.js"
        }
      }
    }

Requirejs is a very good choice to manage scripts loading and dependencies. I hope to have brought light in some of the dark spots of using this in a complex environment.