CanJS and AMD #46

Closed
jeffrose opened this Issue May 29, 2012 · 22 comments

Comments

Projects
None yet
@jeffrose

Currently CanJS seems to have incomplete support for AMD. The core files, e.g. can.jquery.js, define themselves if an AMD loader is present.

define( "can", [], function () { return can; } );

However they do not set any dependencies. For libraries that support AMD, e.g. jquery, dojo, etc., the core files should list them as such.

define( "can", [ "jquery" ], function () { return can; } );

Otherwise it becomes necessary to do a workaround to make sure the dependent library is loaded before loading canjs.

Similarly, canjs plugins need to register themselves as AMD modules which depend on the core canjs module.

define( "can/construct/super", [ "can" ], function () { return null; } );

-Jeff

@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer May 29, 2012

Contributor

Yep. Ralph, can you take a look at this?

Sent from my iPhone

On May 29, 2012, at 1:24 PM, Jeff Rosereply@reply.github.com wrote:

Currently CanJS seems to have incomplete support for AMD. The core files, e.g. can.jquery.js, define themselves if an AMD loader is present.

define( "can", [], function () { return can; } );

However they do not set any dependencies. For libraries that support AMD, e.g. jquery, dojo, etc., the core files should list them as such.

define( "can", [ "jquery" ], function () { return can; } );

Otherwise it becomes necessary to do a workaround to make sure the dependent library is loaded before loading canjs.

Similarly, canjs plugins need to register themselves as AMD modules which depend on the core canjs module.

define( "can/construct/super", [ "can" ], function () { return null; } );

-Jeff


Reply to this email directly or view it on GitHub:
#46

Contributor

justinbmeyer commented May 29, 2012

Yep. Ralph, can you take a look at this?

Sent from my iPhone

On May 29, 2012, at 1:24 PM, Jeff Rosereply@reply.github.com wrote:

Currently CanJS seems to have incomplete support for AMD. The core files, e.g. can.jquery.js, define themselves if an AMD loader is present.

define( "can", [], function () { return can; } );

However they do not set any dependencies. For libraries that support AMD, e.g. jquery, dojo, etc., the core files should list them as such.

define( "can", [ "jquery" ], function () { return can; } );

Otherwise it becomes necessary to do a workaround to make sure the dependent library is loaded before loading canjs.

Similarly, canjs plugins need to register themselves as AMD modules which depend on the core canjs module.

define( "can/construct/super", [ "can" ], function () { return null; } );

-Jeff


Reply to this email directly or view it on GitHub:
#46

@jeffrose

This comment has been minimized.

Show comment
Hide comment
@jeffrose

jeffrose May 30, 2012

If you need guidance on adding optional AMD compatibility to code, check out https://github.com/umdjs/umd

If you need guidance on adding optional AMD compatibility to code, check out https://github.com/umdjs/umd

@ralphholzmann

This comment has been minimized.

Show comment
Hide comment
@ralphholzmann

ralphholzmann Jun 5, 2012

Hey, yep. This is a good call. I'm just wondering how to support this for all the different library builds. If I recall correctly, jQuery and Dojo support an optional AMD call, but does Zepto, YUI, MooTools?

I'll work on this this afternoon.

Hey, yep. This is a good call. I'm just wondering how to support this for all the different library builds. If I recall correctly, jQuery and Dojo support an optional AMD call, but does Zepto, YUI, MooTools?

I'll work on this this afternoon.

@jeffrose

This comment has been minimized.

Show comment
Hide comment
@jeffrose

jeffrose Jun 5, 2012

I believe only jQuery and Dojo support it right now.

YUI has it's own non-AMD loader.

