module.exports vs exports vs export

模块导出的声明语句有很多种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module.exports.hello = function() {
console.log('hello');
}

module.exports = {
hello: function() {
console.log('hello');
}
}

exports.hi = function() {
console.log('hi');
};

const david = 'David';
export default david;

export {
hiDavid: function() {
console.log('Hi David!');
}
};

Nodejs遵循的是CommonJS规范,使用require方法来加载模块,而ES6中是使用import来加载模块。参考这个图片:

commonjs-es6

module.export vs exports

1
2
3
function (exports, require, module, __filename, __dirname) {
//...
}

可以看出require规范下moduleexports都是内置的变量。但是exports是被module.exports引用的,所以给任意其一赋值都是可行的。

1
2
3
4
5
6
7
8
9
// working
module.exports.hello = function() {
console.log('hello');
}

// working
exports.hi = function() {
console.log('hi');
};

这种情况下,模块导出的是包含两个方法的对象。但是对比下面的声明:

1
2
3
4
5
6
7
8
9
10
11
// working
module.exports = {
hello: function() {
console.log('hello');
}
}

// NOT Working!!
exports.hi = function() {
console.log('hi');
};

这种情况下,第二个声明失效,因为module.exports已经被直接赋值,exports会被忽略。
另外,因为exports只是一个变量,所以直接给其赋值也是无意义的。

1
2
3
4
5
6
7
8
9
10
// working
module.exports.hello = function() {
console.log('hello');
}
// NOT Working!!
exports = {
hi: function() {
console.log('hi');
}
}

Nodejs

require方法无法识别export导出语句,会直接报错。因此Nodejs环境下只能使用module.exports或者exports语法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// module.js
module.exports.hello = function() {
console.log('hello');
}

exports.hi = function() {
console.log('hi');
};

const david = 'David';
exports.default = david;

// index.js
var module = require('./module.js');
console.log(module); // {hello: function, hi: function, default: "David"}

ES6

import语句可以识别module.exportsexportsexport语法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// module.js
module.exports.hello = function() {
console.log('hello');
}

exports.hi = function() {
console.log('hi');
};

const david = 'david';
const hiDavid = function() {
console.log('Hi David');
};

export default david;
export {
hiDavid
};

// index.js
import test, {hello} from './module.js';
import TestModule from './module.js';
console.log(test); // david
console.log(hello); // function
console.log(TestModule); // {hello: function, hi: function, default: "david", hiDavid: function}

可以猜想到,import方法中,export defaultexports.default是被认为相似的声明,而且不会强制认为必须是相同的导出导入名称匹配。

这种互通,在Server Side Rendering技术中会有更好的体现。在服务器渲染技术中,我们需要找到一种声明在ES6模块(比如React代码)和Nodejs中都可以被识别,这种情况下,使用module.exports / exports顶替export会是更好的选择。