Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Reconsider AMD support #2641

Closed
timmywil opened this Issue · 39 comments
@timmywil

Since AMD support was rejected back in late 2011 (and more times since then), I think it is time to reconsider adding support for AMD in backbone and underscore. It was hoped that backbone would be on the "right side of javascript history", but if you've been to any javascript conferences recently or if you have been paying attention to the javascript community at all, you will have noticed that AMD will continue to play a large role, especially given that native modules are not part of any spec that has reached recommendation status; it's pretty clear that ES proposals will continue to be revised for a long while before we see anything in browsers. The reasoning provided by Jeremy has since proved mostly invalid (jQuery has confirmed that concerns regarding backwards-compatibility and wide support can be addressed by exporting the global along with AMD). It's not about arguing whether AMD is viable anymore. We've seen AMD supported across the web and win out in more and more popular javascript libraries. Probably the most notable and unfortunate exceptions today are backbone and underscore.

As Addy said in 2011, "your best bets until we have both spec finalization and coverage are AMD (for in-browser modules) and CJS (for those on the server)."

@braddunbar
Collaborator

Hi @timmywil! I use Backbone and Underscore with AMD all the time without any extra support added. If you're using Backbone in your app you can just include it and use the global or use the shim configuration with requirejs. It works just fine and requires nothing extra.

@jrf0110

Thank you for bringing up this conversation again. You're a brave, brave man.

I'm a requirejs user. While it would be fantastic if everyone adhered my developer ideologies, it's just not going to happen. What would be really nice is if Backbone was a collection of different modules that could be required separately. It's silly that Two.js for instance requires the Backbone namespace so it can take advantage of Backbone.Events.

@braddunbar Backbone/Underscore can easily be shimmed no problem. Using a package manager like jam makes the task completely trivial. However, one gets in a little bit of trouble in certain use-cases. When you compile and optimize, depending on how your module dependency tree looks, you can run into load-order problems because of the shim caolan/jam#121

Shimming is a sham. It works mostly great. I would love if Backbone we're AMD compliant, but in the end, everyone's going to switch to Angular anyway, AMIRITE?

@braddunbar
Collaborator

What would be really nice is if Backbone was a collection of different modules that could be required separately. It's silly that Two.js for instance requires the Backbone namespace so it can take advantage of Backbone.Events.

For one, Backbone is rather small as a whole and is not breaking the bank on size. For two, Backbone.Events is not enmeshed in the rest of the source. It's very easy to delete the things you don't want and just use the things you do.

However, one gets in a little bit of trouble in certain use-cases. When you compile and optimize, depending on how your module dependency tree looks, you can run into load-order problems because of the shim

This is why I recommend not using them. Just include Backbone separately, either as an include or a separate file with other vendor code (my personal preference). The dependency rules for vendor code are not complex the way application code is.

@zowens

I'd like to know the reason for not supporting AMD or, even better, UMD. This is 2013... CommonJS and AMD are both here to stay for the near future.

@craigjennings11

Instead of staying the way it is OR supporting AMD, why not do both? Marionette has the pattern of building both a standard core file and an AMD version of that core file. I don't see any reason why we can't do the same here.

Until then, there's always amdjs/backbone and amdjs/underscore.

@braddunbar
Collaborator

Thanks for the discussion but, as stated before, adding explicit support for particular module loaders makes it harder to support them all. To quote Jeremy:

If JavaScript ever gets a real module system, and that module system is deployed in cross browser, we'll support it.

In the meantime, Backbone is perfectly usable with AMD/UMD/etc.

@braddunbar braddunbar closed this
@timmywil

Thanks for the discussion but, as stated before, adding explicit support for particular module loaders makes it harder to support them all.

First of all, the particular module loaders to which you are referring have been dealing with all of the javascript libraries that already support AMD. Secondly, this is actually not true anyway. As I said, exporting the global along with AMD means the behavior doesn't affect non-AMD loaders. It's not hard to support.

