|
|
| Line 1: |
Line 1: |
| This specification addresses how modules should be written in order to be interoperable among a class of module systems that can be both client and server side, secure or insecure, implemented today or supported by future systems with syntax extensions. These modules are offered privacy of their top scope, facility for importing singleton objects from other modules, and exporting their own API. | | This specification addresses how modules should be written in order to be interoperable among a class of module systems that can be both client and server side, secure or insecure, implemented today or supported by future systems with syntax extensions. These modules are offered privacy of their top scope, facility for importing singleton objects from other modules, and exporting their own API. |
|
| |
|
| === Module Environment === | | == Contract == |
| | |
| | === Module Context === |
|
| |
|
| # A module receives a "require" function. | | # A module receives a "require" function. |
| Line 9: |
Line 11: |
| ## If the requested module cannot be returned, "require" must throw an error. | | ## If the requested module cannot be returned, "require" must throw an error. |
| # A module receives an "exports" object that it may add its exported API to as it executes. | | # A module receives an "exports" object that it may add its exported API to as it executes. |
| # Interoperable modules must use the exports object as the only means of exporting, since an implementation may prevent tampering with any other object shared among modules. | | # modules must use the "exports" object as the only means of exporting. |
|
| |
|
| === Module Identifiers === | | === Module Identifiers === |
| Line 19: |
Line 21: |
| # Absolute identifiers are resolved off the conceptual name space root. A loader may check multiple roots in a consistent order, like a PATH. | | # Absolute identifiers are resolved off the conceptual name space root. A loader may check multiple roots in a consistent order, like a PATH. |
| # Relative identifiers are resolved relative to the file in which "require" is called. | | # Relative identifiers are resolved relative to the file in which "require" is called. |
|
| |
|
| |
| === Security ===
| |
|
| |
| To be interoperable with secure environments, a module must satisfy the following additional constraints:
| |
|
| |
| # A module receives an "environment" variable that is an Object.
| |
| ## The "environment" may have a "print" function that accepts a "message" and an optional "label" String.
| |
| ### The label may be one of "log", "warn", "error", "pass", "fail".
| |
| ### Any other, unspecified label must be in lower-case and begin with "x" and a vendor-specific label like "x-v8cgi-database".
| |
| ## Any other, unspecified members of "environment" must be in camelCase and begin with "x" and a vendor-specific label like "xCajaDomita"
| |
| # The "require" function may have additional members.
| |
| ## Any unspecified members of "require" must be in camelCase and begin with "x" and a vendor-specific label like "xChironModule".
| |
| # A module must not refer or assign to any free variables apart from primordials ("Object", "Array", etc. as defined by ECMAScript), "require", "environment", and "exports".
| |
| # A module must not tamper with (assign to, assign to members of, delete, or otherwise mutate) the transitive primordials, the "require" object, any object returned by "require", or the transitive "environment".
| |
|
| |
|
| === Unspecified === | | === Unspecified === |
| Line 42: |
Line 29: |
| # Whether a PATH is supported by the module loader for resolving module identifiers. | | # Whether a PATH is supported by the module loader for resolving module identifiers. |
|
| |
|
|
| |
| == Loaders ==
| |
|
| |
| * ondras is presently implementing this proposal for v8cgi http://code.google.com/p/v8cgi/
| |
| * tlrobinson is presently implementing this proposal for Jack http://github.com/tlrobinson/jack/tree/functional
| |
| * kriszyp has implemented this proposal in Persevere http://www.persvr.org/
| |
| * pmuellr posted a sample loader http://wiki.github.com/pmuellr/modjewel
| |
| * kriskowal: prototype for a client-side module loader that supports modules that conform to this specification using PATH relative URL's or module relative URL's without making security guarantees is partially complete in the [[http://code.google.com/p/chironjs/source/browse/branch/safe/src/modules.js "safe" branch of modules.js]].
| |
| * wes garland at pagemail is working on a spidermonkey implementation
| |
|
| |
|
| == Sample Code == | | == Sample Code == |
|
| |
|
| // ==================================================================
| | math.js: |
| // source code
| |
|
| |
| //
| |
| // math.js
| |
| //
| |
| exports.add = function() { | | exports.add = function() { |
| var sum = arguments[0]; | | var sum = arguments[0]; |
| Line 68: |
Line 41: |
| }; | | }; |
| | | |
| //
| | increment.js: |
| // increment.js
| |
| //
| |
| var add = require('math').add; | | var add = require('math').add; |
| exports.increment = function(val) { | | exports.increment = function(val) { |
| Line 76: |
Line 47: |
| }; | | }; |
| | | |
| //
| | program.js: |
| // program.js
| |
| //
| |
| var inc = require('increment').increment; | | var inc = require('increment').increment; |
| var a = 1; | | var a = 1; |
| inc(a); // 2 | | inc(a); // 2 |
|
| |
|
| |
| // ==================================================================
| |
| // browser code in development
| |
|
| |
| // locked into on of these options
| |
| //
| |
| // 1) using a special XHR synchronous script loader and
| |
| // a) seeing poor error messages with strange line numbering
| |
| // not corresponding with source code line numbering or
| |
| // b) using a JavaScript interpreter written in JavaScript
| |
| // to execute the code retrieved by the XHR. Line numbers
| |
| // would then correspond to source code line numbers.
| |
| // (Seems like an extreme solution.)
| |
| //
| |
| // 2) edit-compile-load-test cycle using the code below. Also
| |
| // strange numbering in error messages that does not match
| |
| // source files. Tools could make compile automatic but that
| |
| // means tools are required and that is not the case in
| |
| // the current browser scripting world.
| |
|
| |
|
| |
| // ==================================================================
| |
| // compiled for production in browser
| |
|
| |
| //
| |
| // library.js
| |
| //
| |
| var require = (function() {
| |
|
| |
| // memoized export objects
| |
| var exportsObjects = {}
| |
|
| |
| // don't want outsider redefining "require" and don't want
| |
| // to use arguments.callee so name the function here.
| |
| var require = function(name) {
| |
| if (exportsObject.hasOwnProperty(name)) {
| |
| return exportsObject[name];
| |
| }
| |
| var exports = {};
| |
| // memoize before executing module for cyclic dependencies
| |
| exportsObject[name] = exports;
| |
| modules[name](require, exports);
| |
| return exports;
| |
| };
| |
|
| |
| return require;
| |
| })();
| |
|
| |
| var run = function(name) {
| |
| require(name); // doesn't return exports
| |
| };
| |
|
| |
| var modules = {};
| |
|
| |
| //
| |
| // compiledModules.js
| |
| //
| |
| modules["math"] = function(require, exports) {
| |
| exports.add = function() {
| |
| var sum = arguments[0];
| |
| for (var i=1; i<arguments.length; i++) {
| |
| sum += arguments[i];
| |
| }
| |
| return sum;
| |
| };
| |
| };
| |
|
| |
| modules["increment"] = function(require, exports) {
| |
| var add = require('math').add;
| |
| exports.increment = function(val) {
| |
| add(val, 1);
| |
| };
| |
| };
| |
|
| |
| modules["program"] = function(require, exports) {
| |
| var inc = require('increment').increment;
| |
| var a = 1;
| |
| inc(a); // 2
| |
| };
| |
|
| |
| //
| |
| // html in document head
| |
| //
| |
| <script src="library.js" type="text/javascript"></script>
| |
| <script src="compiledModules.js" type="text/javascript"></script>
| |
| <script type="text/javascript">
| |
| // You might not use use the window.onload property
| |
| // but rather addEventListener/attachEvent.
| |
| window.onload = function() {
| |
| run("program");
| |
| };
| |
| </script>
| |
|
| |
| == Browser Sharable as Written ==
| |
|
| |
| It would be handy to be able to share modules "as written" with the browser by loading the source code with html script tags. I think the following source code allows this feature. The first and last lines of the modules would be idiomatic boilerplate for modules to be shared on both the client and server.
| |
|
| |
|
| There are several ways to write the first and last lines. Some of them are shorter than below but give up some of the protection of the global namespace. The technique below only relies on the browser having the "require.install" property and the server not having it.
| | == Notes == |
|
| |
|
| // ==================================================================
| | * [[ServerJS/Modules/Secure|Secure Modules]] |
| // source code
| | * [[ServerJS/Modules/CompiledModules|Notes on compiling modules for browsers]] |
|
| | * [[ServerJS/Modules/ScriptModules|On writing modules that also work as <script>s]] |
| //
| |
| // math.js
| |
| //
| |
| (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);})();
| |
|
| |
| //
| |
| // increment.js
| |
| //
| |
| (function(){var mod=function(require,exports) {
| |
|
| |
| var add = require('math').add;
| |
| exports.increment = function(val) {
| |
| return add(val, 1);
| |
| };
| |
|
| |
| };require.install?require.install('increment',mod):mod(require,exports);})();
| |
|
| |
| //
| |
| // program.js
| |
| //
| |
| var inc = require('increment').increment;
| |
| var a = 1;
| |
| inc(a); // 2
| |
|
| |
|
| | == Amendment Proposals == |
|
| |
|
| Below is a comparison of a module written two different ways.
| | # [[ServerJS/Modules/Environment|Module Environment]] |
|
| |
|
| First, the "normal" way for the browser that people are accustom to seeing these days. There are other ways to do it but this is certainly one way in an attempt to make an oranges-to-oranges comparison.
| | == Implementations == |
|
| |
|
| // define the library in a file called asdf.js
| | * ondras is presently implementing this proposal for v8cgi http://code.google.com/p/v8cgi/ |
| // first and last lines are boilerplate
| | * tlrobinson is presently implementing this proposal for Jack http://github.com/tlrobinson/jack/tree/functional |
|
| | * kriszyp has implemented this proposal in Persevere http://www.persvr.org/ |
| var LIB=LIB||{};LIB.asdf=(function(){var exports={};
| | * pmuellr posted a sample loader http://wiki.github.com/pmuellr/modjewel |
|
| | * kriskowal: prototype for a client-side module loader that supports modules that conform to this specification using PATH relative URL's or module relative URL's without making security guarantees is partially complete in the [[http://code.google.com/p/chironjs/source/browse/branch/safe/src/modules.js "safe" branch of modules.js]]. |
| var c = 1;
| | * wes garland at pagemail is working on a spidermonkey implementation |
| exports.a = function() {return c;};
| |
| exports.b = 2;
| |
|
| |
| return exports;})();
| |
|
| |
| // use the library in the browser
| |
|
| |
| LIB.asdf.a();
| |
|
| |
| // use the library on the server
| |
|
| |
| require('asdf').LIB.asdf.a();
| |
|
| |
| // Use the library on the server or in browser.
| |
| // A module dependent on asdf would need to
| |
| // be written like this or something similar.
| |
|
| |
| (require?require('asdf'):window).LIB.asdf.a();
| |
| | |
| Now write the library so it can be used the same way in the browser and on the server where the server has securable module loading. Note the boilerplate is only a little longer and both versions of boilerplate include the name of the module once. The main three lines of the library are the same.
| |
|
| |
| // define the library in a file called asdf.js
| |
| // assume "exports" is not defined
| |
| // assume "modules" and "require" are defined
| |
| // first and last lines are boilerplate
| |
|
| |
| (function(){var mod=function(require,exports) {
| |
|
| |
| var c = 1;
| |
| exports.a = function() {return c;};
| |
| exports.b = 2;
| |
|
| |
| };require.install?require.install('asdf',mod):mod(require,exports);})();
| |
|
| |
| // use the library in the browser or on the server
| |
|
| |
| require('asdf').a();
| |