AMD modules with named defines. So much pain for what gain?

1,214 views
Skip to first unread message

D. Dotsenko

unread,
Dec 24, 2011, 10:23:50 PM12/24/11
to amd-implement
Per James Burke's suggestion, starting here a conversation regarding
*named* (where module's name is hardcoded in a define within the
module) AMD modules.

(I put up this email as a blog post as well - code highlights + easier
to read. (No adverts, or gain for me if you read there. Only for your
reading convenience): http://dvdotsenko.blogspot.com/2011/12/amd-modules-with-named-defines-so-much.html
)

Found a number of minor issues while using named modules (jQuery,
underscore). Went looking for definitive, authoritative explanation
for why hard-coding a name into a module *must* be done and found
rather little. Hence, bringing this up here as suggested.

The issues (I personally bumped into) with named AMD modules:

1. Precludes legitimate use of multiple references to "same" module:
a) separate aliases for CDN-to-local failover scenarios:
"jquery_from_cdn" and "jquery_local"
b) separate loading of separate (in)compatible version of the lib:
"jquery" vs.
"jquery_that_older_version_that_does_not_break_plugins_born_pre_prop-
args_split"

The obvious issue here in all these cases is that when require'd by
any other name, not the hardcoded one, module returns Null (tested in
RequireJS, Curl.js)

2. Hard-naming modules is utterly redundant, adds to complexity in
use.
The only (simple, reliable) way to load a hard-named module now *and
make it return non-Null ref* through a require call is to hard-code
the alias (path to the module) in the config of the AMD loader:

{
paths: {
jquery: 'js/libs/jquery-1.7.1.custom.min'
, jquery_from_cdn: '//ajax.googleapis.com/ajax/libs/jquery/
1.7.1/jquery.min.js'
, jquery_older: 'js/libs/jquery-1.4.3.custom.min'
, underscore: 'js/libs/underscore-1.2.3.min'
}
, baseUrl: './'
// etc.
}

And can only be consumed like so:

// Curl.js promise-style API
require(['jquery'])
.then(function($){/* $ is non-Null here because we ask for it by
hardcoded name */})

Because, obviously, this one will not work as expected:

// Curl.js promise-style API
require(['jquery_from_cnd'])
.then(function($){/* since '$' is Null here. */})

If the only way to consume the module properly is to force the end-
developer to hard-code its name *again* in a config file, at the
consumption point, (in that respect only) why waste time, effort and
hard-code the name in the module in the first place (let alone cause
grief to those devs who DO need to load the module under different
name / from alternate sources)?

