Skip to content

doc: add more detailed illustration for module.exports and exports. #9552

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 102 additions & 57 deletions doc/api/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -481,81 +481,126 @@ added: v0.1.16

* {Object}

The `module.exports` object is created by the Module system. Sometimes this is
not acceptable; many want their module to be an instance of some class. To do
this, assign the desired export object to `module.exports`. Note that assigning
the desired object to `exports` will simply rebind the local `exports` variable,
which is probably not what you want to do.
The `module.exports` object is used to export values in a js file. It is
initialized as `{}` by Module system, and passed to
[module wrapper](#modules_the_module_wrapper) as argument. (This makes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"as an argument". Remove the parentheses around the complete sentence.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: replace:

... a js file.

with:

... `*.js` files.

`module.exports` and `exports` available in a js file, the details will be
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

", the details..." unnecessary, remove, or replace with a "see LINK" that links to the docs.

described below.) `require` returns the processed `module.exports` as the
exports of a module.

For example suppose we were making a module called `a.js`

```js
const EventEmitter = require('events');

module.exports = new EventEmitter();
#### exports alias
<!-- YAML
added: v0.1.16
-->

// Do some work, and after some time emit
// the 'ready' event from the module itself.
setTimeout(() => {
module.exports.emit('ready');
}, 1000);
```
There is no magic behind `exports`. It is the `module.exports` passed to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove "there is no magic" phrase, it adds no value. "It is the" change to "exports is an alias to the". Link module.exports to its docs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key point of this sentence is i want to remove the alias, because of this word make some one like me very confused , till check the source code. alias tend to refer to something like reference, imply the things happened in export will reflect into module.export. But it is indeed a value copy from module.export by argument.
I am not sure how to describe this rightly.

[module wrapper](#modules_the_module_wrapper) as the first argument.

Then in another file we could do
Here is a minimized code logic of `require` to illustrate the export
principle, and how `module.exports` and `exports` works:<br/>

As described in [module wrapper](#modules_the_module_wrapper), your js file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

js -> Javascript

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please avoid the use of informal pronouns such as 'you', 'your', 'us', etc.

will be wrapped like this:
```js
const a = require('./a');
a.on('ready', () => {
console.log('module a is ready');
(function (exports, require, module, __filename, __dirname) { // fileWrapper
// Your module code actually lives in here
// So both of the exports and module.exports are valid in your file
});
```
And this is a minimized logic of `require` for `module.exports`:
```js
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the purpose of this code snippet?

function _require(fileWrapper) {
// module.exports is initialized as {}
const module = { exports:{} };
// The wrapped file will be called with this = module.exports
// module.exports is passed as exports. ( exports = module.exports )
// module is passed as module.
fileWrapper.apply(module.exports,
[module.exports, require, module, __filename, __dirname]);
// the return value of require, its the export results of a required module.
return module.exports;
}
```
When you `require` a file, Node.js will wrap it with a function wrapper as
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a useful way to describe require. If you want to read the code to understand the behaviour, the code is there, its open source. Docs should describe behaviour and interface, not just reformat the code and expect the reader to read the code to figure out how it behaves.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, this is me main purpose of the PR, cause of the old docs of module.export did not describe what the module.export should be and should not be, so this led to many people repeatedly ask what is a module.export and export in stackoverflow. previously if some one want to make sure what is a module.export, the only way is read the source code. Want to make this easier.

I thought _require is a pseudocode more than a code of reformating. Cause an exactly description maybe need hundreds of words(maybe will make the docs like a specification), so i thought a pseudocode is suitable here, and i checked it again, it is the things happened for module.export in currently source code.

The pseudocode make thes behaviors and interface of module.exports and exports obviously.
I think there should be an other alternatively good way to describe the behaviors and interface, but i am not be able to do that, cause of my poor English.

above, and call it like the code logic described in `_require`. By the code
logic, behaviors of `module.exports` and `exports` are dependent on these
points:
- `require` is a synchronize process.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

synchronize -> synchronous

- `require` return the `module.exports` as the exports of a module.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"returns"

- Javascript always passes by value.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

javascript always passes what by value?


As a result, the export results of a module is a returned value of
`module.exports` at the tickcount which `require` is returned.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not true

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the behaviors of this. I thought whenever the user do anything in a file 'x.js', the result of require('./x.js') in 'y.js', will always be the returned value of module.exports.
Because my English is poor, so sorry for my inexactly description.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// module "hello.js"
module.exports.yes = true;
setTimeout(function() {
module.exports.yes = false;
module.exports.somethingElse = true;
}, 1000);

I don't know exactly what you are trying to say, but I believe you are saying require(..../hello.js).yes === true always and forevere because it was true in the tick that require was called, but that isn't true. 1 second after the require, the module's API value will change.

Copy link
Contributor Author

@iamchenxin iamchenxin Nov 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, i want to say it is the value of the returned module.exports.
Cause in Javascript value of an Object is a memory location(or Handle?).
not sure how to describe the term value rightly. ( and there is a term deep value in Javascript, but in some place the value is used to refer to deep value).

Copy link
Contributor Author

@iamchenxin iamchenxin Nov 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is why i write a pseudo code for require, a pseudo code will always exactly and cross langauges and terms. (But if i am good at English, may be i do not need that pseudo code to explain what it should be.)


Note that assignment to `module.exports` must be done immediately. It cannot be
done in any callbacks. This does not work:

x.js:
Let's illustrate the behaviors and principles with codes. (You can use either
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Trott is this colloquial "let's" and "you" used in node documentation?

"codes" -> "code"

`_require` or directly a js file to check these behaviors.)

Synchronize behaviors (Demonstrate with the minimized `_require` above) :
```js
setTimeout(() => {
module.exports = { a: 'hello' };
}, 0);
```
function synchronize(exports, _, module) { // the module wrapper
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not a useful way to describe how to write modules, since users do not actually write modules like this

// Your module codes actually lives below:
exports.a = 'Add a props a to module.exports';
module.exports.b = 'Add a props b';
module.exports = {
c: 'Assign module.exports to a new Object, so a and b are invalid.'
};
exports.d = 'exports is invalid here, its referred to old module.exports';
exports = module.exports; // reassigning exports to new module.exports
exports.e = 'After reassigned to new module.exports, exports works again.';
//
exports = {
f: 'Assign exports to a local Object, so its not referred to module.exports'
};
exports.g = 'This just add a prop to the local Object above.';
}

y.js:
const exported = _require(synchronize); // Only c and e will be exported.
console.log(exported);
/* The exported result will be:
{
c: 'Assign a new Object to module.exports, so a and b are invalid.'
e: 'After reassigned to new module.exports, exports works again.'
}
*/
```

Asynchronous behaviors (directly checking in file) :<br/>
Assume you have a `x.js`:
```js
const x = require('./x');
console.log(x.a);
// Let's name the exported value, to make things clearly.
const the_returned_exports = {
a: 'sync export prop a'
};
module.exports = the_returned_exports;
setTimeout( () => {
module.exports.b = 'okay, module.exports is referred to the_returned_exports'+
' which returned in previously tickcount.';
module.exports = {
c: 'does nothing, it assign a new Object, but the module.exports was' +
' returned at previously tickcount.( the return is the_returned_exports ).'
}
},1000);
```

#### exports alias
<!-- YAML
added: v0.1.16
-->

The `exports` variable that is available within a module starts as a reference
to `module.exports`. As with any variable, if you assign a new value to it, it
is no longer bound to the previous value.

To illustrate the behavior, imagine this hypothetical implementation of
`require()`:

Then in `y.js`:
```js
function require(...) {
// ...
((module, exports) => {
// Your module code here
exports = some_func; // re-assigns exports, exports is no longer
// a shortcut, and nothing is exported.
module.exports = some_func; // makes your module export 0
})(module, module.exports);
return module;
}
const x = require('./x');
console.log(x); // { a } here
setTimeout( () => {
console.log(x); // { a, b } here
},1000);
// c is not exported
```

As a guideline, if the relationship between `exports` and `module.exports`
seems like magic to you, ignore `exports` and only use `module.exports`.
There is one more thing to notice: `this`. Because `module.exports` was passed
as `this` argument for `The module wrapper`, you may used `this` as an alias of
`exports` to export values previously, But since `this` is an undocumented
value, the behavior of `this` is not guaranteed. Ex: when you use `babel`,
`transform-es2015-modules-commonjs` will break `this`, so you should not use
`this` as a shortcut for export.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't agree with the above. this is documented, its documented right here! And if the module does not work when some random thirdparty tool is used to convert it to some other form... that should be part of that tools documentation, not proposed as a general rule for node modules.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did do find this in old module docs. searched in doc/api/module.md again previously.


**As a guideline**, never use 'this' to export, and if the `exports` and
`module.exports` makes you confused, ignore `exports` and only use
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you are still confused at this point, the docs have failed. I don't see any description here about the real difference between the two:

exports is more brief to use, and is good when just exporting functions

module.exports is necessary to use when a module should return something other than the default object provided as an export value, such as a function

If you want to add guidelines, the guideline should be to redefine exports and module.exports at the same time:

module.exports = exports = MyConstructorFunction;

`module.exports`.

### module.filename
<!-- YAML
Expand Down