If JavaScript ever gets a real module system, and that module system is deployed in cross browser, we'll support it.

This will probably take years. In the meantime, AMD should be supported as it the best way to help developers. The logic is inherently flawed.

In the meantime, Backbone is perfectly usable with AMD/UMD/etc.

This is not true either. As the comments have pointed out, best practice when using AMD in production is to concatenate the files and compile them together, which is not possible with Backbone without modification.

@braddunbar
Collaborator

This is not true either. As the comments have pointed out, best practice when using AMD in production is to concatenate the files and compile them together, which is not possible with Backbone without modification.

I assure you that it is. I do it all the time via the include option to r.js or similar.

@timmywil

@braddunbar: I should have specified that I load jQuery from the CDN even when concatenating, but that's not the main concern, which I think you understand.

Edit: I'll reiterate. "Perfectly usable" (although I would argue it's not perfect) is not enough. Backbone can do better. You pointed out an inaccuracy in my statement without responding to my other points. If the real reason is that you just don't want to, then let's leave it at that, but I hope, like me, you'd not be satisfied with that.

@braddunbar
Collaborator

Hmm...I don't think I do. How does loading jQuery from the CDN affect concatenating/minifying Backbone with your other javascript?

@timmywil

@braddunbar : I don't think you saw my edit, but I'll answer your question. When loading jQuery from the CDN using a loader, Backbone will not wait for it to load. Backbone.$ will be undefined the first time it is used.

@jrf0110

@timmywil Yeah, it's definitely possible. I know my build scripts have gotten in a little bit of trouble with it in the past, but in the end I got it figured out. But I can also imagine there's a ton of use-cases out there that are broken with AMD+Backbone+Underscore+jQuery

@braddunbar In regards to the "break up backbone" thing, I think you misunderstood my intention in that idea. Of course size is negligible. It's about keeping modules separate but interoperable. It's about using a module system and package manager figure out the details on who needs what component. To me, there seems to be a number of standalone products within backbone that could benefit from having separate codebases.

I know it doesn't really matter. This argument has been had before and it's not likely to change.

@braddunbar
Collaborator

@timmywil Yea, I missed the edit. Sorry about that. :)

The below will work just fine and cause no appreciable performance difference. For other use cases, feel free to alter the source.

<script src='//my-cdn.com/jquery.min.js'></script>
<script src='/my-app-with-backbone.js'></script>

@jrf0110 I understand your point, but splitting up the source is an entirely different can of worms for a separate conversation entirely. Sorry, trying to address one thing at a time. :)

@jrf0110

@braddunbar I understand :D :cake:

@timmywil

@braddunbar Recommending I use script tags doesn't seem like the best solution, and you didn't really respond to my arguments.

@zowens

@timmywil I think resistance is futile... seems as if backbone won't support AMD. Hmmm wonder why underscore was forked into lodash... perhaps it's time for a backbone fork.

@zowens

@philfreo That doesn't really add anything more than AMD right? I'm thinking like a REAL fork... a la lodash.

@tbranyen
Collaborator

I added a minimal AMD implementation here:

tbranyen@be1e498

@paulmillr

@zowens check out Exoskeleton, this is a “real” fork with much more than just AMD support. Speed improvements, optional dependencies etc

@zowens

@paulmillr awesome, looks legit. I may even contribute.

@jashkenas
Owner

Reopening this to be reconsidered ... ;)

@jashkenas jashkenas reopened this
@brighthas

self support , component.io

see:

https://github.com/component/component/wiki/Components#wiki-backbone

use:

var backbone = require("backbone");

or

var Router = require("backbone-router");
var View = require("backbone-view");
var underscore = require("underscore");
... ...

very easy!!!

@arcanis

What is the use case for adding a special AMD registration code into Backbone when you can do it using the shim configuration ? I have read the post of @jrf0110, however I remember that Require.js' shim configuration allows to specify library dependencies, which should solve the issue.

