jashkenas / backbone

Give your JS App some Backbone with Models, Views, Collections, and Events
http://backbonejs.org
MIT License
28.08k stars 5.37k forks source link

AMD support breaks non AMD applications using global window.Backbone #3151

Closed dsiebel closed 9 years ago

dsiebel commented 10 years ago

I tried updating to the latest Backbone version 1.1.2 today, sadly this seems to break our legacy code that is not using AMD (at least not in every part of the application). The global reference to window.Backbone seems to be missing:

Uncaught ReferenceError: Backbone is not defined

I think this is due to the fact, that Backbone will be attached to the window in the AMD factory/callback:

define(['underscore', 'jquery', 'exports'], function(_, $, exports) {
      // Export global even in AMD case in case this script is loaded with
      // others that may still expect a global Backbone.
      root.Backbone = factory(root, exports, _, $);
    });

This one will only be executed on the first (at least with using almond.js or similar)

require('backbone', ...)

Which, from my perspective, makes sense, since you don't want to execute a factory unless the module is really required.

akre54 commented 10 years ago

Ah yeah. if you're using AMD you need to load it via AMD...

define(['backbone'], function(bb) {
  console.log(bb === window.Backbone); // true
});

It'll still be available for you as a global after loading if you need it.

dsiebel commented 10 years ago

We have several components, that don't use AMD and rely on having the global reference window.Backbone available even though backbone hasn't been required via AMD at that point. Otherwise you are forced to load all AMD modules first (or at least one module that requires Backbone) and non-AMD modules after that.

I think the whole point of attaching it to the window is to have it available even when not using AMD throughout your application.

Or am I missing something here?

akre54 commented 10 years ago

Yeah unfortunately that's the way AMD is designed.

It is globally accessible once you've required it in to your app even once via AMD, but the whole notion of the A in AMD (asynchronous) means you can't guarantee that Backbone's dependencies, jquery and underscore, will be set up by the time the individual application parts need it without using the module system. The AMD guys might have a better solution to this than I do.

On Sat, May 17, 2014 at 7:39 AM, Dominik Siebel notifications@github.comwrote:

We have several components, that don't use AMD and rely on having the global reference window.Backbone available even though backbone hasn't been required via AMD at that point. Otherwise you are forced to load all AMD modules first (or at least one module that requires Backbone) and non-AMD modules after that.

I think the whole point of attaching it to the window is to have it available even when not using AMD throughout your application.

Or am I missing something here?

— Reply to this email directly or view it on GitHubhttps://github.com/jashkenas/backbone/issues/3151#issuecomment-43404584 .

philfreo commented 10 years ago

take a look at the wrapShim option of RequireJS

dsiebel commented 10 years ago

@akre54 I am aware of the asynchronous nature of AMD ;) @philfreo We're not using RequireJS ;)

I think I will stick to 1.1.0 then, thanks for your input, guys!

maratbn commented 9 years ago

So far this discussion assumed that the developer has full control over the global environment (and what gets loaded into it), but that is not always the case.

It is not the case when developing modular plugins for CMSes, such as WordPress.

A WordPress plugin developer may happily use Backbone in his plugin, and have it test out well and deploy fine on his and many other WordPress instances. But eventually, some administrator of some other WordPress instance decides to install that particular plugin along with some other plugin that happens to just as happily use say the RequireJS AMD loader. That site would work fine with either one of the plugins, but as soon as both are installed together disaster will strike.

At this time, the only way to avoid this problem is for the developer of the Backbone plugin to alter the Backbone he's integrating to remove AMD support, and for the developer of the RequireJS plugin to alter the RequireJS he's integrating to have it inject the AMD API into some unique namespace rather than the global namespace. This will prevent either plugin from getting into this kind of a future collision.

However, if stock Backbone is intended to be used safely in modular CMS plugins then I think this issue has been closed prematurely.

Fixing this issue would likely require either a separate non-AMD version of Backbone, or changes to the AMD API in conjunction with changes to Backbone to be able to have the Backbone logic ask AMD if the dependencies are available via AMD before attempting to load them via AMD, so that Backbone could just default to accessing them from the global namespace otherwise.

jQuery UI also has similar problem that I already filed a ticket on -- http://bugs.jqueryui.com/ticket/13449

maratbn commented 9 years ago

Demonstrated here -- http://jsfiddle.net/wp9ebxbL/3/

jridgewell commented 9 years ago

@jrburke: Mind weighing in? As far as I know, there's now way to determine if you're inside a requirejs dependency load, or merely in the global context where define() is present.

jrburke commented 9 years ago

If the developer is not in control of the overall environment, and they want the non-AMD pathway followed, they can construct this sort of script sequence around Backbone:

<script>var oldDefine = define; define = undefined;</script>
<script src="backbone.js"></script>
<script>define = oldDefine; oldDefine = undefined;</script>

Note that defer or async attributes on the script tags should not be used in this case.

I do not believe extraordinary measures should be done in UMD boilerplate to account for all possible global environment setup, it would make the UMD blocks incredibly large, and likely will not be comprehensive. If not in control of the whole global environment, I expect the developer will need to be more defensive in coding style anyway, and will know best what sorts of mitigations like the above are needed.

A define('backbone', function() { return Backbone; }) can be used in that third script tag if backbone also wants to be used in the AMD parts of the page. In that case, the developer may need to add in manual script tags for jQuery and underscore in that case to that script block above, and perhaps also do similar define() work for them in addition to the backbone one.

@jridgewell to your question, there is a require.specified(moduleId) that can return true if a module has been specified for loading, but it is only true if the module is part of a require([]) top level dependency chain. It will not be true if a bunch of defines are done without a top level require([]) being done before the require.specified() check.

But I do not believe knowing that distinction will help if the situation is a developer has some things that want a global backbone immediately but also has AMD parts that may also want backbone loaded.