ClosestStorm / v8cgi

Automatically exported from code.google.com/p/v8cgi
BSD 3-Clause "New" or "Revised" License
0 stars 1 forks source link

Assignment to exports breaks the module system #115

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
In a module write:
exports = { fred: "fred", bill: "bill"};

Import this module and examine the imports.

The imports should be fred and bill as defined above
Instead an empty object is imported.

To make this work you must use
exports.fred = "fred";
exports.bill = "bill";

Assigning to exports works in node.js and is especially convenient when the 
code is also used in a browser and the exports are returned from a function 
which provides a private scope.

e.g.

exports = (function(){
   ...
   ...
   return {
      dothis: dothis,
      dothat: dothat
   };
}());

Original issue reported on code.google.com by andy.bis...@gmail.com on 7 Jun 2012 at 3:00

GoogleCodeExporter commented 9 years ago
It seems that the module system is retaining a pointer to the initial value of 
exports rather than the symbol.  Although this behaviour is hinted at in the 
commonjs wiki it is counter-intuitive and awkward (and also time consuming to 
debug when it doesn't work.)

Original comment by andy.bis...@gmail.com on 7 Jun 2012 at 3:10

GoogleCodeExporter commented 9 years ago
Exports are a readonly object; this is a feature, not a bug.

Please see the whole thread at 
https://groups.google.com/forum/?fromgroups#!topic/commonjs/EcbyXEazg3s for 
more discussion and argumentation.

TL;DR: as long as commonjs does not standardize exports as mutable, this 
implementation will remain commonjs-compliant. Assigning to exports is 
generally VERY bad practice with respect to code maintainability; it also 
introduces circular dependency issues and breaks middleware:

var requireGroupOfModules = function(modules) {
  var result = [];
  for (var i=0;i<modules.length;i++) {
    result = result.concat(require(modules[i]));
  }
  return result;
}

This function would not be possible if exports were mutable.

Finally, it is a good and very sane practice that a function returns only ONE 
datatype (an object, in the case of "require"). It makes the code much more 
deterministic and error-proof, even if the language itself is dynamic.

If you show me what do you consider "awkward" and "counter-intuitive", I might 
be able to provide some more assistance. You can also visit #commonjs at 
freenode; people there will be sure open to share their opinions and explain 
motivations behind this.

Original comment by ondrej.zara on 7 Jun 2012 at 4:17

GoogleCodeExporter commented 9 years ago
To be fair, most of that discussion is arguing for something different and
ill-advised.  There is no issue in defining the required return type (in
this case an object) as this is a reasonable expectation.  What is
unreasonable (and breaks the rule of least surprise) is introducing
immutable symbols by decree.  JavaScript symbols are mutable even when they
shouldn't be (undefined!) and this is what the user expects.  Also, I
cannot see any way in which the mutability or otherwise of a symbol in an
included module would affect its use in the including module.  How exactly
would the code presented fail?  I can think of horrible abuses of this rule
(var thing = exports; exports = {}; ....... exports.fred = fred) but I
would not normally consider the potential for such abuse to be a good
argument either way.

Having said that, you are compliant with the letter of the commonjs spec so
I guess that you are the wrong person to argue the case with :)

Original comment by andy.bis...@gmail.com on 7 Jun 2012 at 5:25

GoogleCodeExporter commented 9 years ago
Well, if you agree that require() should always return a plain object, the 
problem with middleware vanishes.

However, circular dependency issues are still present:

a.js:
require("b");

b.js:
require("a");

This situation works as expected when exports are non-mutable; does not work 
otherwise.

As for the mutability of JS objects - think of "exports" as of an function 
argument. In fact, it *is* a function argument: in v8cgi, your code is always 
wrapped in a

(function(require, exports, module){ .... })

wrapper. Assigning to exports just shadows the passed value; that's how 
function arguments behave. 

Original comment by ondrej.zara on 8 Jun 2012 at 7:56