As a Require.js user, I've already faced problems with libraries which were registering themselves into Require.js and/or were requesting other libraries which were not named "as usual". For example, I always register my libraries in a "vendors" namespace (vendors/Backbone, vendors/Underscore, etc), so when a library tries something like require(['underscore']), it fails. To solve this, I have to use the Require.js' map configuration (which wouldn't have been an issue if the library had used exported globals).

What I mean is that there isn't a single use case. It seems inevitable that some of us will have to write a bit more code to their loader configurations. Why should AMD be explicitely registered into libraries rather than AMD supporting external libraries without additional code (since it's possible !) ?

Maybe @jrburke could help us ?

@foxbunny

@arcanis One difference between wrapping and shimming is that excluded dependencies (e.g., jQuery via CDN) will not cause the wrapped code to break after doing a build, while shimmed libraries are known to fail in that case.

@timmywil

Agreed, using map configuration is friendly for more use cases when using r.js to build. Also, it is appealing to be able to avoid globals altogether.

@arcanis

@foxbunny I see, thanks for the example

@jashkenas jashkenas closed this
@jashkenas
Owner

Why should AMD be explicitely registered into libraries rather than AMD supporting external libraries without additional code (since it's possible !) ?

Amen, brother! Preach it! ;)

@jrf0110

I think we all know the web dev community won't converge on a module system until the working groups and browser vendors force something on us. When we finally have standard modules, we'll be able to enjoy benefits of being able to make assumptions about third-party code. The community will be encouraged to write smaller, neatly packaged, single-purpose modules, and not care about the dependency tree (or rather script tag insertion) because there's a tool making assumptions about module format that's handling it for us. It's also easy to see that building will be a lot simpler when everyone is on the standard module format.

