Browse files

Removing AMD 'support' from Underscore.

  • Loading branch information...
1 parent 0b74f2d commit 0d4b1247c45083c695cab4242c084a97aa600221 @jashkenas committed Jan 11, 2012
Showing with 4 additions and 9 deletions.
  1. +4 −9 underscore.js
View
13 underscore.js
@@ -48,21 +48,16 @@
// Create a safe reference to the Underscore object for use below.
var _ = function(obj) { return new wrapper(obj); };
- // Export the Underscore object for **Node.js** and **"CommonJS"**, with
- // backwards-compatibility for the old `require()` API. If we're not in
- // CommonJS, add `_` to the global object.
+ // Export the Underscore object for **Node.js**, with
+ // backwards-compatibility for the old `require()` API. If we're in
+ // the browser, add `_` as a global object via a string identifier,
+ // for Closure Compiler "advanced" mode.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
- } else if (typeof define === 'function' && define.amd) {
- // Register as a named module with AMD.
- define('underscore', function() {
- return _;
- });
} else {
- // Exported as a string, for Closure Compiler "advanced" mode.
root['_'] = _;
@jdalton
jdalton added a note Jan 11, 2012

Why remove that comment about Closure compiler?

@jashkenas
Owner

It just moved up a couple lines, so this will look a 'lil bit nicer:

http://underscorejs.org/docs/underscore.html#section-10

@jdalton
jdalton added a note Jan 11, 2012

Ahh yap, cool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
}

19 comments on commit 0d4b124

@ryanflorence

It's ironic, but this actually allows AMD users to use backbone in an amd environment without hacking the source.

Backbone checks for root._ but underscore didn't export to root when define was detected, and since underscore is a dependency it was all busted.

Now you can write a little shim to get all the libs you need for backbone w/o touching the source of any of them.

define ['vendor/underscore', 'vendor/jquery', 'order!vendor/backbone'] -> Backbone

Good move.

@jashkenas
Owner

Yep. Not supporting a particular script loader will definitely make it easier for all of them to work properly.

I apologize for merging the support in the first place.

@ryanflorence

For those interested it's possible to support node, amd, and the usual global module patterns with dependencies, but it takes a disappointing amount of boilerplate (https://github.com/umdjs/umd/blob/master/returnExports.js). I still think its worth it, but whatever ...

When amd becomes more standard, maybe it'll be worth it to @documentcloud. For now I think this commit is the right move though.

@iammerrick

Ryan I am a little embarrassed to say I didn't recognize your CoffeeScript as code and thought you were saying that those dependencies were required for each module to get backbone thus the arrow.

To spare anyone from the same ignorance. Here is the JS shim. :-)

// backbone.js shim

define(['vendor/underscore', 'vendor/jquery', 'order!vendor/backbone'], function(){
    return Backbone;
});

Sorry dude.

@ryanflorence

Thanks Merrick,

yeah so:

app/
- scripts/
  - backbone.js
  - vendor/
    - backbone.js
    - jquery.js
    - underscore.js

Merrick's code being app/backbone.js, a little shim module to give you direct access to backbone as a module. Could even toss some noConflicts in there to remove everything from the global scope.

@jdalton

I dig AMD support (why not?), never understood @jashkenas' rage/push against it.

@millermedeiros

I don't recommend using the order! plugin, it may fail depending on the server settings and isn't really required in this case, the depend! plugin is a better fit for this case:

define(['depend!vendor/backbone[jquery,underscore]'], function(){
    return Backbone;
});

jQuery should be listed as jquery since it registers itself as a named module...

another option is to wrap Backbone into an AMD module (since it has dependencies):

define(['jquery', 'underscore'], function($, _){
  // paste Backbone.js source code here
  return Backbone;
});

it was a mix of a "bad" and "good" move, good since it won't register it as a named module (named modules are usually a bad thing for portability) and bad since the Backbone conflict could be solved by wrapping it as an UMD...

if you are concerned that wrapping it into an AMD module might make updates of third-party libs harder automate ALL things!!

#!/bin/bash
echo -e "\n -- updating underscore.js --\n"

echo -e "define(function(){\n//start wrap---" > js/lib/underscore.js
curl https://raw.github.com/documentcloud/underscore/master/underscore.js >> js/lib/underscore.js
echo -e "//end wrap---\nvar exports = _.noConflict();\nreturn exports;\n});" >> js/lib/underscore.js

echo -e "\n -- updating backbone.js --\n"

echo -e "define(['jquery', 'underscore'], function($, _){\n//start wrap---" > js/lib/backbone.js
curl https://raw.github.com/documentcloud/backbone/master/backbone.js >> js/lib/backbone.js
echo -e "//end wrap---\nvar exports = Backbone.noConflict();\nreturn exports;\n});" >> js/lib/backbone.js

