Player.me Blog
amd_and_require

As the CTO, I have to make choices for the foundation of the site.. but like everyone else, sometimes we make mistakes and it takes too long to figure out, and you have to take the decision to continue down the rabbit hole, or GTFO of it ASAP.

Instead of crying on my own and defending my bad choice, I will explain my opinions in a series of blog posts. I hope there’s only going to be a very few of them.. I also hope it can help you in the future.

Today, we’re going to talk about the Javascript loader that I decided to use, RequireJS

RequireJS is a JavaScript file and module loader. It is optimized for in-browser use, but it can be used in other JavaScript environments, like Rhino and Node. Using a modular script loader like RequireJS will improve the speed and quality of your code.

This post will be a pretty long one; to really understand the downside of RequireJS, you need to see the whole package. Some parts might be confusing if you never used RequireJS, just comment below and I’ll add some more details.

When I started coding and made the folder structure for RequireJS, everything was rainbows and unicorns. From a development point of view, it’s seriously awesome. You just require the file, and it works in the browser. Modify the file, refresh, changes are there! Magic. It only includes the files you need.

My philosophy was always to be developer friendly. Code should be easy to code, easy to read and understand, easy to deploy.

RequireJS fits into easy to code, easy to read and understand.. but everything breaks when it comes to deploying.

You need to ‘shim’

We need jQuery. We also need jQuery plugins. Want to make both works? Use shim.

And then you can use it like this.

define(["jquery", "jquery.alpha", "jquery.beta"], function($) {
    //the jquery.alpha.js and jquery.beta.js plugins have been loaded.
    $(function() {
        $('body').alpha().beta();
    });
});

Okay seems simple enough.. Just a couple of config file changes. Let’s continue coding.

Your folder structure needs to be revamped

Your site is done. Wohoo! Let’s put it on staging and see how it goes… Dynamic loading is a cool idea.. but terrible in execution.

Let’s take an example: cameraguyalex.

That’s 82 javascript files getting used in there. You either have the choice of making a script for that page, or loading a ton of small scripts.., or assemble them!

Of course, the answer to this is to assemble them.. but then, out of those 82 scripts, 30~ of them are globally used. It would make sense to make 2 files, a common script files, and a page-specific file.

So… how do we achieve that with r.js? RequireJS has this example.

If you want to use shim config, for instance to load Backbone, see the requirejs/example-multipage-shim example instead. This project will not work well with shim config. This project works best when all the dependencies are AMD modules.

So if we want to use common scripts, and at the same time use shim, we have to change our whole folder structure, and use this one.

This is the proposed structure:

js
  app
     homepage.js
  homepage.js

js/homepage.js

require(['common'], function () {
    require(['app/homepage.js']);
});

js/app/homepage.js

define(function (require) {
    var $ = require('jquery');

    // homepage logic goes here
});

Yep, you have 1 file that feels really redundant.. and you have to have them for each pages.

Not only that, but this syntax is not usable anymore:

define(["jquery", "jquery.alpha", "jquery.beta"], function($) {
    //the jquery.alpha.js and jquery.beta.js plugins have been loaded.
    $(function() {
        $('body').alpha().beta();
    });
});

If you had the bad luck of coding before trying to deploy (like me!), you’ll have a bad day. Instead, you have to use them like this:

define(function($) {
    var $ = require('jquery');
    require('jquery.alpha');
    require('jquery.beta');
});

And you also have to put this for each page, in your gruntfile.

{
    name: 'about',
    include: ['app/about'],
    exclude: ['common']
}, 

So.. 1 new page means 1 extra file and an extra grunt config. Okay.

If it works on the dev, it doesn’t mean it will work in production. Also: forget bower.

So now we hacked up our structure and code to make it work in a bundled/shim scenario. Cool.

But, as the title says: It doesn’t always work. This is a killer.

There’s primarily 3 reasons it failed:

  1. I forgot to include the jquery plugin, but I used it in another module so it’s loaded in the dev environment.
  2. For some reason, shims doesn’t always work.
  3. I forgot to include it in the gruntfile.