J Burke pointed out the justifications for hard-naming the two modules
in question here (https://github.com/jrburke/requirejs/wiki/Updating-
existing-libraries#wiki-anon)

These claims provide a good ground for the short discussion of this. I
added numbers to each section for ease of reference:

I: They [use named Define] because they are commonly used as
dependencies by other libraries, and so it is common for other code to
use 'jquery' and 'underscore' as the agreed-upon name for these
dependencies. This allows those other dependencies to share the same
module instance.


II: jQuery and underscore are also the defacto implementations of the
module interface implied by those names.


III: In addition, it is common for them to be used by other third
party javascript libraries. Those third party libraries may be loaded
outside of an AMD script loader that is used on the page to load the
rest of the page logic.If those libraries are not loaded by an AMD
script loader and there is an anonymous define call, that could lead
to an error in loading, since the anonymous define call cannot be tied
to a name that the loader was expecting.


IV: So, to ease into AMD loading on the web, base libraries like
jQuery and underscore register as named modules, particularly since
they will likely be referred to by those IDs. As AMD loading becomes
even more common on the web, at some point those libraries may switch
to an anonymous module call.

My (indeed, subjective) thoughts on these (in context of the real-
live, browser-side usage of the mentioned named modules):

Claim I "the names are in common use" - does not necessitate hard-
naming.
Interestingly, since the recommended (and only one that works) use
pattern now is to set up path alias in the AMD loader's config file
anyway, making the module anonymous right now will not break any
present AMD set up. Present code will continue to rely on config-time
path aliasing. In that respect only, hard-coding the name for this
pattern of use is just redundant.

Indeed, I too would only support standardizing the module *name* but
only as *recommended coding pattern* at the point of consumption. On
top of presently possible aliasing through AMD loader's config, one
more "recommended pattern" will become available to end-user-
developers when jQuery, underscore become anonymous:

define('jquery',['path/to/it'],function($){return $})

Both, the config-path-aliasing and the standard define above fulfill
Claim (I) and provide for use of the module under standardized name,
all without hard-naming the module inside the module.

Claim II - "de facto implementations" - does not imply, benefit from
or necessitate hard-naming. Because these are de facto
implementations of the interface, there must be room for loading the
module in a simple client-side versioning, or failover-cascade
patterns, which naming a module precludes - essentially allows only
one source for the module. This claim relates to Claim (I) and my
thoughts for Claim (I) apply and fulfill needs of Claim (II). Claim II
is actually better (more-reliably) fulfilled by anonymous module.

Claim III - "It CAN be loaded outside of AMD loader. In that scenario
we want to avoid double loading" - indeed substantially supports the
need to have named module. Although the module will not be loaded
twice (since it will be cached by the browser), just, possibly,
evaled, at most, twice, it's worth a thought. However, same exact
argument applies to any other JavaScript lib that could be loaded
outside of AMD, but is AMD-compatible. Do we make them all hard-named?

Actually, we may want jQuery to be evaled second time when it's ran
through AMD. With the ongoing discussion of "polluting the global
scope, overriding one version with another" evaling (future) AMD-
capable (non-polluting) version second time may be the solution, as
opposed to a perceived problem.

Claim IV - "hard naming the modules will make adoption of AMD pattern
easier"
My experience is the opposite. I could not in-line define the modules
to support simple multiple-versions, failover scenarios. The hard-
named modules preclude common-sense use of them through common API
( define('jquery', ['/path/to/it'], function($){return $}) ) in favor
of obscure, AMD loader-specific config tricks. Named modules are just
a pile of pain - definitely do not help in adoption of AMD patterns.


I guess if i hear something like "Oh, this is the only way to fix /
make work such and such framework and there is no other way." it will
settle the issue.

Lacking that, as a user, i would hope to see named defines gone soon
from jQuery, Underscore, I must admit that it may be too late to turn
back the clock on both. Someone, somewhere, in some obscure use-case
may rely on hard-named jQuery already and will scream if this carpet
is pulled out. However, i don't see named-define in Underscore to gel
out there yet, and can, probably, still be pulled out.

In all cases, for the love of all good, please, stop inflicting more
hardcoded named defines upon other popular JS libs. Or, at least,
write down some substantive blurb justifying the atrocity, so the pain
can be relieved by simple Googling.

Daniel

Ben Hockey

unread,
Dec 25, 2011, 10:10:50 PM12/25/11
to amd-im...@googlegroups.com
You make a very good argument. I'm glad you brought it up. I had been feeling the same way but wasn't interested in spending any energy challenging the naming of a few modules that I don't personally use. It didn't directly affect me so i didn't give it any of my time. However, now that you've brought it up I want to add my voice to yours in questioning the value in naming modules - I believe it's an anti-pattern. I won't waste time repeating your argument.

ben...

Rawld

unread,
Jan 2, 2012, 1:25:28 AM1/2/12
to amd-im...@googlegroups.com
On 12/25/2011 07:10 PM, Ben Hockey wrote:
> You make a very good argument. I'm glad you brought it up. I had been feeling the same way but wasn't interested in spending any energy challenging the naming of a few modules that I don't personally use. It didn't directly affect me so i didn't give it any of my time. However, now that you've brought it up I want to add my voice to yours in questioning the value in naming modules - I believe it's an anti-pattern. I won't waste time repeating your argument.
>
> ben...
+1

I've been arguing that for along time:

http://bdframework.org/bdLoad/docs/bdLoad-tutorial/bdLoad-tutorial.html#nameClashes

and continue to do so:

http://livedocs.dojotoolkit.org/loader/amd#the-amd-api


Adam Crabtree

unread,
Jan 3, 2012, 5:30:21 PM1/3/12
to amd-im...@googlegroups.com
Being the "AMD-Implement" list and not the requirejs list, I'll add my unique AMD deviation and considerations.

I'm currently building the soon-to-be-released tiki browser package manager. In tiki, I've separated Javascript module loading into 3 separate concerns: Installation, Parsing/Definition, and Execution/Loading, which correlate respectively with the following 3 APIs: tiki(), define() and require().

The define API is dumb regarding names and accepts any that are passed in, but internally manage defined modules and makes available its own require function ("define.require()" in my case) for the purpose of executing (loading) defined modules.

Example:

define('jquery', function() {
  console.log('hello');
});

define('jquery', function() {
  console.log('world');
});

define.require('jquery'); // "world"

As you can see, in this case, the hard-coding of module names serves to make the define() API I/O agnostic, and with the provided define.require(), also creates an extremely straightforward means to load a given module. Notice also that a module with the same name can be overridden, as in the example above. This doesn't prove to be a problem however as module names can be versioned, "jqu...@1.0.1".

In my case, I handle module names externally through my tiki() API, which is a separate topic for another time. =) This allowance for a package manager managed hard-coded module names allows for a decoupling of the package manager from the AMD define/require implementation.

