CommonJS in the browser (was Re: [CommonJS] Re: require.setExports())

218 views
Skip to first unread message

James Burke

unread,
Dec 22, 2009, 2:51:39 PM12/22/09
to comm...@googlegroups.com
Changed the subject since this is a bit of a different topic from
setExports. More inline below...

On Tue, Dec 22, 2009 at 7:22 AM, Wes Garland <w...@page.ca> wrote:
> Actually, it doesn't mean two flavours of CommonJS modules, nor affect the
> text of the module at all...It affects the module transport system and/or
> loader. *ALL* CommonJS implementations have to have either a function
> wrapper or something equivalent in order to load modules with a module-wide
> variable scope.. a function wrapper (closure) is, in fact, the only
> JavaScript construct which can allow this. Incidentally, while GPSEE uses
> engine-specific tricks for this, I know for a fact that Flusspferd is
> implemented with scripted closure wrappers.

I probably need to clarify a bit more. It is very common for front-end
web developers to code JavaScript using static files, click
Shift+reload and have the new code execute. There is no need for a
server process to transform the file.

Front-end web developers should be able to author CommonJS modules in
the same way --- as static files that can be hand-authored. Since the
schemes to get a CommonJS module to load in the browser require a
function wrapper, then to me, it makes most sense that the CommonJS
module format uses a function wrapper as its default module format.

By specifying what is "required" by the module outside of the module
definition, then it is possible to make sure the required modules have
been loaded before executing the module factory method. This is
required because fetching modules in the browser will be async in many
cases and can be out of order.

So if those are the requirements then it is best to make the module
format as terse as possible since it will be hand-coded by developers.
Something like:

LOADER_ENTRY_FUNCTION(
"a", //Defining module named "a"
["b", "c"], //"a" requires "b" and "c" to do its work.
function(b, c) { //
//Return an object or function to define the "a" module.
return {
doSomething: function() {
return b.foo + c.bar();
};
}
);

Of course this is just an example, and needs more work, but
demonstrates something that could be hand-coded by a developer and
work in a server-side JS as well as just a plain static file in the
browser (given a JavaScript file that defines LOADER_ENTRY_FUNCTION).

Note that the above syntax also avoids a require/require.async split:
all required modules are assumed to be loaded async and the module
factory function is called only when the required modules are
available.

If CommonJS supported this module format by default, I believe
CommonJS will get more traction since front end developers will be
able to code and share modules without server transform setups.

I also feel like CommonJS has treated browser use as a second class
citizen, which may have made more sense when it was ServerJS. However,
claiming CommonJS as the moniker seems to warrant moving the browser
environment to first class. One of the main promises of server-side JS
is being able to share code with the front end. Of course not all
modules map to both environments, but lots do. The more that can be
shared outside of code, like coding practices, module formats, the
more traction it will get.

As it stands today, the CommonJS module format is unfriendly to the
browser. I fully appreciate there are ways to cajole the modules
written in CommonJS to work in the browser, but it requires more
machinery, more developer steps, and some of the approaches via XHR do
not fit the model well. These are higher barriers to entry.

YUI3 has gone with the function wrapper approach via YUI.use(), and
Dojo is likely to use some sort of variant of RunJS in the future. I
will be encouraging a similar format for jQuery too, and any other
browser library that will listen. YUI3 has already shipped their
model, but I am hoping that whatever Dojo or other libraries use can
be more closely aligned with what works best for the CommonJS group,
assuming CommonJS works out well in the browser.

> Of course, that implementation of require requires either module preloading
> or a bit more code with some synchronous XHR.  Personally, I would use
> module preloading *and* tweak the loader to return more than one module:
>
> #! /bin/sh
> #
> # loads /var/www/js/libexec/module.js when query string is module
> # Multiple module name are separated by spaces
> #
> echo "Content-Type: application/x-javascript"
> echo
>
> emit_module()
> {
>   echo "modules[$1] = (function () {"
>   cat /var/www/js/libexec/$1.js
>   echo "});"
> }
>
> set -- `echo "${QUERY_STRING}" | sed 's/%20/ /'` ""
>
> while [ "$1" ]
> do
>   emit_module "$1"
> done
>
> So, a script tag to preload multiple modules might look like
>
> <script language="JavaScript" src="/cgi-bin/preload_modules.cgi?crypto mime
> bignum">
>

Having a runtime server process to aggregate modules is fine for
performance reasons, but this should not be a requirement to run and
test modules. A best practice for browser development: Each page only
loads one module for the page.

<!-- loader.js defines LOADER_ENTRY_FUNCTION -->
<script src="loader.js"></script>
<script>LOADER_ENTRY_FUNCTION(["page1"]);</script>

Then the page1 module might look like:

LOADER_ENTRY_FUNCTION(
"page1",
["b", "c"],
function(b, c) { //
document.addEventListener("DOMContentLoaded", function() {
//Do page setup in here, use b and c
}, false);
}
);

While in dev mode, each one of the modules can be loaded separately,
allowing the best debug experience. Then a build process can be run to
inline all of page1's dependencies and nested dependencies into one
file. Or a runtime-combo loader could be used by swapping out the code
in loader.js.

The important thing is that the HTML does not need to change and
performance can be an add-in via a build time step (which would avoid
runtime server transforms) or have the option for runtime server
transform if desired.

>> It would be simpler to just have one flavor, and one that worked well
>> in the browser. That implies the flavor using a function wrapper for
>> the code that defines the module, and use the return value of that
>> function as the "exported" module.
>
> Frankly, that's an implementation detail. Modifying a global variable,
> returning a value.. heck, browser-local storage.. all valid options.
>
> Something else to remember
>  - CommonJS programs themselves are modules
>  - It is not legal to use a return statement outside of a function
>  - am not sure yet if "can programs return?" has been specified; it probably
> should be one way or another.

Hopefully I clarified via code examples above, the return statement is
from the module factory method as an explicitly hand-coded function.

> Keep in mind that APIs built around returning functions somehow seem to
> evolve by giving functions properties. Yucky yuck.  I prefer "pick one thing
> which does not restrict what you can do" - and to me that is an object.
>
> Important: Objects in JS in this context aren't just like C++ object
> instances.  They are more like namespaces.  I don't have a problem with a
> constructor in a namespace.
>
> In fact, I bet most objections to require returning an object would go away
> if we called them namespaces, specified their behaviour, and let people
> implement as they wished. Of course, everybody would just use an object...

I appreciate that is your personal preference, but I do not think a
name change would avoid the desire for functions. I have found
constructor functions with their prototype property to be useful units
of code. This is used extensively in Dojo today, where every widget is
a constructor function. Each widget is its own module.

Besides that though, allowing properties on functions is legitimate
JavaScript, and I find actually helps write a terser API. I see it as
a distinct advantage of JavaScript.

>> If module dependencies are passed to the module function wrapper, it
>> avoids the concern about needing a require and a require.async, more
>> module API surface reduction. It also makes it possible for the
>> modules to work naturally in the browser environment.
>
> Module dependencies are not the business of the module user. They are the
> business of the module-writer.

Correct, but a module user is a module writer normally. The module
user should only need to specify the other module as its dependency
(as happens today in CommonJS modules via a require() call), and of
course the module user should not have to specify the nested
dependencies behind that other module.

>
> Unfortunately, this leaks out at the browser module user unless
>  - You can perform static analysis on the content/modules to work with your
> loader, or
>  - You load modules synchronously, or
>  - You use a completely different module loading scheme (async require)
>
> Personally, I am in favour of the first choice for the browser environment.
> It is not a hard problem to solve in most cases.  I would be willing to help
> solve it, as well, with a loader/resolver written in CommonJS on the server
> end.

Hopefully I outlined above why I believe option three fits best with
normal browser-based development, and should be considered a first
class CommonJS module format.

James

Kevin Dangoor

unread,
Dec 22, 2009, 3:32:49 PM12/22/09
to comm...@googlegroups.com
On Tue, Dec 22, 2009 at 2:51 PM, James Burke <jrb...@gmail.com> wrote:
[snip]

I also feel like CommonJS has treated browser use as a second class
citizen, which may have made more sense when it was ServerJS. However,
claiming CommonJS as the moniker seems to warrant moving the browser
environment to first class. One of the main promises of server-side JS
is being able to share code with the front end. Of course not all
modules map to both environments, but lots do. The more that can be
shared outside of code, like coding practices, module formats, the
more traction it will get.


The "CommonJS" name reflects the idea that CommonJS apps can be server-side, desktop apps, command line tools and in-browser apps.

So, the idea is that relevant parts of CommonJS apps could be used in any one of those platforms. It then becomes a question of what you optimize for.

Imagine, for a second, a version of Python that supported inline function definitions. We then give it a module loader like so:

run(
    "foo",
    ["bar"],
    def(bar):
        return {
            "dosomething": def(x):
                return bar.multiply(x, 2)
        }
)

How successful would such a Python have been against

import bar

def dosomething(x):
    return bar.multiply(x, 2)


and in JS

var bar = require("bar");

exports.dosomething = function(x) {
    return bar.multiply(x, 2);
};



I know what you're getting at, but I also know just how little server-side scaffolding is required to support CommonJS modules. It's tiny and can be written in any language easily. As Kris Kowal has mentioned, there is some value in standardizing the transport a bit so that the server side/build tool portion of this equation can be shared.

If it was *impossible* to use CommonJS modules in the browser, or if the server side had to be some complex beast with only one likely implementation, then yes that'd be a problem to fix.

As it is, though, I don't think adding extra noise to every module is worth it just to eliminate a small server/build component.

Kevin

--
Kevin Dangoor

work: http://labs.mozilla.com/
email: k...@blazingthings.com
blog: http://www.BlueSkyOnMars.com

Tom Robinson

unread,
Dec 22, 2009, 3:58:28 PM12/22/09
to comm...@googlegroups.com

On Dec 22, 2009, at 11:51 AM, James Burke wrote:

> Changed the subject since this is a bit of a different topic from
> setExports. More inline below...
>
> On Tue, Dec 22, 2009 at 7:22 AM, Wes Garland <w...@page.ca> wrote:
>> Actually, it doesn't mean two flavours of CommonJS modules, nor affect the
>> text of the module at all...It affects the module transport system and/or
>> loader. *ALL* CommonJS implementations have to have either a function
>> wrapper or something equivalent in order to load modules with a module-wide
>> variable scope.. a function wrapper (closure) is, in fact, the only
>> JavaScript construct which can allow this. Incidentally, while GPSEE uses
>> engine-specific tricks for this, I know for a fact that Flusspferd is
>> implemented with scripted closure wrappers.
>
> I probably need to clarify a bit more. It is very common for front-end
> web developers to code JavaScript using static files, click
> Shift+reload and have the new code execute. There is no need for a
> server process to transform the file.

Absolutely, but the function wrapper boilerplate is not necessary for development scenarios.

XHR + eval are adequate in most situations. Until recently this would lead to a poor debugging experience, but now both WebKit [1] and Firebug [2] support the sourceURL hack.

Of course when you deploy any app of moderate size you should be running some sort of build process anyway, which can do whatever processing is necessary to efficiently load the modules.

This is the approach we take with Objective-J and Cappuccino and it works well.


[1] http://pmuellr.blogspot.com/2009/06/debugger-friendly.html
[2] http://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/


>
> Front-end web developers should be able to author CommonJS modules in
> the same way --- as static files that can be hand-authored. Since the
> schemes to get a CommonJS module to load in the browser require a
> function wrapper, then to me, it makes most sense that the CommonJS
> module format uses a function wrapper as its default module format.
>
> By specifying what is "required" by the module outside of the module
> definition, then it is possible to make sure the required modules have
> been loaded before executing the module factory method. This is
> required because fetching modules in the browser will be async in many
> cases and can be out of order.

This is why the modules spec says that the argument to require() should be a string literal, to allow for simple static analysis of dependencies. In Objective-J we use a different syntax (@import "foo.j" etc) but this works too. If you need to compute a module id you should use require.async() for compatibility.

>
> So if those are the requirements then it is best to make the module
> format as terse as possible since it will be hand-coded by developers.
> Something like:
>
> LOADER_ENTRY_FUNCTION(
> "a", //Defining module named "a"
> ["b", "c"], //"a" requires "b" and "c" to do its work.
> function(b, c) { //
> //Return an object or function to define the "a" module.
> return {
> doSomething: function() {
> return b.foo + c.bar();
> };
> }
> );

IMO this is *far* too much boilerplate to be hand-coded. Generated by a build process though, sure.


James Burke

unread,
Dec 22, 2009, 4:55:57 PM12/22/09
to comm...@googlegroups.com
On Tue, Dec 22, 2009 at 12:58 PM, Tom Robinson <tlrob...@gmail.com> wrote:
> XHR + eval are adequate in most situations. Until recently this would lead to a poor debugging experience, but now both WebKit [1] and Firebug [2] support the sourceURL hack.
>
> Of course when you deploy any app of moderate size you should be running some sort of build process anyway, which can do whatever processing is necessary to efficiently load the modules.
>
> This is the approach we take with Objective-J and Cappuccino and it works well.
>

XHR +eval is what Dojo has used for a very long time. However,
developers complain about it, it is just not as nice a development
environment. I believe Kevin even complained about its use in Dojo
before. And it is slower than script tags. You need at least async XHR
to compete (which adds to the loader size) but still using eval is
slower.

eval is not supported in some environments like Adobe AIR. Lots of
front end developers have been told that eval is actually evil. I have
been told that some people did not consider using Dojo because it uses
eval.

The loader gets more complicated when you want to load some things off
the CDN and then some things locally while developing. Then add in
cross-domain multiversion support.

It is more code and complication because it is going against the grain
of the browser.

> IMO this is *far* too much boilerplate to be hand-coded. Generated by a build process though, sure.

There is a threshold where the browser boilerplate is not that much
more (given that "require" and "exports" need to be typed multiple
times). However, on the whole, for things with one or two
dependencies, I agree the browser boilerplate is more typing.

However, it also fits better with javascript -- returning a value from
a function seems more natural than a magic exports variable. And can I
do exports= or just give properties to export, or call exports()?

Those are mostly sugar/bikeshed arguments. But JavaScript is not
popular because of its syntax. It is popular because it is deployed,
in the browser. Yes, we should make the syntax as nice as we can, but
it is unfortunate if it increases the barrier of its use in the most
widely deployed JavaScript environment.

This discussion has really helped me though. It is good to know the
explicit tradeoffs being made by the group, as Kevin mentions in his
email on this thread -- going against the grain in the browser for a
syntax that is more targeted for other environments.

Thanks,
James

Kevin Dangoor

unread,
Dec 23, 2009, 7:58:16 AM12/23/09
to comm...@googlegroups.com
On Tue, Dec 22, 2009 at 4:55 PM, James Burke <jrb...@gmail.com> wrote:
On Tue, Dec 22, 2009 at 12:58 PM, Tom Robinson <tlrob...@gmail.com> wrote:
> XHR + eval are adequate in most situations. Until recently this would lead to a poor debugging experience, but now both WebKit [1] and Firebug [2] support the sourceURL hack.

The sourceURL hack will likely get better over time, but at least as of a few months ago Firebug was still not reporting the proper locations of exceptions in eval'ed code.
 
>
> Of course when you deploy any app of moderate size you should be running some sort of build process anyway, which can do whatever processing is necessary to efficiently load the modules.
>
> This is the approach we take with Objective-J and Cappuccino and it works well.
>

XHR +eval is what Dojo has used for a very long time. However,
developers complain about it, it is just not as nice a development
environment. I believe Kevin even complained about its use in Dojo
before. And it is slower than script tags. You need at least async XHR
to compete (which adds to the loader size) but still using eval is
slower.

The ability to use script tags in development and then build something for deployment has worked well for me.
 
eval is not supported in some environments like Adobe AIR. Lots of
front end developers have been told that eval is actually evil. I have
been told that some people did not consider using Dojo because it uses
eval.

Well, eval may not supported in Adobe AIR, but Adobe AIR runs locally so you don't need to worry about the round trip time of making multiple requests.
 
The loader gets more complicated when you want to load some things off
the CDN and then some things locally while developing. Then add in
cross-domain multiversion support.

It is more code and complication because it is going against the grain
of the browser.

I would think that cross-domain multiversion support would add some amount of complication to any. Also, in the CDN case, what you'd deploy to the CDN would have the loader and modules already "compiled" with the loading wrapper.
 

> IMO this is *far* too much boilerplate to be hand-coded. Generated by a build process though, sure.

There is a threshold where the browser boilerplate is not that much
more (given that "require" and "exports" need to be typed multiple
times). However, on the whole, for things with one or two
dependencies, I agree the browser boilerplate is more typing.

You are right that RunJS is more concise for bigger modules with more dependencies.

It's worth considering here as well that JavaScript can grow additional syntax that makes CommonJS-style modules quite a natural fit for JS. Granted, it takes years for anything to be widely deployed in the browser, but CommonJS can be deployed there today.
 
However, it also fits better with javascript -- returning a value from
a function seems more natural than a magic exports variable. And can I
do exports= or just give properties to export, or call exports()?

but what if JS gets an "export" keyword?

export foo = 'bar';

Those are mostly sugar/bikeshed arguments. But JavaScript is not
popular because of its syntax. It is popular because it is deployed,
in the browser. Yes, we should make the syntax as nice as we can, but
it is unfortunate if it increases the barrier of its use in the most
widely deployed JavaScript environment.


I can't deny that it increases the barrier, but I still contend that it only increases the barrier by a small amount.
 
This discussion has really helped me though. It is good to know the
explicit tradeoffs being made by the group, as Kevin mentions in his
email on this thread -- going against the grain in the browser for a
syntax that is more targeted for other environments.

Yeah, and that is the crux of it.

The browser is the most widely deployed JS environment, but I think that the syntax that we've chosen will help JS start to compete better in other environments.

James Burke

unread,
Dec 23, 2009, 1:06:21 PM12/23/09
to comm...@googlegroups.com
On Wed, Dec 23, 2009 at 4:58 AM, Kevin Dangoor <dan...@gmail.com> wrote:
> The ability to use script tags in development and then build something for
> deployment has worked well for me.

That might work if your module is at the top of the stack, just
consuming other modules. Although I do not see how this works for a
CommonJS module -- you need some other boilerplate in the page to
specify what exports is, and how it ties into the right place in the
JS loader?

Even with that working, seems awkward to swap out a set of script tags
as you move to developing one module to the next.

> Well, eval may not supported in Adobe AIR, but Adobe AIR runs locally so you
> don't need to worry about the round trip time of making multiple requests.

While local file serving is nice for performance, it will not matter
since the modules will not run without eval support.

> I would think that cross-domain multiversion support would add some amount
> of complication to any. Also, in the CDN case, what you'd deploy to the CDN
> would have the loader and modules already "compiled" with the loading
> wrapper.

Correct, CDN files could be compiled with the boilerplate wrapper, but
with an XHR loader, there are now two code paths for IO, one for XHR
stuff and one for script tags. If it was a script based loader, there
is just one path. It can certainly be done (as shown in Dojo), but
there are more moving parts.

> but what if JS gets an "export" keyword?
>
> export foo = 'bar';

The export keyword has always struck me as weird. It seems like a
variant of the return keyword, with an assumption of the file
boundaries as the function boundary? Why not just use return? I can
appreciate you cannot use return in CommonJS at the moment because of
the current state of the language. Hmm, although since all CommonJS
loaders wrap the module in some sort of boilerplate, maybe return
would work if that boilerplate includes a function wrapper.

There may be another language that return cannot be used, although
maybe it is more appropriate to fix that issue if looking at a new
keyword. At the very least for CommonJS today, assigning to exports
seems needed, instead of the current CommonJS approach of only
allowing property additions. So if assigning is allowed, then seems
like return could be used.

I also do not see how export works at least with current browser
script loading. In the browser it is common to combine many module
files into one file for performance. For export to work in that case,
there needs to be some sort of module boundary syntax? function(){}?
Or is it assumed (or even better, specified) that some other script
loading scheme will work its way into the browser, maybe something
that can process the equivalent of a zip file that has each module as
a file in the zip?

For my noob eyes, a new export keyword in the language seems to be an
effort to work around current JavaScript syntax -- "using function(){}
with a return seems like too much typing and too many braces". I
appreciate wanting sugar. It could be gotten in other ways, shortening
the function keyword, allow a "use nobraces" mode for more python-like
indentation for blocks. But then every language has its funny parts.
Having more than one way to do something can have a negative effect.

James

Wes Garland

unread,
Dec 23, 2009, 2:22:56 PM12/23/09
to comm...@googlegroups.com
On Wed, Dec 23, 2009 at 1:06 PM, James Burke <jrb...@gmail.com> wrote:
That might work if your module is at the top of the stack, just
consuming other modules. Although I do not see how this works for a
CommonJS module -- you need some other boilerplate in the page to
specify what exports is, and how it ties into the right place in the
JS loader?

That's not boilerplate -- that's the scaffolding of the implementation of CommonJS for your platform.  CommonJS modules simply do not work anywhere without some surround scaffolding.

A reasonable browser-based CommonJS environment will probably have a script loaded in the document head (first script tag) and some server-side support.  Server-side support will either be dynamic (like the module loader cgi I posted yesterday) or a sort of "compilation" environment.  More than likely both.

Loading modules will also be an implementation detail. I suspect we will see a variety of tricks, including SCRIPT-tag injection.

The one thing you're *not* going to see is "bare" modules working with SCRIPT tags.  The browser just doesn't support this.  You need to composite on the server end, either dynamically or statically.  A smart dynamic compositor would do composition by prepending to the first line of the emitted script, so that line numbers are not altered.

Even with that working, seems awkward to swap out a set of script tags
as you move to developing one module to the next.

That's not necessary.  Develop on a rich server platform with a dynamic module system. If you need to move to static for a CDN (CDN should just proxy IMHO anyhow!) you fire up a pre-generated version for "production".
 

Correct, CDN files could be compiled with the boilerplate wrapper, but
with an XHR loader, there are now two code paths for IO, one for XHR
stuff and one for script tags. If it was a script based loader, there
is just one path. It can certainly be done (as shown in Dojo), but
there are more moving parts.

Is there a way to synchronously run a script by injecting SCRIPT tags into the DOM? (I don't think so).   The only time XHR becomes interesting in this game, IMHO, is for synchronous require.
 
> but what if JS gets an "export" keyword?
>
> export foo = 'bar';

The export keyword has always struck me as weird. It seems like a
variant of the return keyword, with an assumption of the file
boundaries as the function boundary? Why not just use return?

Circular depends.  When A requires B requires A -- if A hasn't returned, what is B going to get? This is why we have the exports object. B will always be able to reference it, even if it's not fully populated yet.
 
 So if assigning is allowed, then seems
like return could be used.

Except that assignment happens instantly and return happens once.
 
I also do not see how export works at least with current browser
script loading. In the browser it is common to combine many module
files into one file for performance. For export to work in that case,
there needs to be some sort of module boundary syntax? function(){}?

Did you read my post from yesterday?   I included sample code which demonstrates exactly how to accomplish this.
 
Wes

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

James Burke

unread,
Dec 23, 2009, 3:02:25 PM12/23/09
to comm...@googlegroups.com
On Wed, Dec 23, 2009 at 11:22 AM, Wes Garland <w...@page.ca> wrote:
> The one thing you're *not* going to see is "bare" modules working with
> SCRIPT tags.  The browser just doesn't support this.  You need to composite
> on the server end, either dynamically or statically.  A smart dynamic
> compositor would do composition by prepending to the first line of the
> emitted script, so that line numbers are not altered.

Ah OK, so Kevin was probably using a server-assisted transform for the modules.

>> Even with that working, seems awkward to swap out a set of script tags
>> as you move to developing one module to the next.
>
> That's not necessary.  Develop on a rich server platform with a dynamic
> module system. If you need to move to static for a CDN (CDN should just
> proxy IMHO anyhow!) you fire up a pre-generated version for "production".

Right, this is where we will have to agree to disagree. And I
appreciate that I am in the minority in this group. I prefer a module
format that does not *require* server transforms for it to work (or as
mentioned before, XHR/eval tricks that go against the browser grain).

>> The export keyword has always struck me as weird. It seems like a
>> variant of the return keyword, with an assumption of the file
>> boundaries as the function boundary? Why not just use return?
>
> Circular depends.  When A requires B requires A -- if A hasn't returned,
> what is B going to get? This is why we have the exports object. B will
> always be able to reference it, even if it's not fully populated yet.

I have come to the conclusion that circular depends should not be a
blocker for this. If it means sacrificing being able to assign your
export, particularly for constructor functions, that is a bigger
problem.

Circular dependencies are rare. If they happen then the design of the
modules usually needs to be rethought. However, there are legitimate
uses for circular requires, so there should be some way to handle
them. It is OK if the way to handle them is a bit weird, since they
are rare.

What I have working now in RunJS gives an undefined value back for the
circular dependency, but then the code can use run.get("modulename")
outside the circular definition loop to get the circular dependency
(search for the "Circular Dependencies" section):
http://github.com/jrburke/runjs/blob/master/README.md

With that code change, anything is allowed via the return from the
module definition function, including regular or constructor
functions.

>>
>>  So if assigning is allowed, then seems
>> like return could be used.
>
> Except that assignment happens instantly and return happens once.

I'm fine with that, given the circular dependency workaround mentioned
above. But I appreciate that this group may have different goals.

> Did you read my post from yesterday?   I included sample code which
> demonstrates exactly how to accomplish this.

Your post was how to support CommonJS modules. I do not see how that
works with a JavaScript export keyword. With an export keyword, what
is the module boundary? What does the exported value go? How is it
related to the "module"?

James

Wes Garland

unread,
Dec 23, 2009, 3:58:13 PM12/23/09
to comm...@googlegroups.com
On Wed, Dec 23, 2009 at 3:02 PM, James Burke <jrb...@gmail.com> wrote:Circular dependencies are rare. If they happen then the design of the

modules usually needs to be rethought. However, there are legitimate
uses for circular requires, so there should be some way to handle
them. It is OK if the way to handle them is a bit weird, since they
are rare.

Circular dependencies should not require any knowledge of the dependency graph by module implementors.  This is most true at the system library level, but also at the complex application level.


Your post was how to support CommonJS modules. I do not see how that
works with a JavaScript export keyword. With an export keyword, what
is the module boundary? What does the exported value go? How is it
related to the "module"?


Sorry, I thought the code was clear.

The stuff that gets injected at XXXX in the function() {   XXXX } is the module.  The closure allows JS to have a module-wide private scope.  The require function looks up the dependency in the global variable modules.

Of course, that is just one possible implementation.  I provide a module-wide scope through the use of JSAPI primitives and Ondras uses v8 contexts.

Wes

--

James Burke

unread,
Dec 23, 2009, 4:58:29 PM12/23/09
to comm...@googlegroups.com
On Wed, Dec 23, 2009 at 12:58 PM, Wes Garland <w...@page.ca> wrote:
> Circular dependencies should not require any knowledge of the dependency
> graph by module implementors.  This is most true at the system library
> level, but also at the complex application level.

But circular dependencies do require knowledge. Any of the solutions I
have heard on this list will either throw or there will be some sort
of error at some point. It sounds like there are schemes to avoid how
often a circular dependency error might occur, but the end result,
there are cases when the module implementor needs to do something
special for a circular dependency (for example using a require.async
in some cases).

So the module implementor will need to know how to deal with at least
some kinds of circular dependencies. If they need to know anyway,
might as well choose a path that gives greater benefits outside the
rare cases of circular dependencies.

What bothers me is that circular dependencies argument is being used
to cripple the type of the exported module. Restricting return type
affects more things than the rare case of circular dependency. And the
restriction does not eliminate a module owner from handling certain
types of circular dependencies.

In summary the urge to reduce the percentage of an already rare case
should not cripple larger, useful functionality.

>
>>
>> Your post was how to support CommonJS modules. I do not see how that
>> works with a JavaScript export keyword. With an export keyword, what
>> is the module boundary? What does the exported value go? How is it
>> related to the "module"?
>>
>
> Sorry, I thought the code was clear.
>
> The stuff that gets injected at XXXX in the function() {   XXXX } is the
> module.  The closure allows JS to have a module-wide private scope.  The
> require function looks up the dependency in the global variable modules.
>
> Of course, that is just one possible implementation.  I provide a
> module-wide scope through the use of JSAPI primitives and Ondras uses v8
> contexts.

Right, that is one possible implementation. However I was interested
in any real concrete export ECMAScript proposal. An export keyword by
itself is not enough if you consider how browsers load code today,
where multiple script files can be combined into one file. There needs
to be some other keywords, special variables for that to work (IIRC,
in you example that would be the "modules" variable and the function
wrappers).

I was trying to respond to Kevin's question about "but what if JS gets
an "export" keyword?"

I could not find a proposal for an export keyword in ECMAScript. I
found this, which does not talk about export:
http://wiki.ecmascript.org/doku.php?id=strawman:modules_emaker_style&s=export

and then there is this:
https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Statements/export
which implies the script file is the module boundary.

If the script file is the boundary of the script, this does not seem
to work with current JavaScript loading in the browser, particularly
since multiple script files could be combined into one file.

All of this was said to try to answer Kevin's question: saying export
might be a keyword at some point implies other things, and I do not
think it makes sense to use a variable name that may match closely
with some ECMAScript keyword proposal unless it is clear what those
other things around export are, and how they will work in the browser
particularly when multiple scripts may be joined together for
performance reasons.

But this particular point is minutiae. And probably more of a
distraction, not providing any real value. Sorry for dragging it out.

James

ihab...@gmail.com

unread,
Dec 23, 2009, 5:21:30 PM12/23/09
to comm...@googlegroups.com
On Wed, Dec 23, 2009 at 1:58 PM, James Burke <jrb...@gmail.com> wrote:
> What bothers me is that circular dependencies argument is being used
> to cripple the type of the exported module. Restricting return type
> affects more things than the rare case of circular dependency. And the
> restriction does not eliminate a module owner from handling certain
> types of circular dependencies.

For the record, I am personally not averse to either solution: either
"require.setExports() semantics all the time" or "attach exported data
to framework provided exports object all the time". What bothers me is
mushing the two solutions into one platform, precisely because each of
them makes a different flavor of deal with the devil of cyclic
dependency, and there's no need to make two deals where one will do.

Ihab

--
Ihab A.B. Awad, Palo Alto, CA

Kris Kowal

unread,
Dec 23, 2009, 5:36:26 PM12/23/09
to comm...@googlegroups.com
On Wed, Dec 23, 2009 at 1:58 PM, James Burke <jrb...@gmail.com> wrote:
> I was trying to respond to Kevin's question about "but what if JS gets
> an "export" keyword?"

This is the proposal Ihab and I made to TC39 in January.

http://docs.google.com/Doc?docid=0AVWyIFBvIVXGZGZneGI3Z2tfMzRncGszN3o5dg&hl=en

In the broad strokes, it still applies. We are writing new proposals
presently, including the E Maker proposal you cite, which is lower
level.

Kris Kowal

James Burke

unread,
Dec 23, 2009, 6:04:14 PM12/23/09
to comm...@googlegroups.com

Thanks for the link. It does not describe how a web browser could get
more than one module in a file. I can understand that may be out of
scope for that proposal, but it is hard for me to see how useful it is
until mechanisms for browser use are laid out.

James

Irakli Gozalishvili

unread,
Dec 24, 2009, 6:56:35 PM12/24/09
to comm...@googlegroups.com

Hi James,

Let me remind you that there is already a way for importing modules / scripts in the webworkers and I do think it's just a matter of time when will it get to the non worker scopes, and as an answer to your question how probably each browser will do it it's own way, probably it will be quite close to the way it's implemented in web workers.

Back to your proposal I also do think that it forces to much of a boilerplate and honestly I would've avoid that both as on server so in browser. All this metadata not only desugars the syntax it also increases size of your scripts which is quite unacceptable for a non local data.

Regarding usage of the modules as they are in browser: Actually I own on of the I believe many implementations for requiring commonjs modules. It's fully async and handles all the dependency resolutions, besides it uses differed evaluation what had been proven to be a really efficient for a browser. In the end I can run most of my packages without single line of change between server and client.

It's based on async XHR + eval + @ sourceURL

but can be easily switched to

async XHR + new Function + @ sourceURL (not debuggable in FF though)

or to the async XHR + script.textContent = "require.register(function(exports, module) {" + source.... (not debuggable )

I do think that making a module loader which deals with all the boilerplate stuff is much handier and efficient as well.

I think 2 & 3 options will work for AIR as well.

I also think that at some point @ sourceURL hack will be adopted for script injection as well :)

I you want to look at my implementation it here
http://github.com/Gozala/experiments/tree/experimental/commonjs/

Regarding
 
James

--

You received this message because you are subscribed to the Google Groups "CommonJS" group.
To post to this group, send email to comm...@googlegroups.com.
To unsubscribe from this group, send email to commonjs+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/commonjs?hl=en.



Wes Garland

unread,
Dec 25, 2009, 12:35:53 PM12/25/09
to comm...@googlegroups.com
But circular dependencies do require knowledge. Any of the solutions I
have heard on this list will either throw or there will be some sort
of error at some point.

If there is a circular dependency problem with require-current when modules do not execute other modules' functions during load, it's new to me.  Referencing them is okay, executing is not.  (If anybody can provide a counterexample, I'm all eyes)
 
It sounds like there are schemes to avoid how
often a circular dependency error might occur, but the end result,
there are cases when the module implementor needs to do something
special for a circular dependency (for example using a require.async
in some cases).

I don't believe this is true, unless the module is implementor is "doing it wrong". Modules should not be executing code willy-nilly during initialization, just setting stuff up for later execution.
 
What bothers me is that circular dependencies argument is being used
to cripple the type of the exported module.

That is not my argument at all. In fact, if real circular dependency problems exist with the current scheme, it might be easier to convince me it needs changing.
 
Restricting return type
affects more things than the rare case of circular dependency.

Yes, it does.  Restricting the return type is necessary when you restrict the return value.

However, allowing arbitrary return types appears to be nearly 100% sugar from my POV.

In summary the urge to reduce the percentage of an already rare case
should not cripple larger, useful functionality.

I guess by this statement, you mean that you consider having an environment where the caller can reason about the behaviour of the callee to be completely useless?
 


Right, that is one possible implementation. However I was interested
in any real concrete export ECMAScript proposal. An export keyword by
itself is not enough if you consider how browsers load code today,
where multiple script files can be combined into one file. There needs
to be some other keywords, special variables for that to work (IIRC,
in you example that would be the "modules" variable and the function
wrappers).

You're absolutely right that export alone is not the only semantic.  It's entirely possible that they would go with the CommonJS semantic, which is that each module-file gets its own var object.  This is not unheard of; many other programming languages do the same thing.

All of this was said to try to answer Kevin's question: saying export
might be a keyword at some point implies other things, and I do not
think it makes sense to use a variable name that may match closely
with some ECMAScript keyword proposal unless it is clear what those
other things around export are, and how they will work in the browser
particularly when multiple scripts may be joined together for
performance reasons.

But this particular point is minutiae. And probably more of a
distraction, not providing any real value. Sorry for dragging it out.


Yeah.  My take on this: name is unimportant; behaviour IS.

Wes

James Burke

unread,
Dec 25, 2009, 8:57:26 PM12/25/09
to comm...@googlegroups.com
On Fri, Dec 25, 2009 at 9:35 AM, Wes Garland <w...@page.ca> wrote:
>> But circular dependencies do require knowledge. Any of the solutions I
>>
>> have heard on this list will either throw or there will be some sort
>> of error at some point.
>
> If there is a circular dependency problem with require-current when modules
> do not execute other modules' functions during load, it's new to me.
> Referencing them is okay, executing is not.  (If anybody can provide a
> counterexample, I'm all eyes)

This is exactly the point I was trying to get at was in relation to
your earlier comment: "Circular dependencies should not require any


knowledge of the dependency graph by module implementors."

Special knowledge is needed even in the current CommonJS scheme: if a
circular dependency, then only reference do not execute. From what I
have seen so far of CommonJS modules, direction execution of a
required module is a common practice.

While a circular ref scheme (like current CommonJS one) that just
makes executing a dependency hazardous is more robust vs. a circular
ref scheme that makes direct reference and execution hazardous, it is
an advantage only in the rare case of circular dependencies.

For me, restricting return type to gain that slightly better circular
dependency handling only helps a rare case (circular dep management).
For me, wanting different return types is more valuable.

But as you mention in your response, you treat return type as sugar. I
see it more that requiring a module is like calling a function, and
calling functions allows for more than one type of return value. It
fits better with what I believe to be the normal expectations in
programming, particularly in JavaScript, with constructor functions.
But I appreciate that is my view, and your view is that it is sugar
and not needed.

>>
>> It sounds like there are schemes to avoid how
>> often a circular dependency error might occur, but the end result,
>> there are cases when the module implementor needs to do something
>> special for a circular dependency (for example using a require.async
>> in some cases).
>
> I don't believe this is true, unless the module is implementor is "doing it
> wrong". Modules should not be executing code willy-nilly during
> initialization, just setting stuff up for later execution.
>
>>
>> What bothers me is that circular dependencies argument is being used
>> to cripple the type of the exported module.
>
> That is not my argument at all. In fact, if real circular dependency
> problems exist with the current scheme, it might be easier to convince me it
> needs changing.

There are not real problems with the circular dependency handling in
and of itself with the current CommonJS scheme. However, I read the
responses to wanting different return types as saying "that hurts
circular referencing" because it would make direct reference
hazardous. However, hopefully I outlined that while it does make the
circular ref case not as robust, circular refs are a minority case in
a system, and they require special knowledge anyway, even with the
current scheme (for example do not execute only reference).

I can appreciate that you and others in the group may not want to make
that tradeoff though.

>> In summary the urge to reduce the percentage of an already rare case
>> should not cripple larger, useful functionality.
>
> I guess by this statement, you mean that you consider having an environment
> where the caller can reason about the behaviour of the callee to be
> completely useless?

If "reason about the behaviour of the callee" is in reference to the
circ dependency where the caller of require() should expect the callee
(require) to return something that is usable, then as described above,
the caller always needs to follow special rules about the callee's
return in the circ dep case. In the existing CommonJS scheme, the
caller must not try to execute, just reference. In my scheme I would
say execution and direct reference are hazardous with circ
dependencies. Either case, there is a strict logic that can be used by
the caller. In both circ dep handling cases, special rules are
applied, but it is a known set of behavior in both circ dep schemes.

James

Reply all
Reply to author
Forward
0 new messages