Search code examples
requirejsr.jsrequirejs-optimizer

RequireJS optimization into multiple modules


I am trying to use RequireJS to modularize our JS into a few larger JS files so that dependencies can be loaded on-demand but without the heft of a large single-JS download when loading the page.

As an example, with the JS files as:

  • js/main.js
  • js/views/category1/js1.js
  • js/views/category1/js2.js
  • js/views/category2/js1.js
  • js/views/category2/js2.js

Some parts of the application (e.g. everything in category1) are only used by certain types of users, and similarly with category2, so it doesn't make sense loading it all for every user.

I'm trying a build config for r.js to create two dynamically loaded modules (category1.js & category2.js) which contain all the code from their respective js1.js and js2.js files.

({
appDir: './',
baseUrl: './js',
dir: './dist',
modules: [
    {
        name: 'main',
        exclude: [
            "category1",
            "category2"
        ]
    },
    {
        name: "category1",
        include: [
            "views/category1/js1",
            "views/category1/js2"
        ],
        create: true
    },
    {
        name: "category2",
        include: [
            "views/category2/js1",
            "views/category2/js2"
        ],
        create: true
    }
],
fileExclusionRegExp: /^(r|build)\.js$/,
writeBuildTxt: false,
optimizeCss: 'standard',
removeCombined: true,
paths: {
    jquery: 'lib/jquery',
    underscore: 'lib/underscore',
    backbone: 'lib/backbone/backbone'
},
shim: {
    underscore: {
        exports: '_'
    },
    backbone: {
        deps: [
            'underscore',
            'jquery'
        ],
        exports: 'Backbone'
    }
}
})

However, when loading the compiled output, the browser is complaining that it can't find views/category1/js1, views/category1/js2 and so on.

I'm not even sure it's possible to create a few high-level modules from multiple smaller JS files in RequireJS. Has anyone had any experience with this?

EDIT

main.js:

require.config({
    shim: {
        underscore: {
            exports: '_'
        },
        backbone: {
            deps: [
                'underscore',
                'jquery'
            ],
            exports: 'Backbone'
        }
    },
    paths: {
        jquery: 'lib/jquery',
        underscore: 'lib/underscore',
        backbone: 'lib/backbone/backbone'
    }
});

require([
    'views/app',
    'router'
], function(AppView, Router) {
    new Router();
    Backbone.history.start();
});

index.html:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <script data-main="js/main" src="js/lib/require/require.js"></script>
</head>
<body></body>
</html>

The JS1, JS2, etc files are being referenced from router.js.


Solution

  • When you do require(['A'], ...) or you have a define(['A'], ..., RequireJS inspects its configuration to resolve the name A, and builds a URL that it uses to load the module A. It may try to load something like:

    http://localhost/js/A.js
    

    If you optimize your modules so that A, B, C are included in a bundle named category1, you have the following possibilities:

    1. The bundle category1 is loaded, and then a request is made for A. This is fine because A was found when category1 was loadded.

    2. The bundle is not yet loaded but a request is made for A. This is a problem because it will resolve A in the same way as without optimization. But you've got a problem here because if you've optimized your modules, then A is no longer available outside category1 (because removeCombined is true).

    The solution is to tell RequireJS in your run time configuration that the bundle contains the modules you want to load. You use the bundles option for this. For instance:

    bundles: {
      category1: ['A', 'B', 'C']
    }
    

    This tells RequireJS, "when you want to load module A, load category1 and you'll find it in there".

    Note that you don't have to list every single module in the bundle. If you have a module X for instance, and you know that the only modules that will want to load it are part of the same bundle, then you don't need to list it as a module in bundles.