Cheers,
Adam Crabtree
--
Better a little with righteousness
       than much gain with injustice.
Proverbs 16:8

James Burke

unread,
Jan 6, 2012, 1:20:33 PM1/6/12
to amd-im...@googlegroups.com
Sorry for the late reply:

On Sat, Dec 24, 2011 at 7:23 PM, D. Dotsenko <dvdot...@gmail.com> wrote:
> Claim III - "It CAN be loaded outside of  AMD loader. In that scenario
> we want to avoid double loading" - indeed substantially supports the
> need to have named module. Although the module will not be loaded
> twice (since it will be cached by the browser), just, possibly,
> evaled, at most, twice, it's worth a thought. However, same exact
> argument applies to any other JavaScript lib that could be loaded
> outside of AMD, but is AMD-compatible. Do we make them all hard-named?
>
> Actually, we may want jQuery to be evaled second time when it's ran
> through AMD. With the ongoing discussion of "polluting the global
> scope, overriding one version with another" evaling (future) AMD-
> capable (non-polluting) version second time may be the solution, as
> opposed to a perceived problem.
>
> Claim IV - "hard naming the modules will make adoption of AMD pattern
> easier"
> My experience is the opposite. I could not in-line define the modules
> to support simple multiple-versions, failover scenarios. The hard-
> named modules preclude common-sense use of them through common API
> ( define('jquery', ['/path/to/it'], function($){return $}) ) in favor
> of obscure, AMD loader-specific config  tricks. Named modules are just
> a pile of pain - definitely do not help in adoption of AMD patterns.

Thanks for naming the claims. I probably have them out of order. Claim
III is actually the main reason for jquery and underscore being named
modules.

Claim III: if jQuery is used an anonymous module definition and it is
loaded in a page with an AMD loader, but *outside* of a loader call,
it will most likely generate an error. This error will occur in the
wild because there are third party scripts that use jQuery, and they
can be loaded on the page, for example as part of a CMS template, that
may not be part of the AMD loading on the page. Yes, this means there
are pages that load two versions of jQuery. It does not matter if we
think that is good practice, it happens.

