This is a sequel for the Journey From RequireJS to Browserify post.
After publishing the previous post I got a lot of feedback saying that Browserify can’t do asynchronous module loading. Since that’s something I’d like to have with Browserify too I started looking on how to do it and after couple of pull requests and one published npm module later I’m happy to say it’s very much possible now!
Background
Basically asynchronous module loading can be done just by creating multiple bundles with Browserify and loading them with a script loader of your choosing. There is one in jQuery or if you don’t like jQuery there are quite a few standalone ones out there.
Loading shims for old browsers
This is a simple use case and it has almost nothing to do with Browserify. For
example if we are using Function.prototype.bind
in our app and we want to
load es5-shim if the browser is missing the implementation:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Conditional bundle loading
We might have different bundles for different browsers. Often on modern browsers we can rely on the native implementations and skip the Javascript shims or polyphills that are required for the older ones. We could for example inlude a custom lightweight jQuery build to our “modern browser” bundle or go little extreme and use Zepto in place of jQuery for modern browsers like we do here.
To detect modern enough browsers for Zepto we check for
document.querySelector
implementation.
To do this we need to build simple lightweight entry point with a script loader and two versions of the main bundle. One with Zepto and one with jQuery.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
In index.js we just detect which one we want to use and load it:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
In our app code we need to abstract the require calls to jQuery and Zepto to make it smooth:
1 2 3 4 5 6 7 8 9 10 |
|
And in app code just use var $ = require("./jquery-or-zepto");
to get the
correct one depending on the bundle we loaded.
Lazy loading rarely used parts
Now this is where things get really interesting.
Lets say our app has some graph view which requires a large graphing library and we want to load that library and related code lazily only when the user actually uses the feature like this:
1 2 3 4 5 6 7 8 |
|
To do this we have to remove the ./graph-view
and its dependencies from the
main bundle. Here’s where my module, externalize, comes to help.
We create a second Browserify bundle which is a subset of the main bundle and
use the externalize
function to remove code from the main:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Now just include ./bundle/main.js
to the page and ./bundle/graph.js
will
be loaded lazily when needed.
Checkout the externalize readme file for more information.
Putting it all together
I’ve uploaded a more real worldish example on github pages using the techniques presented here. It will also serve as a decent Backbone.js and Handlebars example with Browserify. It’s heavily commented so it should be easy to follow even if you don’t know/care anything about Backbone or Handlebars.
Open up devtools from your browser and look at the network tab to see what happens when you hit to “Toggle graph” button for the first time.
I encourage you to read the source code of the example in following order:
- build.js
- Build script for the bundles
- index.js
- This is the first thing executed on the page
- It determines whether to load jQuery or the Zepto version of the main bundle and whether to load the shims
- main.js
- The actual application code starts here
- It lazy loads the graph code on first time it is used
- Compiled bundles
- Take a look at their sizes and skim through what they contain
Before going crazy with it
Before slicing up your Browserify bundles you should really sit down and think through whether this is really required for your app. For example if you add a largish image to your app it might easily dissolve all the benefits you gained by slicing the bundles. Even a single 100kb image will translate to a quite big chunk of minified and gzipped Javascript code. And because this can get very complex if you go crazy with it you should make sure it is worth it.
Happy hacking!