Module spec questions

29 views
Skip to first unread message

James Burke

unread,
Nov 16, 2009, 2:30:34 PM11/16/09
to comm...@googlegroups.com
I want to provide a way to convert existing CommonJS modules as part
of my proposal for an alternate syntax for modules that works well in
the browser and can be hand-authored by devs:
http://wiki.commonjs.org/wiki/AlternateModuleFormatRun

As part of looking at some of the modules, I do not understand some of
the idioms I have seen in some code. I have looked a bit in the
archives, but some of the names involved below are fairly generic and
some of the past discussions are hard to parse out the final needs of
some things. I apologize if some of this is a rehash for you. If it is
helpful, I am happy to update the wiki with any of the answers here,
perhaps as "why" or "motivation" links next to the appropriate places
in any specs.

These questions relate to sections on the Modules 1.1 page:
http://wiki.commonjs.org/wiki/Modules/1.1

Some of the things listed here are listed as "may" items on that page,
so I can appreciate that they are not required by a conforming
implementation, but when I look at trying to convert module syntax,
since they are possible I seem to need to account for them. Most of
the questions for me come down to one basic theme: leaking too much
information about module environment into modules.

1) Module Context, #1,5: The "require" function may have a "main" property:
Why is a main property desirable? At first glance, it seems odd that
modules would want to inspect the ID of the module that is considered
the "main". Knowledge of the entry point into the system seems outside
the scope of a module definition.

2) Module Context, #3: In a module, there must be a free variable "module":
Why is this useful? The one place I have seen it used is to do an
if(require.main == module.id) check to then run the equivalent of a
"main" function in the module. However, it seems odd to me that this
is preferred over having a convention that a module exports a main()
function that is called by the system. In short, requiring the module
to bootstrap itself into the main entry point seems like leaking more
environment into the module. Also seems like the module would not have
enough information to execute a "main" function -- the type of "main"
seems to be dependent on the environment (command line args vs. a web
request/response system). Seems like each environment would have their
own conventions on a "main".

3) Not part of Modules 1.1, but I have seen require and require.async
mentioned. This seems to be another instance of how the loading of a
module is done (sync vs async) that seems like it would not be a
primary concern of a module that is specifying dependencies.

James

Kris Kowal

unread,
Nov 17, 2009, 2:11:11 AM11/17/09
to comm...@googlegroups.com
On Mon, Nov 16, 2009 at 11:30 AM, James Burke <jrb...@gmail.com> wrote:
> 1) Module Context, #1,5: The "require" function may have a "main" property:
> Why is a main property desirable? At first glance, it seems odd that
> modules would want to inspect the ID of the module that is considered
> the "main". Knowledge of the entry point into the system seems outside
> the scope of a module definition.

Python and other languages have an idiom that looks like:

if __name__ == '__main__':
main()

We support the idiom:

if (require.main == module) {
main();
}

This gracefully fails if "main" is not defined.

> 2) Module Context, #3: In a module, there must be a free variable "module":
> Why is this useful? The one place I have seen it used is to do an
> if(require.main == module.id) check to then run the equivalent of a
> "main" function in the module. However, it seems odd to me that this
> is preferred over having a convention that a module exports a main()
> function that is called by the system. In short, requiring the module
> to bootstrap itself into the main entry point seems like leaking more
> environment into the module. Also seems like the module would not have
> enough information to execute a "main" function -- the type of "main"
> seems to be dependent on the environment (command line args vs. a web
> request/response system). Seems like each environment would have their
> own conventions on a "main".

We have a "system" module specification that hosts "args", "env",
"stdin", "stdout", and "stderr".

I presume you're proposing that instead we establish a convention that
"exports.main(env)" is a reserved name to be exported by modules that
are used as an entry-point for a program. That's pretty clean,
although it does force our will on everyone's module exports API.

Anyone have a strong opinion on this?

