Often when building a single-page app, you’ll want to optimize all your js into a single asset. RequireJs is a great mechanism for managing your js dependencies. It also comes with a great build tool for doing the optimization (r.js). But sometimes you won’t want to put all your js into a single asset. For instance, perhaps you only want to load a large chunk of code when the user interacts with the app so that you know he intends to use that functionality, and so you load it dynamically. But, you still want that dynamically-loaded set of modules to be optimized to increase the performance of your app. It’s a pretty simple desired functionality, but I didn’t just stumble upon the solution.
In my case, those dynamically-loaded collections of modules are written, potentially, by 3rd parties to be included in my webapp, requested dynamically at runtime. Thus, I optimize my app, and each of the widgets that might appear on my app are optimized individually and independently.
Once you run your RequireJs modules through the r.js build process via:
What used to be a single module per file, will now look something like this:
1 2 3 4 5 6 7 8 9
All of your define blocks now live in a single js file.
Require Optimized Modules
How should you request such a thing. Well, if you were going to request
MyWidget.js dynamically from your
App.js, it would normally look like this (non-optimized):
1 2 3 4 5 6 7
This will work fine in a dev, non-optimized module world, but as soon as you want to require an optimized module like this, you’ll start having
Widget come back as
undefined in your require callback.
Modules vs. Scripts
Maybe the old thinking cap was broken, but I could not figure out why this was for a while. Now that I know the solution, the principle is simple and I’ve seen it hundreds of times before in RequireJs: A RequireJs module will be made available via an alias in the require callback. Everything else (eg, a regular js script file) will not. And it turns out that when your optimize your widget, so that the resulting
MyWidget.js file includes multiple define blocks, suddenly RequireJs no longer sees it as a module; it’s a script.
But, as soon as your script is loaded, the callback is still fired, and the multiple defines that you just requested are available to the app from that point on. So, the final code ends up looking something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13
So now the code covers two situations: dev mode and optimized mode. In dev mode, there is one define block per file. Modules are requested. Easy cheesy. In optimized mode, the first require loads the script (not seen as a module). So, if the module alias in the callback is undefined, we have to require the widget again once that widget’s define block is available to the code. On the 2nd require, that script is already in memory, so there is no network request, but we can finally get a handle on
MyWidget via the module alias in the callback.