Inject Add-Ons And Plugins With RequireJS, Non-AMD Libraries And Shims

I’m using a few libraries that don’t have official support for AMD, in my current project with RequireJS. No problem – just shim the library in, and it’s good to go. Some of these libraries also need some add-ons and plugins, though… and there aren’t necessarily official hooks for the add-ons, either. So I find myself in this situation: I need a non-AMD library with non-AMD add-ons, loaded through a single require statement in my module. What to do?

Named Dependencies

In your app configuration for RequireJS, you can name dependencies to make it easier to load them later. I do this for my 3rd party libraries and other common things in the app. 

Example:

requirejs.config({
  paths: {
    jquery: "vendor/jquery",
    rsvp: "vendor/rsvp",
    kendoui: "vendor/kendo.all",
    thatCoolLib: "vendor/thatCoolLib"
    // ... etc
  },
  
  // ...
});

I’ve named “jquery”, “rsvp”, “kendoui” and “thatCoolLib” (a made up example name for this post) here, so that I don’t have to load them by the full path, later.

Now I can include these libs in my other modules with just the name.

define([
  "jquery",
  "rsvp",
  "kendoui",
  "thatCoolLib"
], function(jquery, rsvp, thatCoolLib){
  
  // ...
  
});

Now let’s say that I now want to load an add-on for thatCoolLib, by including yet another file in the project. Perhaps I want to add a new function to an existing prototype from that library. How do I do that?

Hijacking The Named Dependencies

Say I’ve got this awesome plugin for thatCoolLib:

thatCoolLib.SomeType.prototype.myMethod = function(){
  console.log("I'm doing cool stuff, here");
};

I want to get this loaded in to the library whenever I require “thatCoolLib”, but I don’t want to specify yet another dependency in my modules. I just want this add-on to be available, always.  To do that, I can hijack the named dependencies, and compose the modified version of thatCoolLib at runtime.

Rename the current “thatCoolLib” in the requirejs config, to “thatCoolLib.original” or any other name that would say this is the original version. Keep it pointing to the same file, though – just change the name.

Now add another named dependency, called “thatCoolLib” – yes, the name that you just changed – and point it to a new file. In this case, I’ll point it to “vendor/thatCoolLib.bootstrap”. 

requirejs.config({
  paths: {
    jquery: "vendor/jquery",
    rsvp: "vendor/rsvp",
    kendoui: "vendor/kendo.all",
    "thatCoolLib.original": "vendor/thatCoolLib",
    thatCoolLib: "vendor/thatCoolLib.bootstrap"
    // ... etc
  },
  
  // ...
});

Create the “thatCoolLib.boostrap.js” file, next. Add a standard AMD module to it, and require the “thatCoolLib.original” in to the module. Now you can modify the original object with your add-on code, and then return thatCoolLib from your new module. You can even include other external files, and attach the functions to the library inside of this module.

define([
  "thatCoolLib.original",
  "vendor/anotherCoolFunction"
], function(thatCoolLib, antoherFunc){
  
  thatCoolLib.SomeType.prototype.myMethod = function(){
    console.log("I'm doing cool stuff, here");
  };
  
  thatCoolLib.SomeType.prototype.anotherFunc = anotherFunc;
  
  return thatCoolLib;
  
});

New Behavior Without Modifying The Rest Of The App

Now when any part of your app requires “thatCoolLib” in to a module, it will get the new behavior from the plugins. But the best part, here, is that any existing use of “thatCoolLib” will also have the new behavior available, without having to change any of the dependencies, or any any new dependencies, in the existing modules.


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Javascript, requirejs. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://ifandelse.com Jim Cowart

    You can also do something along these lines if you prefer to not worry about name hijacking, etc:

    1.) include the original lib you want to extend in your project
    2.) take a dependency on the original lib in your extension lib
    3.) ensure that you include your extension lib before the extension is needed

    (for example – you could include your extension lib in your main.js’s require call dependencies, or if the extension is only used in one place, include it in that module’s dependencies along with the original lib.

    Since all deps have to be resolved in a module before the factory method is invoked, the extension will have added it’s stuff to the original lib before your code using the extension is invoked….

    • http://mutedsolutions.com Derick Bailey

      i had something like that at first and it bothered me. i didn’t like having to load up a dependency at some point in the app where it wasn’t needed, just to get the plugin in place. i like it better that my dependency just includes the plugin at the point where i need the dependency in the first place.

      • http://ifandelse.com Jim Cowart

        Yeah, I understand. The only thing I’d point out is that in a non-AMD app with lots of jQuery plugins, for example, you’re doing exactly that: loading the dependency up ‘at some point in the app where it wasn’t needed just to get the plugin in place’. The potential drawback to hijacking the name is that you might have multiple plugins (jQuery is a great example of this, IME). So you can:

        1.) have a pipeline of hijacking (which is awful & not performant), or
        2.) a single “plugin loader” that takes a dep on all the plugins and the main lib and adds them to the lib in the loader module, or
        3.) you can include individual plugins in the specific modules that use them alongside the original lib that modify. The more I’ve worked on require.js projects the more I find myself moving from #2 to #3. In the case of jQuery, most plugins were obviously DOM related, so it made sense to include them in, for example, a base Backbone View module, and wrap plugin access via View helper methods (thus, eliminating the need to hit a global $ object and scoping all DOM interaction to the node the view managed). keeping the deps close to where they are used also made it clear in the module (like that view, for example) what dependencies were really involved (since it wasn’t hidden from the deps list). It also allows for tighter control over concatenation/minification (for ex – if you break your AMD build up into higher level site sections, you don’t have to download plugins with your “customer” section build if they aren’t used there, but are used in another section).

  • Michael Fasani

    How easy is it to get these Gists into your blog? :-)

    • http://mutedsolutions.com Derick Bailey

      very easy! there’s a WordPress plugin for it, or if you’re on another setup, you can just copy the embed code from the gist directly. super simple.

  • Simon Boudrias

    I usually prefer to use Requirejs ‘map’ configurations to point a lib name to a facade loading plugins. Depends on the use case, but I feel it’s cleaner this way. (BTW this pattern is super usefull in internationnalization context whee you want to load a module with a certain language)

    • http://www.surfulater.com/ nevf

      Simon, could you provide a code example for this. A gist would be great.

  • simonsmith

    I would recommend using the map config for this. A good example exists on the RequireJS site – http://requirejs.org/docs/jquery.html#noconflictmap

  • adrian

    ” just shim the library in, and it’s good to go. ” cant even get past step 1. how is that done