> 3) Not part of Modules 1.1, but I have seen require and require.async
> mentioned. This seems to be another instance of how the loading of a
> module is done (sync vs async) that seems like it would not be a
> primary concern of a module that is specifying dependencies.

Synchronous and asynchronous module loading require different API's.
We definitely need to lock down a specification for require.async, but
we need to ratify "Promise" first.

require.async is distinct from require because it can import run-time
computed dependencies. For example:

exports.action = function (name) {
return require.async('./action' + name).when(function (action) {
return new action.Actoin();
});
};

Since this operation might lead to a long-waiting network transfer, it
cannot block, which is why we go the promise route. With a
string-literal require, we have an opportunity to guarantee that we
don't execute the module until its transitive dependencies are loaded.

Kris Kowal

James Burke

unread,
Nov 17, 2009, 5:27:00 PM11/17/09
to comm...@googlegroups.com
On Mon, Nov 16, 2009 at 11:11 PM, Kris Kowal <cowber...@gmail.com> wrote:
> I presume you're proposing that instead we establish a convention that
> "exports.main(env)" is a reserved name to be exported by modules that
> are used as an entry-point for a program.  That's pretty clean,
> although it does force our will on everyone's module exports API.
>
> Anyone have a strong opinion on this?

Correct. It makes the contract clear -- I can see different systems
having different definitions on what "main" means. It seems best to
allow the negotiation of the entry point to be done by the system and
not by the module. The module can advertise via its methods what kind
of entry point it can handle, but the choice of when to call it and
how is left up to the system environment. Modules can be kept as more
declarative entities.

It also seems like for main() to do anything useful it will likely
require the import of a specific CommonJS module like "env", so there
may already be an implied contract with the existing require.main ==
module.id idiom.

With defined entry points, then it seems to remove the need for having
require.main and the "module" reserved variable name. Less
requirements on the module syntax/loader seem like a net win.

>
>> 3) Not part of Modules 1.1, but I have seen require and require.async
>> mentioned. This seems to be another instance of how the loading of a
>> module is done (sync vs async) that seems like it would not be a
>> primary concern of a module that is specifying dependencies.
>
> Synchronous and asynchronous module loading require different API's.
> We definitely need to lock down a specification for require.async, but
> we need to ratify "Promise" first.
>
> require.async is distinct from require because it can import run-time
> computed dependencies.  For example:
>
> exports.action = function (name) {
>    return require.async('./action' + name).when(function (action) {
>        return new action.Actoin();
>    });
> };
>
> Since this operation might lead to a long-waiting network transfer, it
> cannot block, which is why we go the promise route.  With a
> string-literal require, we have an opportunity to guarantee that we
> don't execute the module until its transitive dependencies are loaded.

I believe something like the function callback syntax that is used by
runjs removes the need for the distinction of sync vs async and then
also make is possible to get by without needing to explicitly expose
promises or promise objects as part of the module definition/loading
API. So it minimizes what is needed to build a CommonJS module loader.
Here is how I would do the example above. It is a bit different than
your example above. In this example you pass the context or callback
that needs the action, or depends on the action executing. ("run" is
just a placeholder name for the module loader function):

run("foo",
function () {
//define foo
return {
action: function (name, contextOrCallback) {
run(['./action' + name],
function(actionCtor) {
//This function runs after './action' + name
is loaded, sync or async
var action = new actionCtor();
//Either do this:
contextOrCallback(action);
//or this:
action.execute(contextOrCallback);
}
};
}
};
}
);

It seems fine to say a promise might be passed in as a context or
callback, but that is something that is specified in the API for the
modules, it is outside the scope of the module definition and module
loading.

In summary, I am looking at minimizing the requirements for a module
syntax and loader.

James

Eugene Lazutkin

unread,
Nov 18, 2009, 1:55:39 PM11/18/09
to CommonJS
+1. All points sound reasonable.

On Nov 17, 4:27 pm, James Burke <jrbu...@gmail.com> wrote:
Reply all
Reply to author
Forward
0 new messages