If an error is generated, that is bad for AMD and bad for jQuery: the
AMD loader will get bug reports and jQuery will likely get them too.
jQuery is more likely to then just pull AMD support than figure it
out, since the problem is really with the AMD loader(s).

To me, it was more valuable to have some sort of AMD support rather
than none, and have it done in a way that does not generate errors.
Since it is *extremely* common to use some other library/jQuery plugin
that depends on jQuery and if that file is using jQuery AMD-style, it
will use the 'jquery' as the dependency name, so (this is Claim I) a
paths mapping will need to be in the project anyway. Or better yet,
use convention of baseUrl + 'jquery.js' to avoid the paths mapping
altogether.

It is true that this makes it harder to use jQuery in a case where you
want to load more than one jQuery in a page, but that is a much
smaller use case than what is described above, and it is best if you
can use tools to help you manage that. If tooling is in play, then
that tooling can help manage named renaming. This is what I plan to do
as part of volo[1].

Now, that is *not* to say that named modules should be encouraged. My
doc with these claims in it explicitly encourages using anonymous
modules. I also do not think anything besides jQuery or underscore
should get this treatment.

End story: upgrading the web is hard. AMD needs to get a foothold to
be seen as useful, and jQuery/underscore support is important in that
goal. Even though it causes problems with multiple AMD versions of
jQuery in an AMD project, and initial project setup, it was worth the
tradeoff.

However, I do think we need to do more evangelizing on how best to set
up AMD projects that avoid configuration (drop jQuery in as baseUrl +
'jquery.js' is the way to go). This is also something I want to push
via volo -- it just does this automatically as part of a library add
-- just drops it in baseUrl as 'jquery.js'.

I probably need to make Claim IV clearer -- it is not about AMD users
having an easier time, but demonstrating for non-AMD code that using
AMD will not break things. In other words, because the issues in Claim
III were avoided, AMD will be viewed as safe to call as part of
libraries that AMD users use.

All that said, here are some things we can do to try to get a handle
on how much of a problem Claim III is:

1) I need to make a test or couple of tests as part of the amd-tests
set that tests what happens when an AMD loader gets a call for an
anonymous module when that anonymous module is not loaded by the AMD
loader.

I am fairly sure that because of the nature of browsers' script onload
notifications that an AMD loader cannot reliably discard the
out-of-loader anonymous define() call, and it will interfere with the
pairing logic that matches anonymous modules to names. However, I
could be mistaken, so having some tests to try it and to investigate
what is going on would help. One of my main goals of RequireJS 1.1 is
this work to try to mitigate this effect, and not throw an error if an
anon module comes in from outside the loader.

It would also be good to get a read from other AMD implementers on
what happens if they get an anon module call outside their
loader-specified scripts. Having the tests would help them test the
issue.

2) For underscore, it could be that it is not used commonly in third
party scripts, not how jQuery is used. I am doubtful, since it is even
a more basic library than jQuery. But I do not have enough experience
with that community to know. If anyone has experience in this area,
that would be helpful.

3) I'm open to rewording the document you got these claims from. Maybe
move Claim III to be the first claim? Maybe pull that whole section
out of that doc and just add a link to that "updating existing
libraries" to just have a link to "why are jQuery and underscore named
libraries?" which then goes to a doc with these claims.

In summary: I want to continue working through this by getting some real data.

[1] https://github.com/volojs/volo

James

Daniel D.

unread,
Jan 6, 2012, 10:12:42 PM1/6/12
to amd-im...@googlegroups.com
Indeed see the "error" in scenario you describe with anonymous define
- this is on curl.js

<script src="js/libs/curl-0.5.4.min.js"></script>
<script src="js/libs/jquery.js"></script>
<script src="js/loader.js"></script>

Where loader.js contains:

define(
'jquery'
, ['js/libs/jquery']


, function($){
return $
}
)

