Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Uncaught Error: define() missing or duplicated #256

Open
jdalton opened this issue Feb 15, 2014 · 11 comments
Open

Uncaught Error: define() missing or duplicated #256

jdalton opened this issue Feb 15, 2014 · 11 comments

Comments

@jdalton
Copy link

jdalton commented Feb 15, 2014

I'm experimenting with making the lodash.underscore.js build use a named module: define('underscore', function() { return lodash; });

I'm using a package config to work with amd modules and single files.
I noticed if I used the config of:

'packages': [
{
'name':'underscore',
'location': '../dist',
'main': 'lodash.underscore'
}
]

I get Uncaught Error: define() missing or duplicated: ./../dist/lodash.underscore.js.
The Dojo and RequireJS loader both work.

@unscriptable
Copy link
Member

Hey @jdalton!

tl;dr: curl.js expects a named module to use its true name. There are no single-word module names in a package. All module names in packages take the form "[package-name]/[module-path]". Underscore is a collection of things all shoved onto one, large module. It is not truly a package. Since underscore is not actually a package, you shouldn't use a package descriptor to configure it. If you use a "paths" configuration descriptor, it will work.

Some background: there are two general types of transformation in AMD: id transforms and url transforms. (I like to call the latter "path transforms" since they could also be performed on file system paths, not just urls.)

Path transforms are useful for describing where things are located. ID transforms can be used for a few useful things, including the resolution of the convenient, short-form main module name of a package. I'll use when.js as an example. The package is named "when", but the main module is actually named "when/when". If you type var when = require("when");, you're using a shortcut since there really is no module named "when". If that's not immediately clear to you, then let's look at some code:

// this is inside module when/node/function
when = require('../when');

According to the CommonJS and AMD rules for name normalization, "../when" should resolve to "when/node/../when", which collapses to "when/when". (I can provide detail on the name normalization, if you like.) By this, you can infer that the when/node/function module is looking for a module named "when/when", not "when".

For historical reasons that I missed, folks apparently didn't want to force devs to write var when = require("when/when"); in their code. To get around this, package metadata files (e.g. package.json, bower.json) and AMD package configuration descriptors provide a "main" property that can be used to convert the convenient short-form (e.g. "when") to the true form (e.g. "when/when", which is also the normalized form!).

Side note: AMD package descriptors define both types of transforms. The "location" property defines the path transform that will be applied to all modules in the package. The "main" property describes the id transform that will happen when somebody decides to use the convenient, short-form name of the package's main module.

I'd argue that underscore isn't a package. It's a module wherein all of it's bits are shoved onto a single object. Perhaps one could envision it as a package with exactly one module. However, if it's a package at all, then curl.js requires that its modules be named "[package-name]/[module-path]".

If you envision underscore as a module, then you could use a "paths" configuration descriptor instead of "packages" and it should just work in all of the loaders.

Apparently, both dojo and RequireJS have logic to accommodate these single-module-packages-masquerading-as-just-a-module things. I looked into providing similar capabilities a few years back and concluded that it could cause problems for multi-bundle, multi-version application development (iow: apps that fetch multiple bundles and could potentially have multiple versions of a package -- such as lodash or jquery -- in these bundles).

I've been working on this multi-version stuff lately and am using a solution that seems like it would be immune to this problem. Still, I am hesitant to add more special-case logic into an already convoluted part of AMD.

If you've read this far, then I'm really interested in your feedback.

Thanks for posting this issue.

-- John

P.S. Names modules suck for many reasons.

@jdalton
Copy link
Author

jdalton commented Feb 15, 2014

P.S. Named modules suck for many reasons.

True, but Underscore now uses it.

Testing before worked with unnamed modules for both single file and collections using packages. The unit test file works with both depending on the path passed to its query string. I used paths because they worked for both and didn't require any additional juggling on my end.

I'm curious why the unnamed flavor works as single files with a package config.

@unscriptable
Copy link
Member

True, but Underscore now uses it.

Oh snap. That's idiotic.

I used paths because they worked for both and didn't require any additional juggling on my end.

So, you're using paths instead of packages in your unit tests now -- and it's working? Ok. I'm closing
the issue, but we can still chat about packages. vs. named-single-module-package-things if you like.

I'm curious why the unnamed flavor works as single files with a package config.

Because when the module is anonymous, the loader assigns the name. In this case, that name is "underscore/underscore". :)

@jdalton
Copy link
Author

jdalton commented Feb 15, 2014

So, you're using paths instead of packages in your unit tests now -- and it's working?

Wow, I typoed hard on that one. I've been using packages because they worked for both single file and modules and didn't require any additional juggling on my end.

@jdalton
Copy link
Author

jdalton commented Feb 15, 2014

The packages worked before when I defined them anonymously, it seems to error now though that I named it "underscore" in lodash.underscore.js (the reason I opened this issue).

@unscriptable
Copy link
Member

Ok, reopening. :)

I'm seriously confused, dude.

I've been using packages because they worked for both single file and modules

What do you mean by "both single file and modules"? Maybe you could paint a precise picture of what you want to do? Can you point me at some source code? Thanks.

@unscriptable unscriptable reopened this Feb 15, 2014
@jdalton
Copy link
Author

jdalton commented Feb 15, 2014

I'm seriously confused, dude.

Sorry, I haven't eaten or had coffee yet, my brain is mush ☕ 🍰
I'm Hulk smashing my keyboard trying to communicate :)

I mean I use them for things like lodash.underscore.js a single module but also for things like ../lodash-amd/modern/main.js which then loads in other modules like ../lodash-amd/modern/collections/forEach.js.

@jdalton
Copy link
Author

jdalton commented Feb 15, 2014

My config look something like

'packages': [
    {
        'name': moduleId,
        'location': locationPath,
        'main': moduleMain
    }
]

and allows testing for both monolithic builds and modularized builds by passing query params to the test page.

@unscriptable
Copy link
Member

As much as it pains me to add more special-casing for your situation (and another that @phated encountered in cram.js builds a few weeks ago), I think I have to bite the bullet in order to plug an AMD abstraction leak. Ick. Oh well.

The fix will allow the true name ("underscore/underscore") as well as the shortcut name ("underscore") to work in builds and when the non-modular version of lodash is declared as a package in an AMD config.

I'll have a fix released in couple of days. Does that work for you?

-- J

@unscriptable
Copy link
Member

Sorry if that comment made it sounds like this is a lodash problem. It's not. It's a combination of the looseness of AMD and its recommended pattern for backwards-compatibility for legacy libraries (or curl.js's lack of completely implementing this pattern).

@jdalton
Copy link
Author

jdalton commented Feb 18, 2014

No worries. That works for me. In the meantime I've modified my test page to detect, based on the query string, if its a modularized or monolithic build and either add packages or paths configs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants