Browserable Modules

1 view
Skip to first unread message

Kris Kowal

unread,
Sep 10, 2010, 7:56:38 PM9/10/10
to CommonJS
Mark Miller has posted a recommendation on the TC39 wiki for CommonJS
module boilerplate that permits modules to be crafted for use as
<scripts>.

http://wiki.ecmascript.org/doku.php?id=conventions:commonjs_adaptor

This builds on work by Peter Michaux, Isaac Schlueter, and myself to
demonstrate that CommonJS modules can be written to seamlessly migrate
from existing <script> systems (where the scripts are manually ordered
from least to most dependent topologically) to managed module systems.
Mark's implementation adds a stub require for scripts that uses the
global object as a module memo. In a browser, scripts are
self-naming, but in a CommonJS system, they are anonymous. So this is
similar to a transport format. I believe both approaches are useful.

Mark points out in the notes that modules conforming to the CommonJS
module conventions should be fine in SES, a secure subset of
ECMAScript.

Kris Kowal

Tom Robinson

unread,
Sep 11, 2010, 12:49:32 AM9/11/10
to comm...@googlegroups.com
Why not just put the modules in a dedicated object to limit the global pollution?

(function(require, exports) {
"use strict";
// ...
exports.x = 21;

}).apply({}, typeof require === 'function' ? [require, exports] :
[function(g){return function(id){return g[id];};}(this._modules = this._modules || {}),
this._modules['example/foo'] = {}]);

I'm not really a fan of distributing your modules with any kind of boilerplate, but since it's compatible with normal CommonJS loaders it's certainly better than RequireJS's approach of including the transport boilerplate in your original modules.

> --
> 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.
>

James Burke

unread,
Sep 11, 2010, 4:26:50 AM9/11/10
to comm...@googlegroups.com
On Fri, Sep 10, 2010 at 4:56 PM, Kris Kowal <kris....@cixar.com> wrote:
> Mark Miller has posted a recommendation on the TC39 wiki for CommonJS
> module boilerplate that permits modules to be crafted for use as
> <scripts>.
>
> http://wiki.ecmascript.org/doku.php?id=conventions:commonjs_adaptor
>
> This builds on work by Peter Michaux, Isaac Schlueter, and myself to
> demonstrate that CommonJS modules can be written to seamlessly migrate
> from existing <script> systems (where the scripts are manually ordered
> from least to most dependent topologically) to managed module systems.

Dealing with nested dependencies and sequencing of the evaluation of
modules in the correct execution order are the harder bits to work out
for browser loading. This recommendation is another take on a function
wrapper for modules, but it is hard to see how it adds more to making
it easier to consume CommonJS modules in the browser, particularly
when some of the other transport formats give more information on a
module's dependencies outside the execution of the module.

In what cases would this type of wrapper be more useful? It seems
unlikely that a site that already ordered its dependencies correctly
via plain script tags would move to this syntax given the the
verbosity of it. They would create a helper function wrapper for it,
and once that happens, it seems to fall more in line with the other
transport proposals. They may save some some size on the
implementation of their wrapper/loader, since it could not do
dependency resolution/ordering, but that is saving at most 4 KB of
minified/gzipped content. A site with enough script tags to make this
worthwhile will not notice the difference in performance of losing
4KB, and still lose out significantly on the benefits of a loader that
can trace dependencies and load via async script tags that will give
better performance vs inlined script tags.

James

Wes Garland

unread,
Sep 11, 2010, 11:29:15 AM9/11/10
to comm...@googlegroups.com
Hi, Kris!

What a timely post, thanks for pointing that out.

I recently started using modules in the browser, and have been serving up modules in a positively trivial way from a CommonJS server-side script that is loaded from the browser's SCRIPT tag.

/* boring set-up code omitted */
print("Content-Type: application/x-javascript");

print("Last-Modified: " + lastModified.toUTCString());
print("");
print("var modules; if (!modules) modules={}; function require(module) { return modules[module]; };");
for (let i=0; i < cgi.module.length; i++)
{
  print('modules["' + cgi.module[i] + '"]={};');
  print('(function(exports){');
  print(mmap(modulePath(cgi.module[i])).asString());
  print('})(modules["' + cgi.module[i] + '"]);');
}

This, too, is similar to transports format, but I don't support automatic dependency resolution, and haven't bothered adding features like "module.id'.  Since I'm loading from SCRIPT, obviously the module.path is also irrelevant.

Another interesting thing is that I keep my modules list on the ? query part of the URL, allowing me the benefit of cacheable URLs -- most of the time I'm sending clients 304s instead of 200s.

When I threw together the implementation above, I considered three key factors:
 - Cacheability
 - Responsiveness (few HTTP requests)
 - Ability to re-use server-side modules without special tweaking
 - Self-interoperability (can use SCRIPT src=require.js many times in one page without breaking)

I considered the following design points unimportant:
 - Lazy loading  (I know in advance I'll be doing mostly 304s and have small-footprint modules)
 - Payload size (see above)
 - Module Dependencies (I am using only my own modules)

So, nothing novel here, just food for future ideas.  Your post reminds, me, though, that I don't support relative require properly. That's something I probably WILL fix.

Incidentally, it should be pointed out somewhere that it is possible to implement something like require.async() via script-tag injection.

Wes

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

Eugene Lazutkin

unread,
Sep 11, 2010, 6:55:26 PM9/11/10
to CommonJS
Allow me to criticize this proposal to offset a heap o' praise in
other posts. Frankly it looks like one more "let's not do anything
about browsers" proposal.

First of all the boilerplate is much bigger and much more involved
than RequireJS requires (it was linked in the proposal). Another thing
is a dependency resolution --- there is none. As soon as you have a
handful of interdependent modules you have big problems with that ---
ask anybody who wrote significant client-side apps. My understanding
that using such modules in browser indirectly assumes "let's include
all modules we may need in the *correct order* with <script> node(s)".
Conversely in SSJS environment there is no such problem with the same
format => browser is still a 3rd-rate citizen for such modules, an
after-though, which deserves a band-aid instead of a real solution.

Cheers,

Eugene

Kris Zyp

unread,
Sep 12, 2010, 11:05:16 PM9/12/10
to comm...@googlegroups.com, Kris Kowal, Mark Miller
Is there any boilerplate that can be applied to ES6/Harmony modules to
run in today's script tags or CommonJS platforms? Unfortunately, at
least AFAICT, it seems that the current proposal in EcmaScript is
impossible to back-shim into ES3 or ES5, due to the fact that it is
based on new syntax. Or am I missing something?
Kris

--
Thanks,
Kris

Wes Garland

unread,
Sep 13, 2010, 9:52:42 AM9/13/10
to comm...@googlegroups.com, Kris Kowal, Mark Miller
Kris Zyp:

You're not missing anything. ES6/Harmony modules (I assume you're thinking of Dave Herman's proposal) will *not* run in a pure ES5 environment. This proposal introduces extra keywords into the language, but the biggest difference from my POV is that unbound references in those modules do NOT travel up the scope chain to the global object.

This means that, in the general case, I don't believe any amount of wrapping or sed^H^H^Hstatic analysis will allow modules to travel to/from the Harmony proposal, unless they were carefully written to allow this in the first place.

It is unclear to me what happens w.r.t. the global object, scope chain and standard classes in the harmony modules proposal, especially as they pertain to [], {}, and instanceof.

Kris Zyp

unread,
Sep 13, 2010, 10:03:28 AM9/13/10
to comm...@googlegroups.com, Wes Garland, Kris Kowal, Mark Miller
On 9/13/2010 7:52 AM, Wes Garland wrote:
> Kris Zyp:
>
> You're not missing anything. ES6/Harmony modules (I assume you're
> thinking of Dave Herman's proposal) will *not* run in a pure ES5
> environment. This proposal introduces extra keywords into the
> language, but the biggest difference from my POV is that unbound
> references in those modules do NOT travel up the scope chain to the
> global object.
>
> This means that, in the general case, I don't believe any amount of
> wrapping or sed^H^H^Hstatic analysis will allow modules to travel
> to/from the Harmony proposal, unless they were carefully written to
> allow this in the first place.
I don't have any objections to constraining modules to lexically scoped
variables. Static analysis can't "fix" a module that references globals
or variables that aren't locally defined, but it certainly can verify
that modules don't violate this constraint (I think JSLint already does
this). In fact, I am definitely all for this constraint, I believe it is
a critical step towards achieving object capability based secure loading
of untrusted code.

It is the keyword based format that makes it impossible to create a
compatible library for ES3/5 that is concerning, but I suppose that is a
discussion for es-discuss someday...

--
Thanks,
Kris

Daniel Friesen

unread,
Sep 13, 2010, 12:19:28 PM9/13/10
to comm...@googlegroups.com
You sure it isn't possible to pre-process the es-harmony modules into
ES5? I probably need a closer re-look at them, but I was really hoping
it was possible. CommonJS support has been losing any value to me lately
(especially after basing the web interface in my web app at work has
actually gotten in the way rather than helped) and reading that proposal
brought the dislike I had for the CommonJS module format in the first
place back to life. So I was thinking of finding a way to pre-process
that module syntax and make it runnable in ES5.

~Daniel Friesen (Dantman, Nadir-Seen-Fire) [http://daniel.friesen.name]

Wes Garland

unread,
Sep 13, 2010, 12:46:44 PM9/13/10
to comm...@googlegroups.com
> You sure it isn't possible to pre-process the es-harmony modules into ES5?

Figure out how to make an ES5 scope that can't read from the global object, then we'll talk.

Kris Kowal

unread,
Sep 13, 2010, 1:17:31 PM9/13/10
to comm...@googlegroups.com
On Mon, Sep 13, 2010 at 9:46 AM, Wes Garland <w...@page.ca> wrote:
> Figure out how to make an ES5 scope that can't read from the global object,
> then we'll talk.

Per spec,

(function () {
"use strict";
// global object is no longer on the scope chain
// and no longer provided as "this" by default
})();

Implementations are catching up.

Kris Kowal

Wes Garland

unread,
Sep 13, 2010, 1:22:06 PM9/13/10
to comm...@googlegroups.com
> Per spec,

HEH!

Thanks for pointing that out!

In that case, you know, it just might be possible to automatically translate between the two. Still need to think about the loader's semantics a little harder.

Mark S. Miller

unread,
Sep 13, 2010, 1:26:39 PM9/13/10
to comm...@googlegroups.com
On Mon, Sep 13, 2010 at 10:17 AM, Kris Kowal <kris....@cixar.com> wrote:
On Mon, Sep 13, 2010 at 9:46 AM, Wes Garland <w...@page.ca> wrote:
> Figure out how to make an ES5 scope that can't read from the global object,
> then we'll talk.

Per spec,

(function () {
   "use strict";
   // global object is no longer on the scope chain
   // and no longer provided as "this" by default
})();

That's incorrect. Free variables within the function body are still references to global variables. (A DeclarativeEnvironmentRecord wrapping) The global object is still at the bottom of the scope chain. 

Nevertheless, this insulation is possible in ES5 without needing to write a full lexer or parser in JavaScript: <https://mail.mozilla.org/pipermail/es-discuss/2010-August/011684.html>



Implementations are catching up.

Kris Kowal
--
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.




--
    Cheers,
    --MarkM

Mark S. Miller

unread,
Sep 13, 2010, 1:27:55 PM9/13/10
to comm...@googlegroups.com
On Mon, Sep 13, 2010 at 10:26 AM, Mark S. Miller <eri...@google.com> wrote:
On Mon, Sep 13, 2010 at 10:17 AM, Kris Kowal <kris....@cixar.com> wrote:
On Mon, Sep 13, 2010 at 9:46 AM, Wes Garland <w...@page.ca> wrote:
> Figure out how to make an ES5 scope that can't read from the global object,
> then we'll talk.

Per spec,

(function () {
   "use strict";
   // global object is no longer on the scope chain
   // and no longer provided as "this" by default
})();

That's incorrect. Free variables within the function body are still references to global variables. (A DeclarativeEnvironmentRecord wrapping) The global object is still at the bottom of the scope chain. 

Oops. I meant "An ObjectEnvironmentRecord".
 

Nevertheless, this insulation is possible in ES5 without needing to write a full lexer or parser in JavaScript: <https://mail.mozilla.org/pipermail/es-discuss/2010-August/011684.html>



Implementations are catching up.

Kris Kowal

--
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.




--
    Cheers,
    --MarkM



--
    Cheers,
    --MarkM

Isaac Schlueter

unread,
Sep 16, 2010, 6:34:07 PM9/16/10
to comm...@googlegroups.com
This is neat.

Mark, won't this approach break in the presence of cycles, though?

// foo.js
exports.bar = require("bar")

// bar.js
var foo = require("foo")
assert(foo.bar === exports)


It seems like the boilerplate could be modified to support the "not
yet done" approach that we've been using in the various ssjs
platforms:

[function(g){return function(id){return g['/'+id] = g['/'+id] || {};};}(this),
this['/example/foo'] = this['/example/foo'] || {}]);

Of course, that means that when a module can't be found, it won't
throw, so I'm not sure how one would work around that. Of course,
whichever tool is generating this code would know ahead of time which
modules it's going to define, and could handle that case easily
enough.

--i

Reply all
Reply to author
Forward
0 new messages