Why should AMD be explicitely registered into libraries rather than AMD supporting external libraries without additional code (since it's possible !) ?

AMD is an attempt to build a standard for modules before the WGs and browser vendors figure it out for us. If everyone embraces the standard, then everyone can start making beneficial assumptions about their codebase. It's not that requirejs can figure out how to load your non-AMD code, it's that now that code is a black box in some regards. No assumptions can be made on its dependency graph (that must be explicitly stated during an application configuration) and no assumptions can be made about what it exports (again, must be explicitly stated).

As I said before, it would be great if everyone adhered to my way of thinking because clearly it's the best :) But that's not going to happen and I completely understand. Everyone's got their own ideas on how modules should be approached. The community is fragmented and AMD was an attempt to unify that fragmentation, but I suppose we'll have to wait until Harmony for everyone to get on the same boat.

@smagch smagch referenced this issue in smagch/simple-lru
Closed

AMD support added. #1

@gmmorris

The problem is that there is no way for me to prevent Backbone from registering itself without patching my copy of the code - which is something I avoid to improve maintainability.

I am working on building a new component for a site which has an old legacy environment which uses requirejs and we want the new component to be sandboxed and separate from the existing code (which is a nightmare).
But we can't use Backbone (even though its the ideal candidate) because it forces itself into the existing requirejs package and we can neither reach it nor prevent it from interfering with the existing code (because it adds requirements for jquery and underscore).

The whole approach is wrong in my opinion, because we are forcing a certain way of thinking which is unrelated to the actual Backbone library.

@timmywil

@gmmorris Do you have a suggestion on how to change Backbone's AMD support to fit your use case? I don't know of an issue that can't now be subverted with requirejs config, but I'm not sure what you're trying to accomplish.

@magalhas

Not just AMD, separation of modules would also be a win when using CommonJS module definition (Browserify/Node).

@gmmorris

I don't think AMD support should be hard coded into Backbone at all, but if it has to be then at least provide a way to tell Backbone to not register itself.
The horrible way would be a window level flag... but we're trying to avoid global variables like that so I'd avoid that if possible.

Just to explain our use case - we have an existing code base which is in dire need of refactoring and scraping.
We're trying to build a sandboxed infrastructure which sits in a separate packaging from the existing code - mainly to prevent developers from getting tempted to mix the new code modules with the old ones, but also in order to refactor various components in such a way that they can live side by side without conflicting in any way.

Obviously this is a very specific use case (first time this has happened to me with leading architecture for about a dozen different companies over the last few years as a consultant) but I see this problem as a direct result of the way in which it is implemented which makes a mistaken assumption as to how the environment is setup.
I simply think the ideal solution is removing this assumption especially considering that requirejs offers better solutions such as shims.

@tbranyen
Collaborator

@gmmorris If you're a third party service injecting JavaScript into the client's page, you need to be more defensive. Wrap your code in a function expression that immediately invokes and specify an inline define implementation. Something like Almond.

Your specific use case is better addressed by this tool. You would then require from an internal registration instead of globally leaking. Load Almond, load Backbone, require('backbone') || window.Backbone, and now you have a great sandbox that you didn't have to invent.

@gmmorris

No no, same site, but I was brought in to help rebuild the existing site without having to rebuild everything from scratch.
Wrapping is an option but its problematic because of the existing backend infrastructure... and there are plenty of solutions, but I'm trying to make a general point regarding the codebase.
My point is that the existing solution is wrong for assuming anything on a global scale - which is what Backbone does out of the box.


Side note:
BTW, the problem with wrapping the code is that you have to do that for every file, so if you want to load specific files on demand rather than concatenate all files you have to wrap every single file, which is problematic because of the backend infrastructure, but also forces you to concatenate your files in the dev environment which creates other problems.
This has nothing to do with Backbone, of course, but it points out the problems of code which makes invalid assumptions.

@tbranyen
Collaborator

Backbone registering itself with a function call is significantly better for your use case. It's encapsulating all logic inside a function scope and only exposing it's global when you call it. This means your code looks like this:

(function() {
/* Include require/define shim, like Almond. */
/* Include Backbone. */
var Backbone = require("backbone");
})();

Compare this to a globals approach:

/* Include Backbone. */

(function() {
// noConflict is not ideal, it will assign undefined to window.Backbone instead 
// of deleting it if it never existed.
//var Backbone = window.Backbone.noConflict();

var hadBackbone = false;
var prevBackbone;

if ("Backbone" in window) {
  hadBackbone = true;
}

/* Load Backbone. */

var Backbone = window.Backbone;

if (!hadBackbone) {
  delete window.Backbone;
}
else {
  window.Backbone = prevBackbone;
}
})();

Edit: Fixed some typos.

@gmmorris

But again, the problem here is that it defined itself in the existing require package... which affects the configuration for requirejs because it also adds the calls for underscore, jquery and extends prerequisits, which means making changes in the existing code base (which is a problem because it spans multiple pages... their exiting code is a mess).
Assuming I don't have an option to simply wrap the Backbone code nor can I change the existing infrastructure - I simply can't prevent Backbone from registering itself.
I suggest we don't make this about my current usecase - because, as I already stated, it is a very specific use case.

Again, I want to stress - my problems on this project have nothing to do with the AMD support in Backbone other than in the fact that it surfaced an inherent problem in the way Backbone works.
I don't think its right for a vendor component to choose where to register itself without, at lest, providing a way to prevent it from acting on that assumption.

@caseywebdev
Collaborator

You can force Backbone to store itself on whatever object you want (and not in any sort of AMD state).

var myRoot = {}; // could also be `window` or what have you
(function () {
  var define, exports; // wipe out `define` and `exports` in this scope
  // include backbone.js
}).call(myRoot);
console.log(myRoot.Backbone);

As you said, your use case is very specific and I think the current state of backbone.js works for the majority.

@gmmorris

Yeah, thats exactly what I've done.
My point wasn't about solving the issue on my end, I just honestly think the way it works now is wrong.
But thanks for your time anyway guys.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.