Reusing Web Modules with RequireJS

why

  • Our application┬ácontains┬áseveral modules that uniquely addresses a key aspect of wealth management. eg: Investment checkup, Net worth, etc.┬áThe application brings together various such aspects to present a comprehensive one-stop wealth management tool to the user.
  • How-ever each user’s needs are unique and so are the motivations to use our application. So we wanted to build landing pages that ties a user’s need to a module thus providing more context and motivation to use the module and eventually our entire offerings.
  • We went native for our mobile presence to provide great user experience and that has served us well. How-ever they are few modules of our application that are self contained with simpler ui design and fewer interactions with rest of the application. We wanted to embed such modules in our native app and determine if we could still deliver a great seamless user experience.

what

  • Build “stand-alone” versions of our web modules.
  • Publish the module with a unique url that will be used to embed it in other apps.

how

We use Requirejs as our module loader and its optimization tool to package a module and its dependencies into single units for production use. The module definition would define all its dependencies. During development phase, all the dependencies are loaded as required. How-ever in production mode, all the dependencies are packaged into a single file along with the module code to minimize our http calls and optimize load times. For eg:

A simple module definition would be as follows:

define([
 'jquery'
 , 'underscore'
 ], function($, _){

 var foo = {};
 // module code
 return foo;
});

And we reference the above module as follows:

<script data-main="scripts/foo" src="scripts/require.js"></script>

But most real-world application will have several modules and would share considerable number of framework libraries (jquery, underscore, backbone and etc) and utility libraries. And our application is no exception. So we defined a common module that would require all the common libs and in turn require this as part of our main module. The sub modules would still continue to list of all its required dependencies, but as part of the build process, we exclude them for optimization and to prevent from any shared lib being re-initialized/reset.

When the application starts, it loads the main module first and there after required modules as user navigates the application. Following is a gist of our build file is defined:

({
 modules: [
 { 
 name: 'app_main'
 }
 , {
 name: 'foo'
 , exclude:[
 'jquery'
 , 'underscore'
 , 'backbone'
 , 'services'
 , 'hbs'
 ]
 }
 ]
})

app_main will be loaded at the start of the application and if a user navigated to a foo module, only then the application will load foo. This worked great for us in keeping our main module lean as we kept adding more features (modules) until the requirement of reusing some of the modules in other apps/clients (mobile).

Fortunately for us, this did not involve lot of work and more importantly did not result in any redundant code. All we had to do was to define another main module that would include our common module and foo module. That is it. We had to refactor our common module a little bit to include only the core libs. Other shared libs were loaded as part of the main module (app_main). This way we did not load any libs that were not needed for foo module.

({
 modules: [
 { 
 name: 'app_main'
 }
 , {
 name: 'foo'
 , exclude:[
 'jquery'
 , 'underscore'
 , 'backbone'
 , 'services'
 , 'hbs'
 ]
 }
 , {
 name: 'app_foo'
 }
 ]
})
//app_main.
require(['common'], function (common) {
 require(['main']);
});

//app_foo
require(['common'], function (common) {
 require(['foo']);
});

This was just not fortune. From the very start of this app development, we not only aimed for modular code, but also to have an ability to extend any module to work by itself. Our entire MV* stack was architected that way and we are glad the returns are truly amazing.

We have a SPA that is comprised of several modules and at any time, any module can be extended to its own SPA app with minimal effort and no codes changes to the modules themselves. Thanks to the amazing requirejs optimization tool and a little foresight.

More details on how we use requirejs and its optimization tools are presented here.

reads

http://addyosmani.github.com/backbone-fundamentals

http://requirejs.org/docs/optimization.html

http://requirejs.org/docs/optimization.html#wholemultipage

http://requirejs.org/