cujojs / curl

curl.js is small, fast, extensible module loader that handles AMD, CommonJS Modules/1.1, CSS, HTML/text, and legacy scripts.
https://github.com/cujojs/curl/wiki
Other
1.88k stars 216 forks source link

0.7.3: define() missing or duplicated in a simple module #171

Closed jcollum closed 11 years ago

jcollum commented 11 years ago
define('modTest',
  // module definition function
  function () {
      // return a value that defines the module export
      // (i.e the functionality we want to expose for consumption)

      // create your module here
      var myModule = {
        doStuff:function(){
          console.log('Yay! Stuff');
        }
      }

      return myModule;
  }
);

Markup (jade):

    script
      curl = {
            baseUrl: "/js",
            paths: { "lib" : "/lib" }
          };
    script(src='/lib/curl.js')

Code (from console): curl(['/js/modTest.js'], function(dep){console.log(dep)});

Result: Error: define() missing or duplicated: /js/modTest.js

Umm, sure looks like there's a define there to me.

The relevant lines of code;

            core.loadScript(def,

                function () {
                    var args = argsNet;
                    argsNet = undef; // reset it before we get deps

                    // if our resource was not explicitly defined with an id (anonymous)
                    // Note: if it did have an id, it will be resolved in the define()
                    if (def.useNet !== false) {

                        // if !args, nothing was added to the argsNet
                        if (!args || args.ex) {

Error is on list line.

jcollum commented 11 years ago

Tried the same thing with this, got the same result:

define('simpleMod', [], function() {return {test: 1}})
unscriptable commented 11 years ago

Hey Justin,

AMD resolves the location of things via two steps: 1) module ID resolution, 2) URL resolution. Fortunately or unfortunately, IDs and URLs looks very similar. (Hint: it's the slashes. :) )

The string in the optional first parameter of define() is always a module ID. The strings in curl() -- or in a local require() -- are typically IDs, but can also be URLs for special cases.

In your example, curl(['/js/modTest.js'], function(dep){console.log(dep)});, "/js/modTest.js" is clearly a URL to my eyes. Obviously, it's not clear to curl.js. :) Actually, curl.js always expects IDs, not URLs. (Some other AMD loaders will assume URLs when they see a ".js" extension.)

So, back to your example. You asked curl() to fetch a module called "/js/modTest.js". It found the file and loaded it and found a module named "modTest", so it complained.

Here's how you can fix it:

1) Remove the ID from your define(). The ID is not recommended. It's typically only used by AMD build tools and when declaring modules inside test harnesses. OR...

2) Refer to the module by the ID you gave it in the define(). (Again, the ID is not recommended in most cases.)

curl(['modTest'], doSomething);

3) Map a package (or a path) to the folder with your application's modules. It's not clear to me what that would be from your example since modTest appears to be a stand-alone module. However, if you were to decide to organize your app's files under an "app" package, you packages config might look like this:

packages: [ { name: 'app', location: 'app' } ]

Then, when you have code that relies on the modTest module, you can get to it via an ID of "app/modTest".

curl(['app/modTest'], doSomething);

I hope that helps clear things up!

-- John

unscriptable commented 11 years ago

closing this, but keep posting if you like.

jcollum commented 11 years ago

Totally fixed it. Again, thanks for your patience.

This line:

curl(['modTest']).then(function(dep){ console.log('load complete'); dep.doStuff(); });

Produces this output: load complete Yay! Stuff

Mission accomplished.

unscriptable commented 11 years ago

/me looks for the +1 button....

jcollum commented 11 years ago

I see now why I was confused, Addy has the load going this way:

curl(['app/myModule.js'], 
    function( myModule ){
        // start the main module which in-turn
        // loads other modules
        var module = new myModule();
        module.doStuff();
});

When it should be:

curl(['app/myModule''], 
    function( myModule ){
        // start the main module which in-turn
        // loads other modules
        var module = new myModule();
        module.doStuff();
});

Yes?

jcollum commented 11 years ago

Posted this over on SO, if you want the credit: http://stackoverflow.com/questions/15212375/how-should-i-write-my-define-to-work-with-curl-js. I'll post this answer up later if you can't get to it.

jcollum commented 11 years ago

It found the file and loaded it and found a module named "modTest", so it complained.

As a suggestion, the error that I got didn't say much about the id / url mismatch, so it would be helpful if the error mentioned that.

unscriptable commented 11 years ago

You are right about the error message. Will fix.

irontoby commented 10 years ago

Hello, apologies for bumping a closed issue but this seems related so wanted to post here in case anyone else has the problem...

I am trying to use Curl 0.8.4 to load moment.js version 2.4.0.

There seems to be some overlap in functionality of 'paths' vs. 'packages', but since the latter seems to be the only way to define a loader (I have some scripts that need loading via legacy loader, but not moment), I was using packages for everything to keep my definitions consistent.

If I call curl.config() with a path, everything works fine:

    curl.config({
        paths: {
            'moment': 'Scripts/vendor/moment-2.4.0.min.js',
            // more...
        }
    });

// later on...

curl(['jquery', 'underscore', 'backbone', 'backgrid', 'moment', 'domReady!']).then( ... );

However if I do the same with packages, I get the "define() missing or duplicated: /Path/To/Scripts/vendor/moment-2.4.0.min.js" error:

    curl.config({
        packages: [
        {
            name: 'moment',
            location: 'vendor',
            main: 'moment-2.4.0.min.js'
        }]
    });

// later on...

curl(['jquery', 'underscore', 'backbone', 'backgrid', 'moment', 'domReady!']).then( ... );

Looking at the source for moment.js (which I obviously don't control) it is defining the ID explicitly as "moment", but in both cases I'm using that specific ID, so is there some other nuance of packages vs. paths at play here or is there something else that's getting me?