Modules/2.0 questions

40 views
Skip to first unread message

Domenic

unread,
Jul 19, 2011, 10:35:59 AM7/19/11
to CommonJS
Myself and a colleague are putting the finishing touches on a Modules/
2.0 implementation, based on the draft-8 spec. It started as part of
our work at Barnes & Noble.com, where our team needed a robust
JavaScript modules
solution, but we’re hoping---once we can get the polish all there---to
open source it.

However, we've run into a few questions while deciphering the spec
that we want to get clarified before calling ourselves "done." We
tried emailing Wes about this directly, but with no response from him,
we hope that maybe this community could be more helpful. (Although,
looking at the archives, has the community abandoned Modules/2.0 in
favor of AMD? It's not entirely clear.)

Let's call the module and declare variables in the extra-module
environment "global.module" and "global.require," while the
unqualified versions refer to the appropriate parameters to the module
factory function. Then our questions are:

1. Can you call module.declare, or only global.module.declare? If the
former, how is that supposed to work; if the latter, how should this
be prevented?

2. What is global.module.dependencies? global.module.id is specified,
to be undefined, but not dependencies.

3. If you call global.module.provide(["a"], function () { /* ... */ })
in the EME, global.module.declare will be called for the first time
ever, declaring the "a" module. Furthermore, if inside the provide
callback, you call global.require("a"), the factory function for "a"
will be necessarily run. How does this fit with the requirement
(2.3.1.1) that the main module's factory function is invoked before
any other module's? Does "a" become the main module? If so, what
happens when global.module.declare is called in EME? Should
global.module.provide even exist?

4. Should module.provide accept a dependency array in the sense of
section 3.5, i.e. one which can contain labels? The sentence "The
dependency list format is described in §3.5" seems to indicate so.

5. If 5, should calling module.provide "update the knowledge" of its
counterpart require to reflect these new labels? (Otherwise they would
be useless.) Should module.dependencies be updated to reflect this
knowledge, or does it only reflect the original dependencies array
passed to global.module.declare? Similarly, do labels introduced by
global.module.provide update the knowledge of global.require?

Thanks all!

Christoph Dorn

unread,
Jul 19, 2011, 2:22:38 PM7/19/11
to comm...@googlegroups.com
On 11-07-19 7:35 AM, Domenic wrote:
> However, we've run into a few questions while deciphering the spec
> that we want to get clarified before calling ourselves "done." We
> tried emailing Wes about this directly, but with no response from him,
> we hope that maybe this community could be more helpful. (Although,
> looking at the archives, has the community abandoned Modules/2.0 in
> favor of AMD? It's not entirely clear.)

Modules/2.0 has *not* been abandoned in favor of AMD as AMD is only a
transport format for Modules/2.0. Not everyone is on board with that
(especially people writing mostly for the browser) but it has been
decided that AMD is not a compatible improvement over Modules/1.0 and we
are waiting for implementations and reviewers of Modules/2.0.

It has been a while since I looked at the details of the spec. I'll try
and answer as best as I can. You could also verify the intended behavior
by taking a look at the two loaders that implement Modules/2.0:

* http://code.google.com/p/bravojs/
* https://github.com/pinf/loader-js

There are a bunch of tests with these projects. I hope to get these
tests onto https://github.com/kriskowal/uncommonjs at some point.


> Let's call the module and declare variables in the extra-module
> environment "global.module" and "global.require," while the
> unqualified versions refer to the appropriate parameters to the module
> factory function. Then our questions are:
>
> 1. Can you call module.declare, or only global.module.declare? If the
> former, how is that supposed to work; if the latter, how should this
> be prevented?

`global.module.declare` is used in browser environments only so modules
loaded via script tags can declare themselves.

`module.declare` is used in server environments where the loader
provides a new `module` variable to each module being loaded to allow
the module to declare itself asynchronously.

Server environments should not have a `global.module.declare` unless you
have a bootstrap mode that executes some code outside of a module
context initially.

Both `module.declare` and `global.module.declare` may only be caled once
per module ID.


> 2. What is global.module.dependencies? global.module.id is specified,
> to be undefined, but not dependencies.

Should be undefined as well I believe. Only `global.module.declare`
makes sense.


> 3. If you call global.module.provide(["a"], function () { /* ... */ })
> in the EME, global.module.declare will be called for the first time
> ever, declaring the "a" module. Furthermore, if inside the provide
> callback, you call global.require("a"), the factory function for "a"
> will be necessarily run. How does this fit with the requirement
> (2.3.1.1) that the main module's factory function is invoked before
> any other module's? Does "a" become the main module? If so, what
> happens when global.module.declare is called in EME? Should
> global.module.provide even exist?

Not sure. I only deal with the EME during bootstrapping and don't recall
all the logic behind it. The BravoJS loader is probably the best place
to look.


> 4. Should module.provide accept a dependency array in the sense of
> section 3.5, i.e. one which can contain labels? The sentence "The

> dependency list format is described in �3.5" seems to indicate so.

Labels are not supported at that level.


> 5. If 5, should calling module.provide "update the knowledge" of its
> counterpart require to reflect these new labels? (Otherwise they would
> be useless.) Should module.dependencies be updated to reflect this
> knowledge, or does it only reflect the original dependencies array
> passed to global.module.declare? Similarly, do labels introduced by
> global.module.provide update the knowledge of global.require?

The signature is not:

module.provide ([], factory)

but:

module.provide ([], callback)

Meaning that `callback` is a callback with no arguments and not a
factory function. `module.provide` is simply a means to wait for the
dependencies to be loaded. It is not a way to declare a module.

Hope this helps.

Christoph

Wes Garland

unread,
Jul 19, 2011, 3:43:44 PM7/19/11
to comm...@googlegroups.com
On 19 July 2011 10:35, Domenic <dom...@domenicdenicola.com> wrote:
However, we've run into a few questions while deciphering the spec
that we want to get clarified before calling ourselves "done." We
tried emailing Wes about this directly, but with no response from him,

You must have gotten caught in my spam filter, sorry about that.

BTW -- have you taken a look at BravoJS?  It's close to a full implementation of -2.0 draft 7, and running tests against it may help to clarify some quesitons.
 
we hope that maybe this community could be more helpful. (Although,
looking at the archives, has the community abandoned Modules/2.0 in
favor of AMD? It's not entirely clear.)

There has been no community clarification at this point, other than that the AMD module author appears to want to not concern himself with CommonJS compatibility, at last for the time being.
 
Let's call the module and declare variables in the extra-module
environment "global.module" and "global.require," while the
unqualified versions refer to the appropriate parameters to the module
factory function. Then our questions are:

I'll try to answer these, although my head space is has been nowhere near this for a couple of months, so forgive me if I start spouting insanity. ;)
 
1. Can you call module.declare, or only global.module.declare? If the
former, how is that supposed to work; if the latter, how should this
be prevented?

IIRC module.declare should be the same function in both the extra-module environment and within a module.   The normal call pattern is to call module.declare from the extra-module environment (by the underlying module system, not the developer). I don't believe there is an explicit prohibition on calling module.declare from a module, but doing so will not be useful either.
 
2. What is global.module.dependencies? global.module.id is specified,
to be undefined, but not dependencies.

Good question!  The spec doesn't seem to say.  I'd go with an empty array, but undefined seems just as reasonable.

3. If you call global.module.provide(["a"], function () { /* ... */ })
in the EME, global.module.declare will be  called for the first time
ever, declaring the "a" module. Furthermore, if inside the provide
callback, you call global.require("a"), the factory function for "a"
will be necessarily run. How does this fit with the requirement
(2.3.1.1) that the main module's factory function is invoked before
any other module's? Does "a" become the main module? If so, what
happens when global.module.declare is called in EME? Should
global.module.provide even exist?

I think this is an interesting spec-hole.  Global module.provide is necessary in certain circumstances, primilarily IIRC in cases where we have applications which are not-quite-CommonJS trying to bootstrap themselves.  In a spec-ideal CommonJS world, there is no code running in the extra module environment, but we all know that's not always achievable in the real world.

To answer your question, the I think the code should be allowed to run and the main module must never be changed.  The main module will still be executed by the environment, but maybe it won't be the first one to run. *hmm*

Any chance you can put together a trivial example for me to mull over?

4. Should module.provide accept a dependency array in the sense of
section 3.5, i.e. one which can contain labels? The sentence "The
dependency list format is described in §3.5" seems to indicate so.

Yes
 
5. If 5, should calling module.provide "update the knowledge" of its
counterpart require to reflect these new labels? (Otherwise they would
be useless.) Should module.dependencies be updated to reflect this
knowledge, or does it only reflect the original dependencies array
passed to global.module.declare? Similarly, do labels introduced by
global.module.provide update the knowledge of global.require?


They're not useless: accepting the same format means you can re-use the same arrays without error.  The meaning of require(X) never changes within a given module after it has been initialized.  Labeled dependencies only affect the current model, not their children.  (A conscious decision to maintain determism as more important than flexibility was made during this design)

Wes
 
--
Wesley W. Garland
Director, Product Development
PageMail, Inc.
+1 613 542 2787 x 102

Christoph Dorn

unread,
Jul 19, 2011, 5:19:34 PM7/19/11
to comm...@googlegroups.com
On 11-07-19 12:43 PM, Wes Garland wrote:
> On 19 July 2011 10:35, Domenic <dom...@domenicdenicola.com
> <mailto:dom...@domenicdenicola.com>> wrote:
>
> 4. Should module.provide accept a dependency array in the sense of
> section 3.5, i.e. one which can contain labels? The sentence "The
> dependency list format is described in �3.5" seems to indicate so.
>
> Yes

That is not consistent with the BravoJS implementation I believe.

Looking at the code it seems to be simply a way to wait for a bunch of
modules to be loaded before the callback is triggered.

See:

* http://code.google.com/p/bravojs/source/browse/bravo.js#518
* http://code.google.com/p/bravojs/source/browse/bravo.js#222

I think it makes sense the way it is implemented. If you want labelled
dependencies use `module.declare`.

Christoph

Christoph Dorn

unread,
Jul 19, 2011, 5:22:32 PM7/19/11
to comm...@googlegroups.com
Let me clarify. It *accepts* a dependency array with labels but does not
*use* them.

I think that should make it into the wording of the spec.

Christoph

--
http://www.ChristophDorn.com/Tools/

Domenic

unread,
Jul 20, 2011, 12:14:28 PM7/20/11
to CommonJS
Thanks very much for the responses! The level of participation is
heartening :)

It seems like my questions about labels have been fully answered, and
although it is missing from the spec, an empty array as
global.module.dependencies makes perfect sense. This leaves the issues
of module.declare inside a module factory, and module.provide in the
EME.

I would argue that module.declare should not be callable (e.g. be
undefined) inside factory functions. This would not impact plugin
writing, as long as the prototype was unchanged; that is,
module.prototype.declare would do something useful, and
global.module.declare would delegate to it, but before passing a
"module" parameter to the factory function, the system would set
declare to undefined, or to some stub function that immediately throws
an error.

A minimal example of the spec hole in 3 works pretty much as you would
expect:

//--- math.js
module.declare([], function () { })

//--- <script> tag
module.provide(["math"], function () {
require("math");
})

// supposed to be the main module
setTimeout(function () {
module.declare([], function () { })
}, 1000);

Here the setTimeout is meant to delay the main module declaration
until after math.js gets loaded, but would be unnecessary if e.g.
module.load was implemented synchronously.

Making the main module not necessarily the first to run seems sensible
to me, although that makes the existence of module.main no longer a
guaranteed feature. But of course if you're doing stuff like this you
probably need to be careful anyway and whatever happens is upon your
own head.

In light of your point about the purpose of global.module.provide, I
wonder whether it could be another "optionally implemented" part of
the spec, like require.paths, module.uri, etc.

Finally, the combination of these points leads me to question why
global.module and module are the same object at all. As far as I can
tell, the only useful part of global.module, for most users, is
declare. For plug-in implementers, eventually, load, and constructor
are important, and for non-spec-ideal worlds, provide is important,
but it seems very awkward to lump these three responsibilities into
the same object. Similarly, the core functionality of global.module,
viz. declare, doesn't even make sense for inside-a-factory-function
module, and neither do any of the plugin-writing methods; we are left
with id, dependencies, main, and provide. This might just be my SOLID
zealousness coming into play, but I'm really not a big fan of giving
users a god object with an unwritten contract that only subsets of
that object make sense in any given context. I'd much rather make that
contract explicit, since we have the tools to do so. What do you
think?

P.S. we have indeed looked at BravoJS, but it failed a lot of the
tests we wrote when TDD-ed our own system. We'd be happy to share and
discuss these tests, but this thread is probably not the best place to
do it, and besides we should really get some code out there for others
to critique before making critiques ourselves.
Reply all
Reply to author
Forward
0 new messages