module, exports, require
var.js
exports.odd = "홀수입니다";
exports.even = "짝수입니다";
index.js
const { odd, even } = require("./var");
const checkNumber = require("./func");
function checkStringOddOrEven(str) {
if (str.length % 2) {
// 홀수만
return odd;
}
return even;
}
console.log(checkNumber(10));
console.log(checkStringOddOrEven("hello"));
콘솔
$ node index
짝수입니다
홀수입니다
module.exports로 한 번에 대입하는 대신, 각각의 변수를 exports 객체에 하나씩 넣었습니다. 동일하게 동작하는 이유는 module.exports와 exports가 같은 객체를 참조하기 때문입니다. 실제로 console.log(module.exports === exports)를 하면 true가 나옵니다. 따라서 exports 객체에 add 함수를 넣으면 module.exports에도 add 함수가 들어갑니다.

이번에는 모듈을 불러오는 require에 대해 알아봅시다. require는 함수이고, 함수는 객체이므로 require는 객체로서 몇 가지 속성을 갖고 있습니다. 그중에서 require.cache와 require,main에 대해 알아보겠습니다.
var.js가 있는 곳에 require.js를 만듭니다.
console.log("require가 가장 위에 오지 않아도 됩니다.");
module.exports = "저를 찾아보세요";
require("./var");
console.log("require.cache입니다.");
console.log(require.cache);
console.log("require.main입니다.");
console.log(require.main === module);
console.log(require.main.filename);
콘솔
$ node require
require가 가장 위에 오지 않아도 됩니다.
require.cache입니다.
[Object: null prototype] {
'D:\\공부\\Nodejs\\3-4\\require.js': {
id: '.',
path: 'D:\\공부\\Nodejs\\3-4',
exports: '저를 찾아보세요',
filename: 'D:\\공부\\Nodejs\\3-4\\require.js',
loaded: false,
children: [ [Object] ],
paths: [
'D:\\공부\\Nodejs\\3-4\\node_modules',
'D:\\공부\\Nodejs\\node_modules',
'D:\\공부\\node_modules',
'D:\\node_modules'
],
[Symbol(kIsMainSymbol)]: true,
[Symbol(kIsCachedByESMLoader)]: false,
[Symbol(kIsExecuting)]: true
},
'D:\\공부\\Nodejs\\3-4\\var.js': {
id: 'D:\\공부\\Nodejs\\3-4\\var.js',
path: 'D:\\공부\\Nodejs\\3-4',
exports: { odd: '홀수입니다', even: '짝수입니다' },
filename: 'D:\\공부\\Nodejs\\3-4\\var.js',
loaded: true,
children: [],
paths: [
'D:\\공부\\Nodejs\\3-4\\node_modules',
'D:\\공부\\Nodejs\\node_modules',
'D:\\공부\\node_modules',
'D:\\node_modules'
],
[Symbol(kIsMainSymbol)]: false,
[Symbol(kIsCachedByESMLoader)]: false,
[Symbol(kIsExecuting)]: false
}
}
require.main입니다.
true
D:\공부\Nodejs\3-4\require.js
require가 반드시 파일 최상단에 위치할 필요가 없고, module.exports도 최하단에 위치할 필요가 없다는 것입니다. 아무 곳에서나 사용해도 됩니다.
require.cache 객체에 대해 require.js나 var.js 같은 파일 이름이 속성명으로 들어 있는 것을 볼 수 있습니다. 속성 값으로는 각 파일의 모듈 객체가 들어 있습니다. 한 번 require한 파일은 require.cache에 저장되므로 다음 번에 require 할 때는 새로 불러오지 않고 require.cache에 있는 것이 재사용됩니다.
만약 새로 require하길 원하다면, require.cache의 속성을 제거하면 됩니다. 다만, 프로그램의 동작이 꼬일 수 있으므로 권장히지 않습니다. 속성을 자세히 살펴보면, module.exports했던 부분(exports)이나 로딩 여부(loaded), 부모(parent), 자식(children) 모듈 관계를 찾을 수 있습니다.
require.main은 노드 실행 시 첫 모듈을 가리킵니다. 현재 node.require로 실행했으므로 require.js가 require,main이 됩니다. require.main 객체의 모양은 require.cache의 모듈 객체와 같습니다.
dep1.js
const dep2 = require("./dep2");
console.log("require dep2", dep2);
module.exports = () => {
console.log("dep2", dep2);
};
dep2.js
const dep1 = require("./dep1");
console.log("require dep1", dep1);
module.exports = () => {
console.log("dep1", dep1);
};
dep-run.js를 만들어 두 모듈을 실행해보겠습니다.
const dep1 = require("./dep1");
const dep2 = require("./dep2");
dep1();
dep2();
코드가 위애서 실행되므로 require('./dep1')이 실행됩니다. dep1.js에서는 제일 먼저 require('./dep2)가 실행됩니다. 다시 dep2.js에서는 require('./dep1')이 실행됩니다.
콘솔
$ node dep-run
require dep1 {}
require dep2 [Function (anonymous)]
dep2 [Function (anonymous)]
dep1 {}
(node:30180) Warning: Accessing non-existent property 'Symbol(nodejs.util.inspect.custom)' of module exports inside circular dependency
(Use `node --trace-warnings ...` to show where the warning was created)
(node:30180) Warning: Accessing non-existent property 'constructor' of module exports inside circular dependency
(node:30180) Warning: Accessing non-existent property 'Symbol(Symbol.toStringTag)' of module exports inside circular dependency
(node:30180) Warning: Accessing non-existent property 'Symbol(nodejs.util.inspect.custom)' of module exports inside circular dependency
(node:30180) Warning: Accessing non-existent property 'constructor' of module exports inside circular dependency
(node:30180) Warning: Accessing non-existent property 'Symbol(Symbol.toStringTag)' of module exports inside circular dependency
dep1의 module.exports가 함수가 아니라 빈 객체로 표시됩니다. 이러한 현상을 순환 참조(circular dependency)라고 부릅니다. 이렇게 순환 참조가 있을 경우에는 순환 참조되는 대상을 빈 객체로 만듭니다. 이때 에러가 발생하지 않고 조용히 빈 객체로 변경되므로 예기치 못한 동작이 발생할 수 있습니다. 따라서 순환 참조가 발생하지 않도록 구조를 잘 잡는 것이 중요합니다.