The first and third reason is a human mistake, okay.

The second reason is a real head banger. I thought shim was supposed to work?

The solution: wrap every plugin with this:

define(function(require) {
    var jQuery = require('jquery');
    var $ = jQuery;

    // plugin goes here
});

Sometimes you’ll be lucky and the plugin dev will use UMD. If not, tough luck. I don’t take chances anymore, so I just wrap them all.

You still need that require.js

Even though you “compiled” everything into 2 files, you still need that requirejs. It’s an extra 10kb.. and no, you can’t use almond because we have 2 files and almond only works with a very specific scenario.

Conclusion

There’s a few more downside that I omitted since it’s very specific to us (like ReactJS server rendered templates).

So.. should you use RequireJS? I have no idea, but it’s definitely not for us. A large web app needs a build system anyway, so might as well take a loader that had that in mind.

What to do from here? I’ll probably take a weekend and convert everything to Browserify. I’ve heard good things about it.

If you have any comments or questions, please hit us up below. If you think I’m doing something wrong, definitely please hit us up below.

Here’s a proof of concept on github: http://maktouch.github.io/requirejs-shim-fail/

Cheers!

Edit

Reddit user tbranyen helped me out. It seems like I have do an inline recursive nested require calls for it to work properly, ensuring that the common file is loaded first. It works! I’m still wary of it, and will probably not use requirejs in my next project, but it’s nice to see a solution to one of the problem.

About author View all posts Autor website

Makara Sok

Makara Sok

Mak is the master of our stack and the company joker. When he’s not striving to be the architect of the sweetest code, he’s busy telling tales of his soapy adventures.

12 CommentsLeave a comment

  • What is the context for your shim config not working? I’ve had plenty of issues with require.js, and am certainly not in love with it, but I’ve always been able to resolve them(possible headbanging required) I use it in the context of a multi-page app, reduce it with r.js and deploy it via grunt. nnEDIT: Just to clarify I ask because I’ve had similar issues with Shims, but again have always been able to resolve them with help from the documentation.

    • Badly written jQuery plugins that uses both $ and jQuery variables, or uses global variables. The kicker is that it’s inconsistent. Sometimes it works, sometimes it doesn’t. Seems like a race condition of whether they get loaded before or something. nnEither way, they get resolved when I wrap them around within the define tags. nnHonestly, I shouldn’t be trying to resolve. RequireJS is wasting too much time tinkering stuff that should be working. Just trying to make r.js work with grunt took wayyy too much time.

        • I like your assertion that it works with ANY plugin, as if you’ve tested all of them. nnnhttp://dimsemenov.com/plugins/magnific-popup/nnnnWhen I remove the define wrap around that plugin, and only shim it in config, it doesn’t work. nnnhttp://imgur.com/Lhfz7Q6nnnnWhen I wrap it around define, it works. =|.

  • Not sure how you have shim issues. when compiled, it just defines a variable, in order of deps, and then returns that global inside a define. Inconsistencies with jQuery should not affect anything as stated in a prior comment. Now that Backbone and Underscore are AMD, the shim definitely gets smaller, and easier to manage.nnYou’re right, RequireJS isn’t for everything, but it is nice to use in complex web apps I build everyday. It was a struggle to get used to it at first, but once understood, it’s a life saver. One of the coolest things we’ve implemented in the stuff I work on is accessing global variables between iframes. Not monumental, but holy hell does it make my life easier

    • I called it wrong, Tim corrected me. My page specific scripts loaded before my common. It somehow worked in dev, but not when bundled. nnnI think requireJS is better than using old school-nothing, but I’ll definitely be looking out for an alternative for my next project. nnnCheers and thanks for your comment.

      • No worries, I’ll take a look at your link. Learn something new everyday! Good article by the way. I’m really looking forward to the ES6 loader. Once it comes out, the only thing standing in my way would be IE (stupid IE)

Leave a Reply

Your email address will not be published. Required fields are marked *