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

Question about API (need way to defer module resolution based on feature detection besides has! plugin) #55

Closed tmaslen closed 12 years ago

tmaslen commented 12 years ago

Hello,

I would like to use this syntax with curl.js...

var foo = require('foo');

I've seen this in the requirejs documentation, however it doesn't appear to work with curl.js. I get the following error:

"Uncaught Error: Module is not already resolved: foo"

Is this even supported in curl.js?

Many thanks, using curl.js extensively in our project. We really like it. /t

unscriptable commented 12 years ago

Hey Tom,

There are some serious limitations to using that syntax in 0.5.x. It works, but it's not useful as it is in 0.6, which I am trying to get released this week. In a nutshell, here's how it will work:

Getting the local require function:

You can grab a reference to the local require function (and the other CJS "free" variables) in any module in one of two ways.

  1. if a module has no dependencies and uses definition function (aka factory function) with parameters it is assumed to be a CommonJS Module that has been wrapped in an AMD wrapper. At execution time, these parameters are fulfilled with the three main CJS "free variables": require, exports, and module -- in this exact order. define(function (require, exports, module) { /* blah */ });
  2. a module can request these same variables as pseudo-dependencies by asking for them by name: define(["require"], function (require) { /* blah */ });

Using the local require:

In case 1, curl.js will scan the module for uses of require as an r-value (require with a single string param). It appends those modules to the dependency list, fetches them, and then executes the definition function. Therefore, any calls to require('myPkg/myModule') are simply lookups, no async fetching is needed since it happened before the definition function executed.

Case 2 is meant mostly for async fetching, but can be used for r-value functionality if you can guarantee that the module has already been fetched. The easiest way to do this is to place that module in the dependency list. Unless you're using a build tool that does this automatically, this is somewhat redundant.

Please note that the lookup rules for the module id in the r-value require() are the same as for the dependencies in the define(). You should probably use relative module ids for modules in the same folder or package. var sibling = require("./sibling");.

If you start sing the "exports" variable/pseudo-dep as well, there's a whole lot more to the story. I hope to have some docs up on the wiki soon after the 0.6 release.

Regards,

-- John

unscriptable commented 12 years ago

Oops. Reopening the issue until 0.6 is released.

tmaslen commented 12 years ago

Hi unscriptable,

Thanks for that. Now that I've plugged 0.6 into my app I can now do this...

define(['require', 'module/foo'], function(require) { var foo = require('./foo'); return foo; });

I think I've been attempting to do something that is not possible with an AMD loader. For example, I was hoping to do the following...

define(function() { var history; if ('history' in window) { history = require('./nativeHistory'); } else { history = require('./polyfilledHistory'); } return history; });

Then only the module that was required would be downloaded. But this is not possible without the referenced modules being fetched already, as everything is async?

Regards, /t

unscriptable commented 12 years ago

Hey Tom,

You are right: using r-value require() isn't going to work in this case. However, it is certainly possible to dynamically load a module based upon feature detection. The easiest way is to use the async require(), but that won't work in your case since your code returns immediately. Plugins are necessary for this type of behavior. You have (at least) two choices:

  1. use the async! plugin.
  2. use the has! plugin

async! plugin:

The async! plugin was meant for this purpose, but is not very desirable since it forces you to specify "async!mylib/moduleId" all over the code rather than just "mylib/moduleId". I was just looking at that module and found a way to remove the need for the plugin syntax. This will likely be a 0.6.1 feature, but I can put something up in a branch for you to try out. I'll reopen this issue to track the progress of that new feature.

has! plugin:

A while back, a group of devs devised the has.js project. There has been some talk about building a has! plugin off of it. The dojo guys succeeded and are using it exhaustively in dojo 1.7.x. We've been toying with the idea. In fact, @briancavalier just did something very similar to what you proposed using the has! plugin last week. I don't know if the code is in a usable state, but I'll check with him to see if we can post a gist somewhere.

In either case, this use case should go onto the wiki.

-- John

tmaslen commented 12 years ago

Thanks unscriptable. :-)

unscriptable commented 12 years ago

Hey Tom,

I know this is a very old issue. Did you ever find a solution? I just realized we could do something like this:

// history.js
define([('history' in window) ? './nativeHistory' : './polyfilledHistory'], function(history) {
    return history;
});

AMD build tools won't bake-in either nativeHistory or polyfilledHistory. If you want to ensure one of them is baked-in, do this:

// history.js
// first dependency is ignored if it's falsey (but some AMD loaders may choke on this)
define([!('history' in window) && './polyfilledHistory', './nativeHistory'], function(polyfilled, native) {
    return polyfilled || native;
});

Please reopen if you're not satisfied with my answer :)

-- J

tmaslen commented 12 years ago

Hey,

Yes this would work, that's obvious now :-)

We ended up wrapping every single AMD module into an all.js as it was so tiny (14kb) it was just easier that way.

But I'll definitely remember to use this technique if we need to conditionally load an AMD module.

You're awesome, /t