Skip to content

Instantly share code, notes, and snippets.

@rhannequin
Created February 15, 2013 23:16
Show Gist options
  • Save rhannequin/4964396 to your computer and use it in GitHub Desktop.
Save rhannequin/4964396 to your computer and use it in GitHub Desktop.
Try to understand how to use RequireJS with tools creating single minified-concatenated script file.

Context

I have a bit experience in JavaScript development but I'm quite new in modern development tools such as Yeoman, Grunt or Bower. But I can create webapps with RequireJS, separate logic with Backbone.js and use templating with Handlebars. I am aware of what's going on.

I saw that these tools leads you to concat files, which is great, in my case I want to speak about JavaScript files. I can't understand how it works.

I love RequireJS

With RequireJS, I can separate files, for example if I'm using Backbone.js, every model, every view can be a file. If my application is big enough, some pages will need some models and some other pages will need some other models. And thanks to RequireJS I can load only the models (I mean the files, then) I need.

The problem

So, how can RequireJS work with modern tools ? Grunt will make me a single application.js file which content all the concatenated and minified JavaScript files in my scripts directory. In these scripts are some RequireJS instructions like :

require(['my-model'], function (MyModel) {/* some code here */})

But know I have one single JavaScript file and my application is serving only this one. I got a 404 error answer from my my-model RequireJS instruction. Moreover, all my application's JavaScript code is now loaded in every pages, even models I don't need.

Is something wrong with me ?

I can see how powerful these tools are and how the JavaScript (and generally develoment) community is active on them, so I guess I missed something. I guess something is wrong with what I just said because I can find blog articles speaking of "Grunt using RequireJS" but I never find in these articles part of what I'm saying. So I guess I ask wrong questions.

Discussion

I'm looking for someone to explain me why I'm wrong and how to use these wonderful tools. I want Grunt to manage my projects and I want RequireJS to load only code I need and work in develoment and production.

Thank you to all and let's talk!

@kud
Copy link

kud commented Feb 16, 2013

My requirejs grunt config:

// r.js
        requirejs: {
            options: {
                baseUrl: 'app',
                name: 'components/almond/almond',
                include: 'app',
                mainConfigFile: 'app/config.js',
                out: 'build/js/app.js',
                wrap: true,
                findNestedDependencies: true
            },
            dev: {
                options: {
                    optimizeAllPluginResources: false,
                    preserveLicenseComments: true,
                    optimize: "none"
                }
            },
            prod: {
                options: {
                    optimizeAllPluginResources: true,
                    preserveLicenseComments: false,
                    optimize: "uglify"
                }
            }
        },

My config.js used by requirejs:

/**
 * Configure your app, mate.
 */
require.config({
    deps: ['app'], // First script called
    baseUrl: 'js', // Scripts folder
    paths: {
        // Libraries supported by Bower (http://twitter.github.com/bower/)
        zepto:          'components/zepto/zepto',                           // http://zeptojs.com/
        underscore:     'components/lodash/lodash',                         // http://lodash.com/ instead of underscore for better performance
        backbone:       'components/backbone/backbone',                     // http://backbonejs.org/
        moment:         'components/moment/moment',                         // http://momentjs.com/
        momentFr:       'components/moment/lang/fr',                        // http://momentjs.com/
        lazyload:       'components/lazyload/lazyload',                     // https://github.com/fasterize/lazyload
        //q:              'components/q/q',                                   // https://github.com/kriskowal/q
        modernizr:      'components/modernizr/modernizr',                   // https://github.com/Modernizr/Modernizr
        handlebars:     'components/handlebars.js/dist/handlebars.runtime', // http://handlebarsjs.com/ Version for precompiled templates
        swfobject:      'components/swfobject/swfobject/src/swfobject',     // http://code.google.com/p/swfobject/
        fastclick:      'components/fastclick/lib/fastclick',               // https://github.com/ftlabs/fastclick
        //hammer:         'components/hammer/hammer',                         // https://github.com/EightMedia/hammer.js

        hb:             'templates/templates',                              // Hack. It will call handlebars + handlebars templates,
        helper:         'helper',                                           // Some own great helpers ;)
        global:         'global',                                           // Global stuff
        common:         'common'                                            // Common stuff
    }
});