require(['jquery'], function($) {
...

That second load blows up with:

Error: Multiple anonymous defines found in ./js/libs/jquery.js.

Sounds like something AMD loaders need to fix though, not something
that would compel me to make jquery named module.

Is there really nothing AMD loaders can do to avoid blowing up on
double-load? This same argument can apply to any other js lib
(BigDecimal is on top of list in our case). We can't make them all
named.

Daniel.

> James

James Burke

unread,
Jan 7, 2012, 1:51:39 AM1/7/12
to amd-im...@googlegroups.com
On Fri, Jan 6, 2012 at 7:12 PM, Daniel D. <dvdot...@gmail.com> wrote:
> Sounds like something AMD loaders need to fix though, not something
> that would compel me to make jquery named module.
>
> Is there really nothing AMD loaders can do to avoid blowing up on
> double-load? This same argument can apply to any other js lib
> (BigDecimal is on top of list in our case). We can't make them all
> named.

Right, this is the difficulty with doing cross browser development but
trying to also develop for the future by just allowing anon modules.
Originally what became the AMD spec did not support anon modules
because we didn't see a way to do it. Fortunately Kris Zyp found a way
to do it. It takes a couple of pathways to support anon modules (of
course IE is different from other browsers).

While we cannot protect against all modules by naming them, once folks
are used to AMD loading, it will be easier to deal with these errors
if they show up for other modules, and we'll likely have some more
tools to help with it too.

But ideally we can make traction in AMD libraries.

I'm hopeful that the actions I do for #1 in my previous list will help
us lock down exactly how much we can mitigate it. I think we might
have a chance to work around the problem, but I will not feel
confident until we have tests and code to prove it out.

It will likely take me a couple of weeks to complete some tests and do
some code inspection for this. However, if you would like to set the
stage by constructing some tests in the meantime, that would be great.
Some tests that would be useful:

1) One similar to what you have now. A basic test.
2) One that does a dynamic, async script load for a script that is
done outside the loader, but while the loader is loading some other
anonymous modules.

I expect the difficulty in the code patch will be handling #2, when
the anonymous define() call happens in the middle of other AMD-loaded
anon script loading. It will also likely take a little bit of tuning
to make sure the test load happens in between files.

James

Daniel D.

unread,
Jan 7, 2012, 1:16:53 PM1/7/12
to amd-im...@googlegroups.com
I already had some anon jquery simulation tests ready. Pushed it here:

https://github.com/dvdotsenko/AMDLoaderTests

test1, 2 are passing, these are simple, test scenarios.
test3 is simulating the double load you described. It fails, as you mentioned.

( I used curl.js because it reliably respects require-nesting order
you see in loader.js. With RequireJS i saw in Chrome plugin code fire
before jquery (real one, that one that loads a long time) exec fully.
I don't want to mix the two problems together for this simulation,
hence the use of curl.js)

test3.html is likely what you refer to as scenario (1) below.

Daniel.

Daniel D.

unread,
Jan 8, 2012, 1:32:45 AM1/8/12
to amd-im...@googlegroups.com
A very minor patch for Curl.JS and it was happily working (fully
ignoring, as opposed to partially ignoring) with AMD-compatible
anon-defined modules loaded outside of require(

(Pushed the patch and note to John Hann.
https://github.com/unscriptable/curl/pull/45)

Ported the tests to RequireJS and, unfortunately see too many things
blowing up. Aside from expected dying on Test3, It was dying on test2.

define(
'baserequirements'
, ['js/libs/jquery.barnacle.js']
, function(){}
)

require(
['jquery', 'baserequirements']
, function($){
$.fn.barnacle()
window.CheckIn("Main Application")
}
)

This is what I saw in log:

/baserequirements.js "Failed to load resource"


Code here, test names start with "rjs_":
https://github.com/dvdotsenko/AMDLoaderTests

I take it there is definitely quite a bit of hacking on AMD-loaders
needed before this "let's make modules anon" issue can be revisited.

Daniel.

Reply all
Reply to author
Forward
0 new messages