2012.08.06

RequireJS 2.0 Delayed Module Evaluation and Google Maps

The RequireJS behavior changed from version 1.0 to 2.0. Now module dependencies are only loaded after a explicit require() call or if it is on the dependency tree of some of the require‘d modules. That means that the module factory (define callback) won’t execute unless it is needed. That is a huge win for many reasons, less work for the JS engine and code has the same behavior before and after build. It also makes it possible to create alias to complex modules without triggering a download.

On my current project I have ~15 JS widgets that can be placed on any page and the amount of JS required by each widget is small so it makes sense to bundle all the JS into a single file for production (<30KB minified + gzipped excluding jQuery) instead of loading things on demand (a single request have better perf results than multiple requests in many cases).

I will show a very simple technique I used on the project to create an alias to the Google Maps API since I think it can be useful to other people as well in different contexts.

Loading Google Maps with RequireJS

Google Maps v3 API loads multiple JS files, so listening to the first JS file to complete isn’t enough to make sure the whole lib is available, luckily it accepts a callback parameter. Because of that we can use the async plugin to wait until all the files are loaded before executing the callback.

define(['async!http://maps.google.com/maps/api/js?v=3&sensor=false'],
function(){
    // google.maps is ready
});

But in case you need to use Google maps inside multiple modules it will become cumbersome to type this long path all the times. So we can make use of the new delayed module evaluation behavior and simply define a new module inside our top-level file (usually called main.js) to be used as an alias:

// convert Google Maps into an AMD module
define('gmaps', ['async!http://maps.google.com/maps/api/js?v=3&sensor=false'],
function(){
    // return the gmaps namespace for brevity
    return window.google.maps;
});

Now you can require it from inside other modules by simply typing:

define(['gmaps'], function(gmaps){
    // shorter namespace, no need to type "google.maps" all the time
    var map = new gmaps.Map(myDiv, mapOpts);
});

Before v2.0 the technique above wouldn’t work. You would need to add a nested require to avoid loading the Google Maps API when not needed, also defining a module would automatically trigger the download of all the dependencies. The current behavior was a huge improvement and made AMD modules even more flexible. #winning

Notes

The Google Maps API will only be loaded when needed (when any module that requires the gmaps module is needed). It is important to notice that the async plugin will NOT work with almond.js (even tho it doesn’t use RequireJS internal methods for script loading, almond can’t handle asynchronous plugins).

We should usually avoid named modules to make code more portable - that way we can move scripts between folders/projects, rename them and create aliases - but in some cases it can be an extremely useful feature. In this case since the amount of code is minimal I preferred to use a named define instead of a separate file to keep it near to my requirejs.config call (since it’s just an alias), but it would also work as a separate file if you think that would be a better approach for your app structure. - My main.js usually contains only the config options and the minimal logic possible to start the app, use what makes more sense in your case.

It is awesome to be able to pack all the JS files into a single file and still have the option to defer some of the loading if needed, check my comment on Ben Nadel blog post about Lazy Loading AMD modules for production to get a better idea of when/how to do it.

AMD works well and is flexible. RequireJS learning curve is a little bit steep (too many config options and different from what most JS devs are used to) but nowadays I can’t see myself going back to naive script concatenation, large source files and awkward namespaces (eg. foo.utils.bar.doSomething()).

If you don’t need to lazy-load scripts you could use Almond.js instead of RequireJS for production since it is way smaller (Almond is under 1KB) but beware that not all features are available (the async plugin for instance doesn’t work).

Embrace AMD and be happy.

Further reading

Edit (2012/08/08): made it clear that almond can’t handle the async plugin.


Comments

[...] diving into Require.js to build a Google Maps application using this link and this link as a guide. My goal is to build a modular application that loads “base” [...]

Bryan Armalavage

Thanks for the article. Very helpful for me as I was just struggling to get Google Maps going with Require.js and Backbone. Still working out some kinks, but I'm well on my way!

Thanks for the great article. You state explicitly that this will not work with almond.js. Do you have any solutions for this? I am using require.js, but when my application is deployed, require.js is swapped out with almond.

Is it at all possible to make the URL following async! dynamic? I would really like to be able to do this:

define(['myAsyncModule', 'async!{my.dynamic.url.from.a.config.file}'], function(){...});

The async URL that I need to reference has to change based on building the code for different environments. I was going to try filtering the value in, via a Maven build profile, but I was wondering if you had any insight on this.

Thanks!

@Tyrone almond really doesn't work with any async dependencies, no way to solve it without forking almond and implementing this feature.

@Ace the async plugin doesn't support the RequireJS paths and map configs for now, but it is definitely doable, I created a feature request for it. The RequireJS mailing list is a good place to ask questions. Cheers.

[...] read the following article on how to get google maps, and gmaps.js to work with require.js. However, when I build my project, [...]

I really spend 2 days figuring out why my map doesn't display. (Using backbonejs and requirejs)

Only after a minute or 2 it displays with completely first with gray tiles, then completely. Very strange. I now come to the conclusion that i have to stop trying to fix this. I just can't get it to work.

But thanks for the writeup!

This works wonderful in dev enviroments, but once you have to build a prod version AKA: minifiy and uglify this doest work anymore,since yo cant shim it, any hint to handle this?

[...] RequireJS Site Why use Web Modules How to load Google Maps with Require JS RequireJS Jquery [...]

Just wondering if the issue with almond.js could be solved by loading in google maps with the requirejs-promise plugin vs the async plugin?

https://github.com/jokeyrhyme/requirejs-promise

[…] That allows us to request the GMaps API asynchronously but to set up the dependency our plugin has, we are going to need to define it as a module like this (thank you Miller!): […]

Leave a Comment

Please post only comments that will add value to the content of this page. Please read the about page to understand the objective of this blog and the way I think about it. Thanks.

Comments are parsed as Markdown and you can use basic HTML tags (a, blockquote, cite, code, del, em, strong) but some code may be striped if not escaped (specially PHP and HTML tags that aren't on the list). Line and paragraph breaks automatic. Code blocks should be indented with 4 spaces.