A model example with requirejs:

/**
 * Tweet
 * @type {Model}
 */
define([
    'backbone',
    'moment',
    'helper'
], function( Backbone, Moment, Helper ) {

    var Tweet = Backbone.Model.extend({
        urlRoot: 'http://' + Helper.getDomain() + '/api/tweets',

        url: function() {
            return this.urlRoot + '/' + encodeURIComponent(this.id) + '/';
        },

        /**
         * @return {string} htmlised content
         */
        getHtmlisedContent: function() {
            return this.get('content')
                .parseTwitterURL()
                .parseTwitterUsername()
                .parseTwitterHashtag();
        },

        /**
         * @return {string} since
         */
        getSince: function() {
            return Moment.unix(this.get('time')).fromNow();
        }
    });

    return Tweet;
});

@kud
Copy link

kud commented Feb 16, 2013

My next step is not to use the single app.js on dev but use require.js as "normal" way (without compilation)

@rhannequin
Copy link
Author

Thank you @kud for your answer.

What about in production ? You said you do not use the single compiled app.js file on dev. Then I guess you can do

require(['models/Tweet.js'], function (Tweet) {/* Some code */});

on dev environment and it will work.

When your website is on production, you don't modify your code to remove this RequireJS instruction. But as you use Uglify in you optimize option, your application will serve serve the single compiled app.js file and your RequireJS instruction will answer a 404 error, am I right ? This is exactly in this case I don't understand how to use an uglifier and RequireJS at the same time.

@PaulLeCam
Copy link

Hey Remy!

Thing is RequireJS references modules IDs, not URIs, it falls back to trying to load by URI only if the ID is not already defined.
The optimizer will create these IDs for you if you have not set them, so the modules in the compiled app.js will work as expected.

@rhannequin
Copy link
Author

Hey Paul, thanks!
You mean, if I configure all my modules in the require.config object like

{
  ...
  TweetModel: 'models/Tweet',
  ...
}

RequireJS will load the code even if the file doesn't exist (in production) ?

I won't to this but just to understand better how RequireJS works, what if I have two models in the same file in development environment but I want to load only one model in my page ? How must I create my require.config object to make RequireJS understand which code to load ?

(Btw, let me know how you're doing by email ;) )

@PaulLeCam
Copy link

No, you don't even need to do it, the optimizer will do it for you.
RequireJS will not try to load any file because it will already have the module with the required ID, generated by the optimizer based on its URI unless you define it yourself.

If you have your model located in models/Tweet relatively to the config baseUrl, the optimizer will attribute it the ID models/Tweet in the optimized file, so RequireJS will know that it has it already, no need to try to load any file.

If you want to define multiple modules in a single file, it is possible and it would probably work after optimization, but it will not work before because RequireJS will not be able to load the file.

@rhannequin
Copy link
Author

Thank you Paul!
Correct me if I'm wrong :

  • I create a wonderful require.config object with a nice baseUrl
  • I don't need to specify my scripts (like my models, my views, my libs) in the path object
  • I let the optimizer create its requered IDs
  • In development and in production I can load my modules with require(['models/Tweet', function (Tweet) {}]);, the optimizer will know where to find the code

I hope I'm not doing the unable to understand anything guy...

@PaulLeCam
Copy link

Yes, as long as the optimizer has retrieved all dependencies needed by your app, their code will be set in the optimized file and will be immediately available to RequireJS.
If you have dynamic dependencies (files you want to load at runtime), you have to choose either to let RequireJS load them when needed (as it would do during development, so the files have to be present), or to force their presence in the optimized file by adding them to the deps key in your require.config object.

@rhannequin
Copy link
Author

Alright, thank you very much! I'll try to do it in one of my current projects, i'm trying to manage it with Yeoman, so Grunt and Bower, which are three technos I have not already tested. I'll let you know if I have further questions, thank you Paul. And thank you @kud too.

@kud
Copy link

kud commented Feb 16, 2013

The only difference between my dev and my prod should be the call of the main js.

In prod:

<script src="app.js">

In dev:

<script src="config.js" data-main="app.js"> or something like that.

It means that js are called dynamically by require.js

I will manage it via grunt-replace with a template. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment