// please read next example too // which seems to be better for AMD tools (this.define || Object)( this.exportName = exportValue );Update with a nice hint by @kentaromiura where the other side of define could be just a function and not a new function as initially proposed.
Another update suggested by @unscriptable that should not break AMD tools, neither CommonJS, neither global Rhino or window, still leaking a couple of properties that should be reserved for module stuff anyhow.
var define = define || Object, exports = this.exports || this // * ; define( exports.module = {} );Nice one, and if you are wondering why
this.exports
is needed, is because RingoJS apparently does not follow a de-facto standard as this
as module exports is for other CommonJS implementations.Even Better
If you don't want to pollute the scope at all:(this.define || Object)( (this.exports || this).module = {} );now all patterns in one for easy portability, cool?
What Is `this`
In node,this
is always the exports
object, if the module has been loaded through require, so this.name = value
is not different from exports.name = value
.Outside node.js world,
this
will be the global object or any augmented environment or namespace if the file is loaded as text and evaluated a part.This makes automatically the module mock ready.
Less Fairytales, Please
This trend to double check thatdefine
function has a truthy amd
is hilarious.
The whole purpose of going modules is to avoid global object pollution so, if you have in your code any script that create an insignificant define
variable, function, or property, in the global scope, drop that jurassic script or rename that function in that script instead of check that property all over.Also, please consider that a property could be part of any object so that check gives us nothing ... right? (but I wouldn't name a propety `amd` ... sure, as well as I wouldn't name a global function
define
...)
A Basic Example
// what.js (this.define || function(){})( this.what = { ever: function () { console.log("Hello"); } }); // node.js var what = require('./what.js').what; what.ever(); // Hello // browser <script src="what.js"></script> <script> what.ever(); // Hello </script> // AMD require('what.js', function (what) { what.ever(); // Hello });All this works with closures too, if necessary, so you don't pollute the global scope in non node environment.
(this.define || function(){})( this.what = function(){ var Hello = "Hello"; return { ever: function () { console.log(Hello); } }; }(/* **INSTANTLY INVOKED** */));As summary, if you want to create a tiny library, utility, module, and you don't have/want dependencies, this approach has a minimum overhead of 39 bytes without compression vs 158. Now multiply this per each file you have there and also try to remember my suggested approach, I bet you have it already ... now tell me the other with if/elses :P
But ... Global Scope Polluted With Define ?
I believe this is another fairytale. If you understand how AMD works you realize the moment you execute code in the closure the module is loaded correctly and passed as argument which means, basically, who cares what that argument outside that scope.Said that, when it comes for globals name clashes possibilities are really low. The classic
$
function or the _
one are two example but again, if you use AMD you receive the right argument while if you don't use AMD and you just inject script you do want that property in the global scope and again, if you don't use a module loader, it's your problem if you have both underscore and lodash in your global scope. Just drop one of them ... and enjoy this simplified UMD :)
Wouldn't "this" be the global object and not exports in Node? Or have they changed this back again?
ReplyDeleteSebastian I believe you refer to the `this` when you launch node. In that case is the global scope but when you require a module, this is never the global scope and is done as such so you don't pollute the global scope when your node code is run as file a part. You have both `module.parent` or `this !== global` to know if you are loaded as module ;-)
ReplyDeleteI don't think, neither believe, this shuold change any time soon, is a smart decision and it works!
ReplyDeleteI like this idea, and hope it works. But...
Sure, _you_ wouldn't name a define property, or an amd property (and nor would I), but other poorly written scripts might. Not everyone has the luxury of complete control over every script, and cannot just edit "Jurassic" scripts. Obviously that's the ideal, but it's just not current reality. Advertisements, for example, exist, and that code is horrible.
Another gotcha is legacy code. I remember when YUI defining exports globally, in the browser, completely broke a library I build because it expected exports to only exist in a node-like environment.
I agree that the current umd convention is annoying and really verbose, and I'm going to think hard about your alternative! Every time I've tried to shortcut umd though, it has backfired. :(
Andrew the idea works for what I believe are 95% of the cases ... and old software is old software, changing a name is usually the less problematic refactoring thanks to search possibility in the whole folder ;-)
ReplyDeletePlease let me know if you come out with anything better than this, thanks!
It's true that `this === exports` has been adopted by the AMD community, but not by the CommonJS community. It works in node.js, but probably not everywhere. (TBH: I haven't checked any other CJS environs besides RingoJS.)
ReplyDeleteTBH, I would expect `this === undefined`.
As James mentioned[1], AMD build tools look for the `define()` signature in order to inject module ids into built files. This will break most, if not all, AMD build tools.
Also, your examples will use the AMD "global require" function. The global require() does not have the ability to resolve relative module ids.
How about something like this?
(function (define) { define(function (require) {
var dep = require("./localDep");
return { foo: 27 };
});}(this.define || function (f) { module.exports = f(require); }));
This won't work in every CJS environ either, but works in more than a wrapper that relies on `this === exports`.
Hmmmm how about this?
(function (define) { define(function (require) {
var dep = require("./localDep");
this.foo = 27;
});}(this.define || function (f) { f.bind(exports, require)(); }));
I think this will work in every AMD and CJS environ, but haven't tested it outside of node and ringo.
AFAIK, it's not possible to get much smaller without breaking either the AMD build tools and CommonJS environments.
[1] http://twitter.com/jrburke/status/290303213423435777
for signature you mean that this won't probably break, right?
ReplyDeletevar define = define || Object;
define(this.exportName = exportValue);
Andrea: yah, that probably won't break AMD build tools, but I haven't tested any. Looks like it'll work in cram.js.
ReplyDeleteAnother idea that also works in ringo:
var define = define || function(){}, exports = this.exports || this;
define(exports.foo = 27);
The problem with this is that it'll create a global `define` and a global `exports` var in non-CJS, non-AMD environs (e.g. legacy, global-ish browser engines or rhino).
I like this a little bit better:
var define = define || function(){}, exports = this.exports || this;
define(exports.module = { foo: 27, bar: 42 });
or this maybe:
var define = define || function(){}, exports = this.exports || this;
define(exports.foo = 27, exports.bar = 42, exports);
Very cool alternatives if you don't mind leaking some globals.
Hi Andrea, excellent debunking work – neat solution, sound reasoning. I'm forever dismayed by the tendency of the Javascript community to overcomplicate.
ReplyDeleteI'd like to write modular code and transform it to adopt this convention procedurally, ie via a CLI command. I'd have thought you might have incorporated this pattern into your own code somewhere, but your public Github projects don't seem to have any appropriate candidates (an AMD / CommonJS inclusion of DOM4 makes little sense, after all). Do you think this is an advisable patterns to adopt in the wild – if so how would you go about it?