or if you prefer to use a helper lib that is already in the AMD format you can try AMD-utils, it doesn't have all the features from underscore but it has a few extras as well... (and you only load what you need)

AMD FTW

EDIT: updated depend! plugin example (since it was redundant) and also updated bash script to add -e flags.

@millermedeiros

just saw @jashkenas twit

… because AMD support is breaking regular Underscore embeds on pages that also happen to use Require.js …

so instead of dropping the define(), why not keep the browser exports but do a unnamed define instead?

if (typeof exports !== 'undefined') {
  if (typeof module !== 'undefined' && module.exports) {
    exports = module.exports = _;
  }
  exports._ = _;
} else {
  if (typeof define === 'function' && define.amd) {
    //register as an unnamed module but still export it as a global
    //object to avoid breaking `undescore` in pages that also happen
    //to use an AMD loader
    define(function(){
      return _;
    });
  }
  root['_'] = _;
}

the user can still call _.noConflict() if needed..

@josscrowcroft

Tend to agree with this, but on the fence about removing the similar lines that I stuck in some of my JS micro libs, since they seem to work great. Was taking underscore as a best practice at the time.. Guess it's different here because of underscore's serving as Backbone's dependency.

I'd say if removing this it'd be nice to add a note in the docs about AMD, perhaps linking to some article explaining how to set up that easy shim which would export underscore and Backbone in noConflict for when people need it.

@jrburke

I agree with @millermedeiros: the problem can be fixed by also exporting the global in addition to the define() call. To be clear, this code change was not for supporting a particular script loader, but a class of script loaders that support modular code. It is difficult upgrading the web, and while ideally underscore would not need to export the global if AMD support is on a page, there is a bootstrap period where it helps.

I hope this code change is reconsidered, feel free to ping me if there is a way I can help.

@jdalton

jQuery has AMD support and a huge web presence without problems.
Maybe underscore.js should look at how jQuery bootstraps its support.
(I think jQuery exports a global reference in the meantime too)

@jrburke

jQuery exports a global in addition to calling define. I'm happy to do a pull request if that helps.

@jrburke

I went ahead and did this pull request to show how the issue could be resolved without removing define() support.

@svieira

@jashkenas - I'm in agreement with @millermedeiros and @jburke - it would be better to have support for AMD if it can be had without breaking existing libraries. I'm using curl.js for some projects right now and when I saw that underscore had updated I was looking forward to upgrading to the newest version of underscore - only to find that the update breaks support for the loader+environment I am using it in. I'm willing to maintain a fork if that's what's necessary - but that seems like unnecessary fragmentation of a really vibrant project.

@xcambar

Although it has its imperfections of its own, the solution provided by the jQuery team is IMO the wiser. It supports the 'traditional' global export while still allowing AMD through the registration of a named module.

Developers that use AMD generally don't rely on globals at all. The only thing I would constrain is about exporting the global: that should ideally be done if and only if the define() function is not available.

@jrburke, your pull request is close to what is to me the ideal solution. To be more specific, the first commit if the pull request is the ideal solution, the second degrades it (with all due respect, obviously). But I'm curious about what had you revert to a "global always" implementation.

@jrburke

@xcambar: I'm not sure I follow your comment, but the global registration needs to be there when an amd loader loads underscore and another script that depends on underscore but does not been call define() for their dependencies. For instance, backbone/master.

If underscore only calls define in that case and does not export a global, then an error occurs. It is also useful when multiple versions of underscore could be loaded on a page (even outside an amd loader), but the loader may only want one of them. If the underscore definition disappears into define, another script that wanted to use it as a global would have a problem.

Basically the same forces are at play for underscore as they are for jQuery, so the same pattern is used. Most other libs do not have this problem though. It just happens to be that underscore and jQuery are really popular, and modular loading is really new for most developers.

If you want to talk about it more, it is probably best to do outside of this commit, to avoid the extra noise for the underscore collaborators.

@bardu

I'm just new to require.js, Backbone.js, and underscore.js. To solve me issue with "Backbone is null" I did as recommended:

define(['jquery', 'underscore'], function($, _){
// paste Backbone.js source code here
return Backbone;
});

However, it seem I have to wrap underscore.js as well. So, I did it like this:

define( function(){
// paste underscore.js source code here
return _;
});

I'm just wondering whether this is a valid approach without causing any side effects?

@ryanflorence

@bardu https://github.com/amdjs/backbone is the drop-in AMD-compatible fork for backbone.

@bardu

This fork still requires to wrap underscore!?

Please sign in to comment.