I hope it's not off-topic, but I'm a client-side JavaScript programmer who wants to write modules. (For what it's worth, Python is my preferred programming language.)
Anyway, I've come up with something that seems to scratch my itch, and I'd like to know what you think of it. The key idea is to use 'eval' to communicate between modules. (I know 'eval' is highly disapproved of, but perhaps in this case it does no harm and a lot of good. I'm not enough of a JavaScript expert to know for sure, and besides I'm biased.)
To my eyes, this module pattern seems to be bringing some indirection without any real benefit that I can see. Without using eval, or duplicate definitions of what's being exported or the name of the module or any of that, you can get modules with public and private variables pretty easily using standard JS:
http://ajaxian.com/archives/a-javascript-module-pattern
So, there's boilerplate in your coding style :)
var mod = function() {
var priv = 1;
return {
get: function() { return priv; },
set: function(p) { priv = p; }
}
}();
Doesn't really have boilerplate.
Even better, though, is the CommonJS standard:
var priv = 1;
exports.get = function() { return priv; };
exports.set = function(p) { priv = p; }
That's exactly the same pattern that I use in base2:
http://dean.edwards.name/weblog/2007/12/packages/
-dean
So, there's boilerplate in your coding style :)
var mod = function() {
var priv = 1;
return {
get: function() { return priv; },
set: function(p) { priv = p; }
}
}();
Doesn't really have boilerplate.
Ah, now we're talking. As described on
http://jshq.org/commonjs/0.1/modules.html
(BTW, there's a broken link to http://jshq.org/commonjs/0.1/ServerJS/Modules/Secure on that page.)
So how do I manage this using client JavaScript? Clearly, 'exports' has to be defined, and also clearly we have problems if it is a global.
Oh, another 404! http://jshq.org/commonjs/0.1/ServerJS/Modules/CompiledModules
(function(){var mod=function(require,exports) {
exports.add = function() {
var sum = arguments[0];
for (var i=1; i<arguments.length; i++) {
sum += arguments[i];
}
return sum;
};
};require.install?require.install('math',mod):mod(require,exports);})();
==
Well, that's got some boilerplate. Still, the body is quite nice, so perhaps I could be persuaded to live with it. (Although I'd rather have the module name 'math' up front rather than at the end. Probably easy to fix.)
http://wiki.commonjs.org/wiki/Modules
The jshq stuff will probably have to be fixed to interpret links better.
~Daniel Friesen (Dantman, Nadir-Seen-Fire) [http://daniel.friesen.name]
You'll find most of our work is in tact on our new wiki,
http://wiki.commonjs.org/
> So how do I manage this using client JavaScript? Clearly, 'exports' has to
> be defined, and also clearly we have problems if it is a global.
I've just completed a JSGI application that will serve any CommonJS
modules installed in Narwhal to a browser client.
http://github.com/kriskowal/narwhal/blob/master/docs/browser-api.md
This has made it finally possible for the same JavaScript modules to
run on both the client and the server for Narwhal.
The boilerplate you saw on the Wiki is the work of Peter Michaux who
did some research on ways to write modules such that they work both on
the server, and as normal global scripts on the client. The Narwhal
Browser API uses a "modules in transport" format, that is similar to
JSON in that it permits CommonJS compliant module factories and their
metadata to be transported to the client through script injection and
a common callback. The format is essentially boilerplate around a
compliant module, in addition to some JSON injected to describe the
module's shallow dependencies.
So, if you had a module:
var _ = require("gettext").gettext;
var drum = 0;
exports.count = function () {
return _(++drum);
};
It would be wrapped as:
require.register({
"factory": function (require, exports, module, system, print) {
var _ = require("gettext").gettext;
var drum = 0;
exports.count = function () {
return _(++drum);
};
},
"depends": ["gettext"]
})
There are a variety of ways that a client side module loader can be
written to make CommonJS compliant modules run in the browser. This
particular strategy is friendly to CDN's and debugging (as it can
preserve file names and line numbers). Other strategies with XHR are
more friendly to developing the JavaScript on the client with static
module files. The procedure is similar in any case: wrap the text of
the module in some sort of function that accepts via injection a
require, exports, and module object, then create a require function
that memoizes all of these "factory" functions and exports objects
based on their module identifier.
The end result is that your modules are very similar to Python, but
with certain advantages in the simplicity of module relative
identifiers and file locations.
Kris Kowal
http://dean.edwards.name/weblog/2007/12/packages/
A word on firepower:
Here's an example server application that requires a module on the client:
http://github.com/kriskowal/narwhal/blob/master/examples/browser-deployment-jackconfig.js
This is the loader that the server puts inline in the HTML:
http://github.com/kriskowal/narwhal/blob/master/lib/narwhal/inline.js
This is the module loader bootstrapper code. This is one of the
modules that gets sent down the wire by the inline loader, and is the
first to execute. It download the sandboxing module and loads the
rest of the necessary modules.
http://github.com/kriskowal/narwhal/blob/master/lib/narwhal/client.js
And this is the server code:
http://github.com/kriskowal/narwhal/blob/master/lib/narwhal/server.js
Kris Kowal
> So how do I manage this using client JavaScript? Clearly, 'exports' has to> be defined, and also clearly we have problems if it is a global.I've just completed a JSGI application that will serve any CommonJS
modules installed in Narwhal to a browser client.
http://github.com/kriskowal/narwhal/blob/master/docs/browser-api.md
This has made it finally possible for the same JavaScript modules to
run on both the client and the server for Narwhal.
The boilerplate you saw on the Wiki is the work of Peter Michaux who
did some research on ways to write modules such that they work both on
the server, and as normal global scripts on the client.
The Narwhal
Browser API uses a "modules in transport" format, that is similar to
JSON in that it permits CommonJS compliant module factories and their
metadata to be transported to the client through script injection and
a common callback. The format is essentially boilerplate around a
compliant module, in addition to some JSON injected to describe the
module's shallow dependencies.
So, if you had a module:
var _ = require("gettext").gettext;
var drum = 0;
exports.count = function () {
return _(++drum);
};
It would be wrapped as:
require.register({
"factory": function (require, exports, module, system, print) {
var _ = require("gettext").gettext;
var drum = 0;
exports.count = function () {
return _(++drum);
};
},
"depends": ["gettext"]
})
There are a variety of ways that a client side module loader can be
written to make CommonJS compliant modules run in the browser. This
particular strategy is friendly to CDN's and debugging (as it can
preserve file names and line numbers).
Other strategies with XHR are
more friendly to developing the JavaScript on the client with static
module files. The procedure is similar in any case: wrap the text of
the module in some sort of function that accepts via injection a
require, exports, and module object, then create a require function
that memoizes all of these "factory" functions and exports objects
based on their module identifier.
It would be wrapped as:
require.register({
"factory": function (require, exports, module, system, print) {
var _ = require("gettext").gettext;
var drum = 0;
exports.count = function () {
return _(++drum);
};
},
"depends": ["gettext"]
})
Well, the repetition of "gettext" violates "Don't Repeat Yourself" and so for me is an accident waiting to happen.
> I had a close look at it now, and it puzzled me for a while. It has
> ===
> var graphics = new base2.Package(this, {
> name: "graphics",
> version: "1.0",
> imports: "shapes",
> exports: "Layout"
> });
>
> // evaluate the imported namespace
> eval(this.imports);
> ===
>
> If I'm right, then
> new base2.Package(this,
> has the side-effect of setting
> this.imports
> to a special value.
>
Yes. The "imports" property defines a list of packages that the package
imports. The package is created inside a "new function(){}" construct so
there is a "this" object that I can use. "this.imports" is created by
adding together the exported symbols from all of the imported packages.
Other than that it is identical to the pattern that you use.
-dean
Oh -- makes sense. Still, unless I'm mistaken, that's not really repetition in the sense that you'll never have to write it -- it's just injected metadata as part of the wrapping. As Kris noted, your module would look like this:As simple and boilerplate-free as it gets.
var _ = require("gettext").gettext;
var drum = 0;
exports.count = function () {
return _(++drum);
};
It *can* be used directly on the client if the client loads the module via XHR and adds the boilerplate before eval'ing..On Thu, Sep 17, 2009 at 1:20 PM, Jonathan Fine <jonatha...@googlemail.com> wrote:
Yes, I agree it's quite nice. But it can't be used directly on the client, which is precisely what I want to be able to do.On Thu, Sep 17, 2009 at 6:12 PM, Dean Landolt <de...@deanlandolt.com> wrote:
Oh -- makes sense. Still, unless I'm mistaken, that's not really repetition in the sense that you'll never have to write it -- it's just injected metadata as part of the wrapping. As Kris noted, your module would look like this:As simple and boilerplate-free as it gets.
var _ = require("gettext").gettext;
var drum = 0;
exports.count = function () {
return _(++drum);
};
That requires HTML and JS to come from the same domain, so it's no good for me. (Nor is running a special server.)
Is PHP too special?On Thu, Sep 17, 2009 at 2:05 PM, Jonathan Fine <jonatha...@googlemail.com> wrote:
That requires HTML and JS to come from the same domain, so it's no good for me. (Nor is running a special server.)
Just curious... because while it'd be nice to use the modules without any kind of server-side cooperation, the reality is that it's not *much* server side cooperation that's necessary.
You might consider reading my draft plan for browser support.
http://github.com/kriskowal/narwhal/raw/master/docs/browser-api-plan.md
And you might want to review discourse on how we arrived at the
securable modules specification, under the headline "Related
Discussions" at http://wiki.commonjs.org/wiki/Modules
Summarily, there's no perfect solution, but there are several
solutions that address needs in various quarters and each makes a
different compromise. My Chiron project can load modules from a
single package, synchronously, over XHR, on demand. It however is
only debuggable in Firefox, is chatty, synchronous, and subject to the
same origin policy. The new technique for Narwhal is debuggable, very
fast, cross-domain, CDN-friendly, supports a module PATH, but requires
server side support to inject boilerplate and scan static
dependencies. In the next couple weeks, I'm working on a solution
that is debuggable, very fast, cross-domain, CDN-friendly, supports
the module PATH and doesn't require server side support, but does
require a build step to compose all of the modules into "factory
transport" notation (the require.register call). There are other
techniques I haven't implemented that will squeeze out more
performance using a combination of bundling, combination, and stateful
cache awareness on the server side. I hope these resources help frame
the issue.
Kris Kowal
I remember earlier discussion where it was said that
only packages would have dependency lists. So what
you are doing here is sort of generating single-
module packages, with dependencies to other single-
module packages?
> So, if you had a module:
>
> var _ = require("gettext").gettext;
> var drum = 0;
> exports.count = function () {
> return _(++drum);
> };
>
> It would be wrapped as:
>
> require.register({
> "factory": function (require, exports, module, system, print) {
> var _ = require("gettext").gettext;
> var drum = 0;
> exports.count = function () {
> return _(++drum);
> };
> },
> "depends": ["gettext"]
> })
Interesting, I've had similar things in mind. How are
you planning to map the above module registering to a
module name that can later be "required" in the browser?
Let's say we want to get to the count function by doing:
count = require("util/math").count;
Best regards
Mike Wilson