Zepto seems unwilling to add AMD support at this time (madrobby/zepto#342).

MooTools seems to be in the middle of adding support (mootools/mootools-core#2102).

That being the case, you could either not list those libraries as dependencies in your define() or make certain assumptions:

  • Developer is using RequireJS 2.0+ (or equivalent), which does not need loader plugins for non-AMD files
  • Developer has shimmed the non-AMD library in configuration (http://requirejs.org/docs/api.html#config-shim)
  • Developer used the name of the library in all lowercase as the module name

This would result in:

define( "can", [ "zepto" ], function () { return can; } );
define( "can", [ "mootools" ], function () { return can; } );
define( "can", [ "yui" ], function () { return can; } );

The only other choice would be to make it configurable in some way. That is, exclude the dependency unless the developer has set a value somewhere and use that value.

Doing that would open it up to developers that are not using RequireJS 2.0+ as they could set the value to include loader plugins.

// Load Zepto using the WrapJS loader plugin in RequireJS < 2.0
define.amd.Zepto = "wrap!zepto"
...
var deps = [];
define.amd.Zepto && deps.push( define.amd.Zepto );
define( "can", deps, function () { return can; } );

In my example, I am bastardizing the define.amd (https://github.com/amdjs/amdjs-api/wiki/AMD#wiki-defineAmd) object a little. James Burke (@jrburke) might have a better suggestion.

For plugins, it should not matter as they only need to list "can" as a dependency.

jeffrose commented Jun 5, 2012

I believe only jQuery and Dojo support it right now.

YUI has it's own non-AMD loader.

Zepto seems unwilling to add AMD support at this time (madrobby/zepto#342).

MooTools seems to be in the middle of adding support (mootools/mootools-core#2102).

That being the case, you could either not list those libraries as dependencies in your define() or make certain assumptions:

  • Developer is using RequireJS 2.0+ (or equivalent), which does not need loader plugins for non-AMD files
  • Developer has shimmed the non-AMD library in configuration (http://requirejs.org/docs/api.html#config-shim)
  • Developer used the name of the library in all lowercase as the module name

This would result in:

define( "can", [ "zepto" ], function () { return can; } );
define( "can", [ "mootools" ], function () { return can; } );
define( "can", [ "yui" ], function () { return can; } );

The only other choice would be to make it configurable in some way. That is, exclude the dependency unless the developer has set a value somewhere and use that value.

Doing that would open it up to developers that are not using RequireJS 2.0+ as they could set the value to include loader plugins.

// Load Zepto using the WrapJS loader plugin in RequireJS < 2.0
define.amd.Zepto = "wrap!zepto"
...
var deps = [];
define.amd.Zepto && deps.push( define.amd.Zepto );
define( "can", deps, function () { return can; } );

In my example, I am bastardizing the define.amd (https://github.com/amdjs/amdjs-api/wiki/AMD#wiki-defineAmd) object a little. James Burke (@jrburke) might have a better suggestion.

For plugins, it should not matter as they only need to list "can" as a dependency.

@jeffrose

This comment has been minimized.

Show comment
Hide comment
@jeffrose

jeffrose Jun 5, 2012

Looking over my own reply, this may make more sense:

// Load Zepto using the WrapJS loader plugin in RequireJS < 2.0
define.shim = define.shim || {};
define.shim.Zepto = "wrap!zepto";
...
var deps = [];
define.shim && define.shim.Zepto && deps.push( define.shim.Zepto );
define( "can", deps, function () { return can; } );

jeffrose commented Jun 5, 2012

Looking over my own reply, this may make more sense:

// Load Zepto using the WrapJS loader plugin in RequireJS < 2.0
define.shim = define.shim || {};
define.shim.Zepto = "wrap!zepto";
...
var deps = [];
define.shim && define.shim.Zepto && deps.push( define.shim.Zepto );
define( "can", deps, function () { return can; } );
@jrburke

This comment has been minimized.

Show comment
Hide comment
@jrburke

jrburke Jun 5, 2012

Some quick notes, related to upgrading existing libraries:

  1. I suggest not using named modules for "can", use anonymous definitions. So prefer this:
define([], function () {});

not this:

define('can', [], function () {});
  1. I would not put anything in the define.amd area. That is just for a loader to set up, and the define.amd.jQuery thing was a special allowance given the high usage of jQuery even multiple versions on a page.

  2. I would not use the define() API if YUI is being used, but instead favor their module format. Since Zepto is supposed to be a replacement for jQuery, and if CanJS only uses APIs on it that also map to jQuery's APIs, then I would just use the 'jquery' name as the dependency. The end developer can use the AMD loader and configure it so that 'jquery' resolves to Zepto if they want. If you need instructions for RequireJS, I'm happy to provide them. So for the jQuery and Zepto options, do something like:

if (typeof define === 'function' && define.amd) {
    define(['jquery'], function ($) {});
}

If you need help fitting that in to your existing code, feel free to give me a pointer to where or the ideal way you would like to see it done and I can help out. Although hopefully the aforementioned umdjs/umd code templates help.

For Dojo, I would try to be specific about what modules you need from it, if that is an option:

    define(['dojo/array', 'dojo/event'], function (array, event) {});

Not sure if those are the right module IDs but hopefully that conveys the idea.

Similar for MooTools, although I am not experienced enough with that library to know. But if unsure, if they export one single object that has the methods you need:

    define(['mootools'], function (mootools) {});

jrburke commented Jun 5, 2012

Some quick notes, related to upgrading existing libraries:

  1. I suggest not using named modules for "can", use anonymous definitions. So prefer this:
define([], function () {});

not this:

define('can', [], function () {});
  1. I would not put anything in the define.amd area. That is just for a loader to set up, and the define.amd.jQuery thing was a special allowance given the high usage of jQuery even multiple versions on a page.

  2. I would not use the define() API if YUI is being used, but instead favor their module format. Since Zepto is supposed to be a replacement for jQuery, and if CanJS only uses APIs on it that also map to jQuery's APIs, then I would just use the 'jquery' name as the dependency. The end developer can use the AMD loader and configure it so that 'jquery' resolves to Zepto if they want. If you need instructions for RequireJS, I'm happy to provide them. So for the jQuery and Zepto options, do something like:

if (typeof define === 'function' && define.amd) {
    define(['jquery'], function ($) {});
}

If you need help fitting that in to your existing code, feel free to give me a pointer to where or the ideal way you would like to see it done and I can help out. Although hopefully the aforementioned umdjs/umd code templates help.

For Dojo, I would try to be specific about what modules you need from it, if that is an option:

    define(['dojo/array', 'dojo/event'], function (array, event) {});

Not sure if those are the right module IDs but hopefully that conveys the idea.

Similar for MooTools, although I am not experienced enough with that library to know. But if unsure, if they export one single object that has the methods you need:

    define(['mootools'], function (mootools) {});
@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer Jun 5, 2012

Contributor

@jrburke Thanks for the suggestions! Your insight is super helpful.

Why should we not used name modules? I thought this was so people could have multiple modules in the same file.

When would be the appropriate time to use named modules?

For Zepto, we have a bunch of additional modifications to add features that jQuery supports. I'm not sure if knowing that changes anything ...

Thanks again!

Contributor

justinbmeyer commented Jun 5, 2012

@jrburke Thanks for the suggestions! Your insight is super helpful.

Why should we not used name modules? I thought this was so people could have multiple modules in the same file.

When would be the appropriate time to use named modules?

For Zepto, we have a bunch of additional modifications to add features that jQuery supports. I'm not sure if knowing that changes anything ...

Thanks again!

@jrburke

This comment has been minimized.

Show comment
Hide comment
@jrburke

jrburke Jun 5, 2012

@justinbmeyer some notes here about anonymous modules:

https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon

but basically it is more flexible.

For Zepto, it may make sense to do:

    define(['zepto'], function ($) {});

if you add things specifically to zepto. RequireJS users can map 'zepto' to 'jquery' if they end up somehow with the zepto version of canjs in their jquery-based project. But in that case, you may want to feature detect any additions to the $ object or be sure the changes are non-destructive. Or, just tell them to swap out the canjs with the jquery one. That seems reasonable too.

jrburke commented Jun 5, 2012

@justinbmeyer some notes here about anonymous modules:

https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon

but basically it is more flexible.

For Zepto, it may make sense to do:

    define(['zepto'], function ($) {});

if you add things specifically to zepto. RequireJS users can map 'zepto' to 'jquery' if they end up somehow with the zepto version of canjs in their jquery-based project. But in that case, you may want to feature detect any additions to the $ object or be sure the changes are non-destructive. Or, just tell them to swap out the canjs with the jquery one. That seems reasonable too.

@jeffrose

This comment has been minimized.

Show comment
Hide comment
@jeffrose

jeffrose Jun 5, 2012

Thanks, James.

The reason for doing...

define( 'can', [], function () {});

... instead of doing...

define([], function () {});

... is that there is no single CanJS core. CanJS sits on top of various libraries, e.g. jQuery, MooTools, etc. If the module was anonymous, you would end up with a core module that has different names, .e.g. "can.jquery", "can.mootools", etc. unless you rename the core file you decide to use. For the sake of CanJS plugins, I would think there needs to be a single, core "can" module.

I think hiding Zepto as jQuery is definitely the way to go.

jeffrose commented Jun 5, 2012

Thanks, James.

The reason for doing...

define( 'can', [], function () {});

... instead of doing...

define([], function () {});

... is that there is no single CanJS core. CanJS sits on top of various libraries, e.g. jQuery, MooTools, etc. If the module was anonymous, you would end up with a core module that has different names, .e.g. "can.jquery", "can.mootools", etc. unless you rename the core file you decide to use. For the sake of CanJS plugins, I would think there needs to be a single, core "can" module.

I think hiding Zepto as jQuery is definitely the way to go.

@jrburke

This comment has been minimized.

Show comment
Hide comment
@jrburke

jrburke Jun 5, 2012

@jeffrose by impression though is that only one can.* is used in a project at a time, so it is OK if they are anonymous. Also any module ID that is used for a named module should match the file name by default. But I'm still new to the expectations around the use of canjs.

jrburke commented Jun 5, 2012

@jeffrose by impression though is that only one can.* is used in a project at a time, so it is OK if they are anonymous. Also any module ID that is used for a named module should match the file name by default. But I'm still new to the expectations around the use of canjs.

@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer Jun 5, 2012

Contributor

One thing I've been thinking about is letting people require only the parts of canjs they need. The project is already broken up like this. For example,

require(['can/control'], function(can){
})

But i'd still like people to be able to require(['can']) so they aren't always writing:

require(['can/control','can/model','can/view/ejs','can/route'])
Contributor

justinbmeyer commented Jun 5, 2012

One thing I've been thinking about is letting people require only the parts of canjs they need. The project is already broken up like this. For example,

require(['can/control'], function(can){
})

But i'd still like people to be able to require(['can']) so they aren't always writing:

require(['can/control','can/model','can/view/ejs','can/route'])
@jrburke

This comment has been minimized.

Show comment
Hide comment
@jrburke

jrburke Jun 5, 2012

@justinbmeyer there can be a top level can.js file that does all of those require calls and exports a module for those values:

define(function (require) {
    return {
        control: require('can/control'),
        model: require('can/model'),
        ejs: require('can/view/ejs'),
        route: require('can/route')
    };
});

So if they want the whole thing, require 'can' instead of 'can/control'.

jrburke commented Jun 5, 2012

@justinbmeyer there can be a top level can.js file that does all of those require calls and exports a module for those values:

define(function (require) {
    return {
        control: require('can/control'),
        model: require('can/model'),
        ejs: require('can/view/ejs'),
        route: require('can/route')
    };
});

So if they want the whole thing, require 'can' instead of 'can/control'.

@jeffrose

This comment has been minimized.

Show comment
Hide comment
@jeffrose

jeffrose Jun 14, 2012

This issue has certainly grown bigger than I expected, though I am encouraged by the direction, i.e. being able require individual modules as well as CanJS as a whole. I understand this isn't a priority compared to other issues but I was wondering if there was a plan of attack? Is this something we would see in the next release?

This issue has certainly grown bigger than I expected, though I am encouraged by the direction, i.e. being able require individual modules as well as CanJS as a whole. I understand this isn't a priority compared to other issues but I was wondering if there was a plan of attack? Is this something we would see in the next release?

@ghost ghost assigned ralphholzmann Jun 29, 2012

@jeffrose

This comment has been minimized.

Show comment
Hide comment
@jeffrose

jeffrose Aug 13, 2012

As part of this effort, it would be nice to see can/util/zepto/fill (and similar) broken up into separate files, like can/util/zepto/fill/promise, can/util/zepto/fill/ajax, etc. This would provide the flexibility of letting developers choose how to fill those gaps. For instance, using whenjs for promises.

As part of this effort, it would be nice to see can/util/zepto/fill (and similar) broken up into separate files, like can/util/zepto/fill/promise, can/util/zepto/fill/ajax, etc. This would provide the flexibility of letting developers choose how to fill those gaps. For instance, using whenjs for promises.

@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer Aug 13, 2012

Contributor

I'm not sure that would help as they wouldn't be present on can. You'd hace to create your own to map to the can object.

Btw, deferred is a separate file.

And outside deferred, there's little that could be swapped.

And, we wouldn't want these things in zepto probably, but in can/util directly so they can be used by other base libraries.

Sent from my iPhone

On Aug 12, 2012, at 5:17 PM, Jeff Rose notifications@github.com wrote:

As part of this effort, it would be nice to see can/util/zepto/fill (and similar) broken up into separate files, like can/util/zepto/fill/promise, can/util/zepto/fill/ajax, etc. This would provide the flexibility of letting developers choose how to fill those gaps. For instance, using whenjs for promises.


Reply to this email directly or view it on GitHub.

Contributor

justinbmeyer commented Aug 13, 2012

I'm not sure that would help as they wouldn't be present on can. You'd hace to create your own to map to the can object.

Btw, deferred is a separate file.

And outside deferred, there's little that could be swapped.

And, we wouldn't want these things in zepto probably, but in can/util directly so they can be used by other base libraries.

Sent from my iPhone

On Aug 12, 2012, at 5:17 PM, Jeff Rose notifications@github.com wrote:

As part of this effort, it would be nice to see can/util/zepto/fill (and similar) broken up into separate files, like can/util/zepto/fill/promise, can/util/zepto/fill/ajax, etc. This would provide the flexibility of letting developers choose how to fill those gaps. For instance, using whenjs for promises.


Reply to this email directly or view it on GitHub.

@jeffrose

This comment has been minimized.

Show comment
Hide comment
@jeffrose

jeffrose Aug 13, 2012

Yes, I was thinking you would create your own mapping in the RequireJS config to swap in the implementation you want to use for whatever can was expecting.

Ah, I see Deferred in can/util/deferred but I see the same code in can/util/zepto/fill (along with other code). I have to assume that can/util/zepto/fill must be built from multiple other files.

I do agree that outside of something common like Promises and maybe some language polyfill (e.g. an implementation of Array.reduce), there's little that could be swapped.

Yup, I was just using can/util/zepto/fill as an example.

Yes, I was thinking you would create your own mapping in the RequireJS config to swap in the implementation you want to use for whatever can was expecting.

Ah, I see Deferred in can/util/deferred but I see the same code in can/util/zepto/fill (along with other code). I have to assume that can/util/zepto/fill must be built from multiple other files.

I do agree that outside of something common like Promises and maybe some language polyfill (e.g. an implementation of Array.reduce), there's little that could be swapped.

Yup, I was just using can/util/zepto/fill as an example.

@saunders99999

This comment has been minimized.

Show comment
Hide comment
@saunders99999

saunders99999 Oct 18, 2012

Hi, will this issue be resolved in the next release?

Hi, will this issue be resolved in the next release?

@patie

This comment has been minimized.

Show comment
Hide comment

patie commented Oct 18, 2012

+1

@guilambert

This comment has been minimized.

Show comment
Hide comment

+1

@jaxley

This comment has been minimized.

Show comment
Hide comment
@jaxley

jaxley Oct 31, 2012

+1, and Bump. What is the status of AMD support now?

jaxley commented Oct 31, 2012

+1, and Bump. What is the status of AMD support now?

@rjgotten

This comment has been minimized.

Show comment
Hide comment
@rjgotten

rjgotten Nov 6, 2012

I would really like to know the status of this as well. Will AMD support make it for 1.1 ?

rjgotten commented Nov 6, 2012

I would really like to know the status of this as well. Will AMD support make it for 1.1 ?

@daffl

This comment has been minimized.

Show comment
Hide comment
@daffl

daffl Nov 14, 2012

Contributor

The version 1.1.0 download contains an amd folder that allows you to dynamically load any CanJS components and plugins as AMD modules. See the overview docs for an example on how to use it with RequireJS and jQuery. Let us know what you think and if you find any issues!

Contributor

daffl commented Nov 14, 2012

The version 1.1.0 download contains an amd folder that allows you to dynamically load any CanJS components and plugins as AMD modules. See the overview docs for an example on how to use it with RequireJS and jQuery. Let us know what you think and if you find any issues!

@daffl daffl closed this Nov 14, 2012

justinbmeyer added a commit that referenced this issue Oct 10, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment