You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@cordova.apache.org by Patrick Mueller <pm...@gmail.com> on 2011/12/08 17:34:02 UTC
CommonJS and AMD (and, not or)
Time to stir the old CommonJS vs AMD wasp's nest again. No real questions
here, more of a status/brain-dump from me.
One thing that may not have been clear from previous discussions, is that
I'm actually pro-AMD when it comes to using the AMD boilerplate as a
transport mechanism. Basically, I'm one of those people (the only one?)
who is happy with a work-flow of:
* author your module in CoffeeScript
* when you module changes, your magic auto-builder converts the
CoffeeScript to JS, then wraps the module in AMD boilerplate
My AMD API usage is minimal; all the modules I generate have the following
boilerplate:
define(insert-module-id-here, function(require,exports,module) {
common-js-authored-module-here
})
I never specify the dependency array. dependencies are retrieved via calls
to require()in the common-js-authored-module-here section. I also,
currently, make use of the pattern to specify what's exported, via the
node-compatible pattern of:
module.exports = blah-blah
I only ever use require() in the following form:
require(insert-module-name-here)
I'm actually doing this, right now, in an internal project, and am quite
happy with the work-flow. If you're curious, the "build" takes less than a
second to run, with some latency for determining when source files have
changed. And, I'm using James Burke's almond AMD runtime as my module
runtime system - https://github.com/jrburke/almond .
I will likely be converting weinre to this style soon, also using almond.
James was kind enough to fix a few bugs in almond yesterday that make me
quite happy with using this as a module runtime system for deployed
applications - and for me, at development-time as well.
I have an outstanding request on the AMD-implementors ml -
https://groups.google.com/d/topic/amd-implement/REKXB6Kbf00/discussion - to
discuss how to consume non-trivial node.js-styled package/module directory
trees. This is fairly important to me. If we can't get this sort of
functionality into whatever AMD loader story we might end up with, I'd want
to "extend" that AMD loader story so that it CAN support it. To me, today,
that would mean forking almond into Cordova and extending it there.
I'm way more interested in consuming existing and future npm packages than
I am in codifying our modules as npm compatible, but would like that too.
Meaning, if we have to, I can live with using AMD APIs and patterns in
Cordova code that aren't portable to node.js, but would rather we didn't.
--
Patrick Mueller
http://muellerware.org
Re: CommonJS and AMD (and, not or)
Posted by Filip Maj <fi...@adobe.com>.
I'll whip up an example when I get a chance and we'll see where it goes
from there.
On 11-12-13 1:24 PM, "Patrick Mueller" <pm...@gmail.com> wrote:
>On Tue, Dec 13, 2011 at 15:02, Filip Maj <fi...@adobe.com> wrote:
>
>> I think dynamic module loading is a use case that must be covered once
>>we
>> switch to a modularized JS... Might as well eh?
>
>
>As long as it doesn't negatively impact the ability to easily "build" a
>cat'd stand-alone file, sounds good.
>
>--
>Patrick Mueller
>http://muellerware.org
Re: CommonJS and AMD (and, not or)
Posted by Patrick Mueller <pm...@gmail.com>.
On Tue, Dec 13, 2011 at 15:02, Filip Maj <fi...@adobe.com> wrote:
> I think dynamic module loading is a use case that must be covered once we
> switch to a modularized JS... Might as well eh?
As long as it doesn't negatively impact the ability to easily "build" a
cat'd stand-alone file, sounds good.
--
Patrick Mueller
http://muellerware.org
Re: CommonJS and AMD (and, not or)
Posted by James Burke <jr...@gmail.com>.
On Tue, Dec 13, 2011 at 1:43 PM, Patrick Mueller <pm...@gmail.com> wrote:
> This is using the anonymous module pattern, instead of naming the module by
> passing an id as the first parameter to define(). Unfortunately, it seems
> difficult to convert this pattern into almond usage, as almond requires
> that you have an id as the first parameter (as near as I can tell, for a
> normal module). To use an anonymous module with almond, you have to
> rewrite the module to add the id at build time (ick!) or you could resort
> to horrible, hideous hacks (you don't want to know!).
Yes, the build step needs to inject a module ID as part of
concatenating anonymous modules together. It is not that bad (a regexp
can handle it), or use r.js, the requirejs optimizer.
Using the requirejs optimizer also allows running AMD loader plugins
as part of build so things like i18n bundles, text templates can be
inlined in the built file. As simple file concat will not get that.
r.js is just a single JS file, runs in node or rhino.
> A named, wrapped CJS module as AMD would look like:
>
> define(moduleId, function(require, exports, module){
> //CJS stuff goes in here.
> })
>
> I'm not sure what breaks when you DO use an id; is that an issue with
> dynamic loading?
You can manually code in a module ID as part of the JS source, and it
will load, but it is not considered best practice. You lose some
flexibility with renaming/moving the file in that case (you then need
to remember to open the file and edit the name).
Naming modules is really a build task anyway. It would have to be done
for CJS modules that get wrapped. While in the CJS case it is just a
simple function wrapping, using a regexp applied to in-source
anonymous define() calls gives these benefits:
* more flexibility in file renaming/moving
* the in-source wrapping shorter
* modules can be loaded dynamically, and the developer does not *have*
to make sure everything is built into one file or converted to a
format the browser will understand.
For me, that is a reasonable tradeoff, particularly since there are
tools that can do the work today. In addition to the requirejs
optimizer, there is one in dojo, and curl's builder, cram, can do that
naming work too.
If AMD loader plugins look interesting to use, particularly during a
build, then using a full AMD-aware builder will be needed anyway,
since it actually runs the loader plugins during the build step to get
their output to inject into the built file.
James
Re: CommonJS and AMD (and, not or)
Posted by Patrick Mueller <pm...@gmail.com>.
On Tue, Dec 13, 2011 at 14:56, James Burke <jr...@gmail.com> wrote:
> On Tue, Dec 13, 2011 at 11:25 AM, Brian LeRoux <b...@brian.io> wrote:
> > Looks rad. I'm curious what an AMD plugin vs the same plugin authored
> > in CJS style would look like. Just a hello world.
>
> Usually the only difference for coding a callback plugin in AMD or CJS
> would be a define wrapper in the AMD case:
>
> define(function(require, exports, module){
> //CJS stuff goes in here.
> });
>
This is using the anonymous module pattern, instead of naming the module by
passing an id as the first parameter to define(). Unfortunately, it seems
difficult to convert this pattern into almond usage, as almond requires
that you have an id as the first parameter (as near as I can tell, for a
normal module). To use an anonymous module with almond, you have to
rewrite the module to add the id at build time (ick!) or you could resort
to horrible, hideous hacks (you don't want to know!).
A named, wrapped CJS module as AMD would look like:
define(moduleId, function(require, exports, module){
//CJS stuff goes in here.
})
I'm not sure what breaks when you DO use an id; is that an issue with
dynamic loading?
--
Patrick Mueller
http://muellerware.org
Re: CommonJS and AMD (and, not or)
Posted by Filip Maj <fi...@adobe.com>.
I think dynamic module loading is a use case that must be covered once we
switch to a modularized JS... Might as well eh?
On 11-12-13 11:56 AM, "James Burke" <jr...@gmail.com> wrote:
>On Tue, Dec 13, 2011 at 11:25 AM, Brian LeRoux <b...@brian.io> wrote:
>> Looks rad. I'm curious what an AMD plugin vs the same plugin authored
>> in CJS style would look like. Just a hello world.
>
>Usually the only difference for coding a callback plugin in AMD or CJS
>would be a define wrapper in the AMD case:
>
>define(function(require, exports, module){
> //CJS stuff goes in here.
> var PG = require('phonegap');
>
> //to export a module value
> module.exports = {};
>});
>
>Most of the time, exports and module is not needed when using AMD, so
>you can just do:
>
>define(function(require){
> var PG = require('phonegap');
>
> //to set the module exports, return a value.
> //can be a function.
> return {};
>});
>
>AMD's define allows for imperative require use with a callback, so:
>
>define(function(require){
> var mod = someCondition ? 'a' : 'b';
>
> require([mod], function (modExports) {
> //called back asynchronously.
> });
>});
>
>This is really helpful for something like google maps code, where you
>may not want to load the code right away, but in response to a user
>action. This is difficult to express in CommonJS code, since require
>is always synchronous in that environment.
>
>Note though, that to get the dynamic require support for loading
>network resources a dynamic require loader, like RequireJS, curl,
>dojo, would be needed instead of almond. However, if the module was
>built into the built file containing almond, the above callback-style
>require will work with almond.
>
>James
Re: CommonJS and AMD (and, not or)
Posted by James Burke <jr...@gmail.com>.
On Tue, Dec 13, 2011 at 11:25 AM, Brian LeRoux <b...@brian.io> wrote:
> Looks rad. I'm curious what an AMD plugin vs the same plugin authored
> in CJS style would look like. Just a hello world.
Usually the only difference for coding a callback plugin in AMD or CJS
would be a define wrapper in the AMD case:
define(function(require, exports, module){
//CJS stuff goes in here.
var PG = require('phonegap');
//to export a module value
module.exports = {};
});
Most of the time, exports and module is not needed when using AMD, so
you can just do:
define(function(require){
var PG = require('phonegap');
//to set the module exports, return a value.
//can be a function.
return {};
});
AMD's define allows for imperative require use with a callback, so:
define(function(require){
var mod = someCondition ? 'a' : 'b';
require([mod], function (modExports) {
//called back asynchronously.
});
});
This is really helpful for something like google maps code, where you
may not want to load the code right away, but in response to a user
action. This is difficult to express in CommonJS code, since require
is always synchronous in that environment.
Note though, that to get the dynamic require support for loading
network resources a dynamic require loader, like RequireJS, curl,
dojo, would be needed instead of almond. However, if the module was
built into the built file containing almond, the above callback-style
require will work with almond.
James
Re: CommonJS and AMD (and, not or)
Posted by Brian LeRoux <b...@brian.io>.
Looks rad. I'm curious what an AMD plugin vs the same plugin authored
in CJS style would look like. Just a hello world.
On Tue, Dec 13, 2011 at 11:09 AM, Gord Tanner <go...@tinyhippos.com> wrote:
> So I ported callback-js to use almond but it is still using the custom
> builder code.
>
> https://github.com/gtanner/callback-js/tree/almond
>
> It was very nice that I could switch this out with no update to any of the
> modules.
>
> Patrick: Would be cool if you could update this to use r.js if it is
> possible.
>
> With my quick experience with almond I have to say I really like what I am
> seeing and I think it will help us out a lot and give the ability to have
> plugin's authored in both commonJS and AMD module definitions. (unless I am
> misunderstanding something).
>
> Gord
>
> On Fri, Dec 9, 2011 at 12:18 PM, Patrick Mueller <pm...@gmail.com> wrote:
>
>> On Fri, Dec 9, 2011 at 11:20, Gord Tanner <go...@tinyhippos.com> wrote:
>>
>> > I am switching out browser-require for require.js in a branch today to
>> see
>> > what this would feel like for a developer.
>> >
>>
>> > I have no preference as to which module system we use so I am going to
>> give
>> > this a go today to learn AMD modules / requireJS and get a feel for how
>> it
>> > would look in phonegap.js development.
>> >
>>
>> I assume you are going to use the big requirejs, which includes dynamically
>> loading modules async, etc. Since I've been playing with almond, I can
>> take whatever you do, and see how to fit almond into the picture for a
>> single cat'd file. Just point me to your repo ...
>>
>> --
>> Patrick Mueller
>> http://muellerware.org
>>
>
>
>
> --
> Gord Tanner
> Senior Developer / Code Poet
> tinyHippos Inc.
> @tinyhippos
Re: CommonJS and AMD (and, not or)
Posted by Patrick Mueller <pm...@gmail.com>.
On Tue, Dec 13, 2011 at 14:09, Gord Tanner <go...@tinyhippos.com> wrote:
> So I ported callback-js to use almond but it is still using the custom
> builder code.
>
> https://github.com/gtanner/callback-js/tree/almond
>
> It was very nice that I could switch this out with no update to any of the
> modules.
>
Excellent.
> Patrick: Would be cool if you could update this to use r.js if it is
> possible.
>
I'm not familiar with r.js; I believe I offered to port a requirejs-ish
flavored piece of work to almond. Almond is the only AMD impl I'm familiar
with, and only one I want to be familiar with :-) Someone interested in
dynamically loading AMD modules should probably take a look.
> With my quick experience with almond I have to say I really like what I am
> seeing and I think it will help us out a lot and give the ability to have
> plugin's authored in both commonJS and AMD module definitions. (unless I am
> misunderstanding something).
>
I concur
--
Patrick Mueller
http://muellerware.org
Re: CommonJS and AMD (and, not or)
Posted by Gord Tanner <go...@tinyhippos.com>.
So I ported callback-js to use almond but it is still using the custom
builder code.
https://github.com/gtanner/callback-js/tree/almond
It was very nice that I could switch this out with no update to any of the
modules.
Patrick: Would be cool if you could update this to use r.js if it is
possible.
With my quick experience with almond I have to say I really like what I am
seeing and I think it will help us out a lot and give the ability to have
plugin's authored in both commonJS and AMD module definitions. (unless I am
misunderstanding something).
Gord
On Fri, Dec 9, 2011 at 12:18 PM, Patrick Mueller <pm...@gmail.com> wrote:
> On Fri, Dec 9, 2011 at 11:20, Gord Tanner <go...@tinyhippos.com> wrote:
>
> > I am switching out browser-require for require.js in a branch today to
> see
> > what this would feel like for a developer.
> >
>
> > I have no preference as to which module system we use so I am going to
> give
> > this a go today to learn AMD modules / requireJS and get a feel for how
> it
> > would look in phonegap.js development.
> >
>
> I assume you are going to use the big requirejs, which includes dynamically
> loading modules async, etc. Since I've been playing with almond, I can
> take whatever you do, and see how to fit almond into the picture for a
> single cat'd file. Just point me to your repo ...
>
> --
> Patrick Mueller
> http://muellerware.org
>
--
Gord Tanner
Senior Developer / Code Poet
tinyHippos Inc.
@tinyhippos
Re: CommonJS and AMD (and, not or)
Posted by Patrick Mueller <pm...@gmail.com>.
On Fri, Dec 9, 2011 at 11:20, Gord Tanner <go...@tinyhippos.com> wrote:
> I am switching out browser-require for require.js in a branch today to see
> what this would feel like for a developer.
>
> I have no preference as to which module system we use so I am going to give
> this a go today to learn AMD modules / requireJS and get a feel for how it
> would look in phonegap.js development.
>
I assume you are going to use the big requirejs, which includes dynamically
loading modules async, etc. Since I've been playing with almond, I can
take whatever you do, and see how to fit almond into the picture for a
single cat'd file. Just point me to your repo ...
--
Patrick Mueller
http://muellerware.org
Re: CommonJS and AMD (and, not or)
Posted by Gord Tanner <go...@tinyhippos.com>.
I am switching out browser-require for require.js in a branch today to see
what this would feel like for a developer.
I have no preference as to which module system we use so I am going to give
this a go today to learn AMD modules / requireJS and get a feel for how it
would look in phonegap.js development.
Gord
On Fri, Dec 9, 2011 at 7:31 AM, Patrick Mueller <pm...@gmail.com> wrote:
> On Thu, Dec 8, 2011 at 20:55, James Burke <jr...@gmail.com> wrote:
>
> > That doc mentions how require.js can be used in dev, to get individual
> > files loaded for debugging, but then have it build down to just one
> > file that uses the small almond implementation of AMD.
> >
>
> At this point I'm fine with using AMD as our module implementation, and
> something the size of almond as our deployment runtime, for folks who want
> to do a "build". I'm not sure what we do about an AMD runtime that needs
> to load modules dynamically though. Do we ship one of these with Cordova?
> Which one? Is it replaceable? If we ship RequireJS, can I replace it
> with curl or pinf?
>
> Also not clear regarding not-quite-standardized stuff in AMD, like configs
> and plugins. Do all the AMD runtimes support these? Where is it spec'd?
>
> As for file conversions, for plain JS and AMD, an almond-type loader
> > is fine, and for dev, requirejs can handle both of those. For CommonJS
> > or node modules, the guidance could just be "make sure you convert to
> > AMD before you bring it into your phonegap project, here is a utility
> > to do that".
>
>
> Keep in mind that at least one person in the world (me) prefers to develop
> with the same runtime and semantics that I'll be using at deployment time,
> meaning I'm going to develop with almond.
>
> This implies I'm going to need to do a build. I needed to do one anyway,
> I'm using LESS, CoffeeScript, etc. Applying AMD wrappers over CommonJS
> code just adds a few more milliseconds to the build. This is essentially
> how the build for weinre works, as I noted previously in this thread.
>
> So, there's actually a workflow which supports authoring your modules in
> CommonJS. Your guidance of "make sure you convert to AMD before you bring
> it into your phonegap project" seems to imply that you can't. Not only can
> you use CommonJS modules, you can author your own CommonJS modules in your
> project, if you're willing to live with the constraint of a build. Of
> course, you are free to author your module in AMD format as well.
>
> --
> Patrick Mueller
> http://muellerware.org
>
--
Gord Tanner
Senior Developer / Code Poet
tinyHippos Inc.
@tinyhippos
Re: CommonJS and AMD (and, not or)
Posted by James Burke <jr...@gmail.com>.
On Fri, Dec 9, 2011 at 4:31 AM, Patrick Mueller <pm...@gmail.com> wrote:
> At this point I'm fine with using AMD as our module implementation, and
> something the size of almond as our deployment runtime, for folks who want
> to do a "build". I'm not sure what we do about an AMD runtime that needs
> to load modules dynamically though. Do we ship one of these with Cordova?
> Which one? Is it replaceable? If we ship RequireJS, can I replace it
> with curl or pinf?
I believe where most AMD loaders differ is some "advanced"
configuration settings, and they each might have a different global
name to enter the loader. From what I have heard, switching from
requirejs to curl is switching out the script name, calling curl()
instead of require() at the global level (or just alias curl). As long
as the project is using the basic config of baseUrl and paths (enough
for most projects) it should be doable.
So, some adjustments, but they should be "top-level" adjustments and
not module reauthoring.
One case that might require touching your module code is the loading
of plain script files. curl uses a loader plugin for that while
requirejs does not, unless you need a few plain scripts loaded in
order.
> Also not clear regarding not-quite-standardized stuff in AMD, like configs
> and plugins. Do all the AMD runtimes support these? Where is it spec'd?
The config values are not officially specified, although we could
probably start with baseUrl and paths which do seem generally
supported.
AMD's define, its use of require, and the loader API are all specified here:
https://github.com/amdjs/amdjs-api/wiki
There is a set of compatibility tests too. The loader API's runtime
API is well specified, but we are still discussing the build-time API.
So there is some variability involved, but mostly on the
tooling/top-level loader level, but not for individual module
authoring.
> So, there's actually a workflow which supports authoring your modules in
> CommonJS. Your guidance of "make sure you convert to AMD before you bring
> it into your phonegap project" seems to imply that you can't. Not only can
> you use CommonJS modules, you can author your own CommonJS modules in your
> project, if you're willing to live with the constraint of a build. Of
> course, you are free to author your module in AMD format as well.
Sorry, it was bad wording on my part. It could be rephrased as "make
sure your module code is a plain browser script or transformed to AMD
before running it". Whether the transform is done via on-the-fly
builds or part of "install time" either should be supported.
James
Re: CommonJS and AMD (and, not or)
Posted by Patrick Mueller <pm...@gmail.com>.
On Thu, Dec 8, 2011 at 20:55, James Burke <jr...@gmail.com> wrote:
> That doc mentions how require.js can be used in dev, to get individual
> files loaded for debugging, but then have it build down to just one
> file that uses the small almond implementation of AMD.
>
At this point I'm fine with using AMD as our module implementation, and
something the size of almond as our deployment runtime, for folks who want
to do a "build". I'm not sure what we do about an AMD runtime that needs
to load modules dynamically though. Do we ship one of these with Cordova?
Which one? Is it replaceable? If we ship RequireJS, can I replace it
with curl or pinf?
Also not clear regarding not-quite-standardized stuff in AMD, like configs
and plugins. Do all the AMD runtimes support these? Where is it spec'd?
As for file conversions, for plain JS and AMD, an almond-type loader
> is fine, and for dev, requirejs can handle both of those. For CommonJS
> or node modules, the guidance could just be "make sure you convert to
> AMD before you bring it into your phonegap project, here is a utility
> to do that".
Keep in mind that at least one person in the world (me) prefers to develop
with the same runtime and semantics that I'll be using at deployment time,
meaning I'm going to develop with almond.
This implies I'm going to need to do a build. I needed to do one anyway,
I'm using LESS, CoffeeScript, etc. Applying AMD wrappers over CommonJS
code just adds a few more milliseconds to the build. This is essentially
how the build for weinre works, as I noted previously in this thread.
So, there's actually a workflow which supports authoring your modules in
CommonJS. Your guidance of "make sure you convert to AMD before you bring
it into your phonegap project" seems to imply that you can't. Not only can
you use CommonJS modules, you can author your own CommonJS modules in your
project, if you're willing to live with the constraint of a build. Of
course, you are free to author your module in AMD format as well.
--
Patrick Mueller
http://muellerware.org
Re: CommonJS and AMD (and, not or)
Posted by James Burke <jr...@gmail.com>.
On Thu, Dec 8, 2011 at 9:33 AM, Patrick Mueller <pm...@gmail.com> wrote:
> I have no answers, only questions. But I do have a preference of
> convention over configuration, and sniffer utilities are too fragile for me.
I just joined this list a little while ago (maybe 1-2 weeks). I'm the
main developer for RequireJS, an AMD loader.
I missed the other modular discussions on this list, but I did write
up some thoughts after I was asked what it might look like to use AMD
either as part of phonegap core or as part of an end developer's
project:
https://github.com/jrburke/sketches/blob/master/docs/phonegap.md
That doc mentions how require.js can be used in dev, to get individual
files loaded for debugging, but then have it build down to just one
file that uses the small almond implementation of AMD.
As for file conversions, for plain JS and AMD, an almond-type loader
is fine, and for dev, requirejs can handle both of those. For CommonJS
or node modules, the guidance could just be "make sure you convert to
AMD before you bring it into your phonegap project, here is a utility
to do that".
If the developer just wants to go old school plain script tags, that
should be allowed, but not to expect to use any functionality from a
cjs/node module set.
I think AMD is a great fit for phonegap apps, even if for phonegap
core it is some commonjs variant that is built to a set of optional
AMD modules. I'm just happy to answer any other questions about using
AMD in a phonegap app -- I use it now in an experimental app that
targets iOS and Android, although the iOS work is mostly a stub at the
moment:
https://github.com/jrburke/submarine
the "main" js file for the page shows some AMD use, in particular some
loader plugins that help avoid what would otherwise be some nested
async callbacks:
https://github.com/jrburke/submarine/blob/master/phonegap/www/js/index.js
The other nice thing is the 'env' loader plugin that loads the iOS or
Android phonegap.js and the appropriate plugin JS for that platform.
It also has an env setup for desktop browser, so I can try some of the
interface without having to use a device. The end result -- I just
have one www directory that I use for Android/iOS and desktop browser
testing.
James
Re: CommonJS and AMD (and, not or)
Posted by Patrick Mueller <pm...@gmail.com>.
A few more thoughts, perhaps jumping the gun a bit.
Seems possible we may want to support a variety of "JS module" types,
including:
* standalone js files that you just <script src=> into your .html (not
really "modules")
* AMD-authored .js files
* CommonJS-authored .js files
* npm-styled packages of .js code
See the problem. Many types of files, all ending in .js. If we want to
have automated processes work against this stuff, for builds, we're going
to need one of:
* project-specific build scripts with hardcoded directory / file names
* utilities to sniff .js files and tell us what kind they are, so tooling
can operate against them correctly
* configuration files that tell us what kind of files live where
* convention that tells us what kind of files live where
Some of this is kinda easy. It's easy to recognize npm-styled packages,
because they live in directories named "node_modules".
But how do I tell an AMD authored .js file from a CommonJS authored .js
file (assuming the CommonJS file doesn't live in an npm package)? How do I
tell either of those apart from a plain old .js file I just want to include
in my .html file?
I have no answers, only questions. But I do have a preference of
convention over configuration, and sniffer utilities are too fragile for me.
--
Patrick Mueller
http://muellerware.org