Why do concatenated RequireJS AMD modules need a loader?
Asked Answered
D

5

37

We love RequireJS and AMD during development, where we can edit a module, hit reload in our browser, and immediately see the result. But when it comes time to concatenate our modules into a single file for production deployment, there apparently has to be an AMD loader still present, whether that loader is RequireJS itself or its smaller partner “almond” as explained here:

http://requirejs.org/docs/faq-optimization.html#wrap

My confusion is: why is a loader necessary at all? Unless you have very unusual circumstances that make it necessary for you to make require() calls inside of your modules, it would appear that a series of AMD modules could be concatenated without a loader present at all. The simplest possible example would be a pair of modules like the following.

ModA.js:

define([], function() {
    return {a: 1};
});

ModB.js:

define(['ModA'], function(A) {
    return {b : 2};
});

Given these two modules, it seems that a concatenator could simply produce the following text, and not burden the production server or browser with the extra bandwidth or computation required by either RequireJS or Almond.

I imagine a concatenator that produces (and I am using chevron-quotes «,» to show where the snippets from the two modules above have been inserted):

(function() {
    var ModA = «function() {
        return {a: 1};
    }»();
    var ModB = «function(A) {
        return {b : 2};
    }»(ModA);
    return ModB;
})();

This, so far as I can see, would correctly reproduce the semantics of AMD, with a minimum of extraneous glue JavaScript. Is there such a concatenator available? If not, would I be a fool for thinking that I should write one — are there really very few code bases that consist of simple and clean modules written with define() and that never need further require() calls inside that kick off later asynchronous fetches of code?

Depict answered 11/6, 2012 at 21:19 Comment(1)
How did you solve this problem? I found that using almond, the min file is bigger by 3k then the concatenated file (9K vs 6K).Extinct
C
14

An AMD optimiser has the scope to optimise more than the number of files to be downloaded, it can also optimise the number of modules loaded in memory.

For example, if you have 10 modules and can optimise them to 1 file, then you have saved yourself 9 downloads.

If Page1 uses all 10 modules then that's great. But what if Page2 only uses 1? An AMD loader can delay the execution of the 'factory function' until a module is require'd. Therefore, Page2 only triggers a single 'factory function' to execute.

If each module consumes 100kb of memory upon being require'd, then an AMD framework that has runtime optimisation will also save us 900kb of memory on Page2.

An example of this could be an 'About Box' style dialog. Where the very execution of it is delayed until the very last second as it won't be accessed in 99% of cases. E.g. (in loose jQuery syntax):

aboutBoxBtn.click(function () {
    require(['aboutBox'], function (aboutBox) {
        aboutBox.show();
    }
});

You save the expense of creating the JS objects and DOM associated with the 'About Box' until you are sure it's necessary.

For more info, see Delay executing defines until first require for requirejs's take on this.

Centrist answered 11/6, 2012 at 23:5 Comment(5)
Interesting! So, if I understand what you are saying, you are positing code that creates objects and modifies the DOM merely as a side-effect of being required, is that correct? I would not have thought of that, and if that practice is common, then you might well have hit upon the reasons that JS coders like having the AMD loader there in production. Probably because of my Python background, none of my code ever executes when imported — it simply returns a object full of functions to call when their side effects are finally needed.Depict
Your example about memory use is often confounded by I/O overhead – for something like that about box it seems like it'd be a win to load earlier but defer initialization so you can save memory and setup without a big interactive latency hit.Denigrate
@Chris, I agree with your points but just wanted to present a case for having an AMD loader present at runtime. I might be worried about too much code executing on startup, whereas you might be worried about latency. Apart from trivial cases, I don't think there's a one-size-fits-all when it comes to performance. And there wouldn't be a latency hit in my example, as all the JS has been loaded, just not 'executed'.Centrist
@Brandon, yes that's the gist. Obviously one's mileage varies with the amount of work done in the module's 'factory function'. If there's not much work done, then the benefits are much reduced. But if it's a particularly complex module then delaying execution of that module's 'factory function' until it's actually required could be a boost. Also, the article I linked to mentions the order in which the defines are executed should not always be the order in which they are encountered in the optimised JS file.Centrist
@PaulGrime: strong agreement on the no one-size-fits-all. No substitute for profiling your app and actually testing different approaches.Denigrate
D
1

The only real benefit is if you use modules across sections so there's a benefit to caching modules independently.

Denigrate answered 11/6, 2012 at 23:26 Comment(0)
B
1

I had the same need, so I created a simple AMD "compiler" for that purpose that does just that. You can get it at https://github.com/amitayh/amd-compiler

Please note that it has many features missing, but it does the job (at least for me). Feel free to contribute to the codebase.

Brute answered 10/5, 2014 at 10:32 Comment(0)
B
0

In case you compile you code with require.js into a single large file for production, you can use almond.js to completely replace require.

Almond only handles the module references management not the loading itself which is no longer needed.

Be careful of the restrictions almond imposes in order to work

Brickle answered 16/11, 2012 at 9:26 Comment(0)
N
0

There is no reason why there couldn't be a build tool such as the one you propose.

The last time* I looked at the optimizer's output, it converted the modules to explicitly named modules, and then concatenated those together. It relied on require itself to make sure that the factory functions were called in the right order, and that the proper module objects were passed around. To build a tool like you want, you would have to explicitly linearize the modules -- not impossible, but a lot more work. That's probably why it hasn't been done.

I believe** that the optimizer has a feature to automatically include require itself (or almond) into the built file, so that you only have to have one download. That would be larger than the output of the build tool you want, but otherwise the same.

If there was a build tool that produced the kind of output you're asking for, It would have to be more careful, in case of the synchronous require, the use of exports instead of return, and any other CommonJS compatibility features.

*That was a few years ago. 2010, I think.

**But can't seem to find it right now.

Nerland answered 11/1, 2013 at 17:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.