[JavaScript] require(), exports, module.exports 정리

Kozel·2022년 10월 29일
1
post-thumbnail

1. 개요

require(), exports, module.exports을 처음 접했을 때 생소했지만 그렇게 어려운 구조는 아니었기에 사용법을 익히는게 큰 어려움은 없었다.

하지만 원리를 알아야 제대로 사용할 수 있다는 것을 알기에 정리를 해보려고 한다.

본문은 require(), exports, module.exports 공식문서로 이해하기를 참고했다.



2. 본문

2.1. Require()함수와 exports

node.js에서는 모듈을 불러오기 위해 require()함수를 사용한다.

다음은 예시이다.

// foo.js
const a = 10;
exports.a = a;
// bar.js
const foo = require('./foo.js');
console.log(foo.a); // 10

코드를 보면 그 구조를 어렵지 않게 파악할 수 있다.

foo.js에서 a변수를 exports.a를 통해 내보내고
bar.js에서 require('./foo.js') 함수를 통해 받아 a변수의 값을 출력한다.

exports와 require()함수를 통해 값을 전달하는 것은 이해했다. 그러면 이 원리는? 그리고 module.exports와 exports의 차이는 무엇일까?


2.2. Require()함수의 구조

Require()함수는 module.exports를 리턴한다.

Require()함수의 소스코드를 살펴보자.

var require = function(src){                  //line 1
    var fileAsStr = readFile(src);            //line 2
    var module.exports = {};                  //line 3
    eval(fileAsStr);                          //line 4
    return module.exports;                    //line 5
}
  • line1 : src의 인자를 받아온다 (2.1.에서 bar.js의 './foo.js')
  • line2 : 소스 파일(src에 해당하는 파일)을 읽어서 fileAsStr에 저장한다.
  • line3 : module.exports라는 빈 해시를 만든다.
  • line4 : fileAsStr을 eval한다.
  • line5 : line3의 exports 해시를 아웃풋으로 내보낸다.

필자로 그러했듯이 line4에 대한 이해가 쉽지 않을 것이다. 쉽게 말하자면 src를 복붙한다고 생각하면 되는데 코드로 보자면 다음과 같이 변경할 수 있다.

var require = function(src){                 //line 1
    var fileAsStr = readFile(src);           //line 2
    var module.exports = {};                 //line 3
    const a = 10; 						     //line 4.1
    exports.a = a;                           //line 4.2
    return module.exports;                   //line 5
}

결국 exports 해시는 { a : 10 } 로 되는 것이다.

따라서 bar.js를 다시 표현하자면?

//bar.js
// const foo = require('./foo.js');
const foo = { a : 10 };
console.log(foo.a);

즉 exprots에 들어간 <key, value>들이 require()함수의 아웃풋으로 나오는 것이다.


2.3. exports와 module.exports의 차이

여기서 꽤 꼼꼼하게 살펴본 사람이라면 의문이 들 것이다. exports와 module.exports는 같은 것인가?

var require = function(src){                 //line 1
    var fileAsStr = readFile(src);           //line 2
    var module.exports = {};                 //line 3
    const a = 10; 						     //line 4.1
    exports.a = a;                           //line 4.2
    return module.exports;                   //line 5
}

다음 코드를 다시 살펴보자면

  • line3에서 module.exports의 변수명으로 빈 해시를 만들었지만
  • line4.2에서는 exports에 <a : 10>을 넣었고
  • 결과로 보자면 이것은 module.exports에 <a : 10>을 넣은 것과 같다.

결론적으로 말하자면 exports는 단순히 module.exports를 참조한다. 짧은 alias일 뿐이다.

공식 문서대로 말하자면
module.exports와 exports는 같은 객체를 바라보고 있으며, exports는 module.exports의 shortcut이다.

쉽게 말해 'module.'을 반복적으로 사용하지 않기 위해서. 라는 이유 때문에 exports가 생겼다.


2.4. require(), exports, module.exports의 사용 목적

2.3.에서 확인했듯이 의문이었던 exports, module.exports에는 개념적으로는 차이가 없다. 그렇다면 우리가 중요하게 봐야할 것은?
바로 '어떻게 사용할까'이다.

크게 두 가지 목적으로 나뉘게 된다.


목적1. exported value/function를 담는 컨테이너로 쓰기.

아주 일반적인 케이스로 값과 함수를 가져오고 싶을 때 이다.

// bar.js
const example_Container = require('./foo.js');

let example_Container.value1;
let example_Container.value2;
let example_Container.function1;
let example_Container.function2;
...

목적2. constructor function으로 쓰기.

단순히 값이 아니라 객체를 생성하는 경우다.

const express = require('express');
const app = express();

목적1에서는 프로퍼티가 담긴 객체가 require()의 아웃풋이 되도록 exports한 것이지만
목적2에서는 객체가 아웃풋으로 나오도록 exports하는 식이다.


2.5. require(), exports, module.exports의 사용 시 유의할 점

다양한 경우마다 물론 주의할 점이 있다.

  • exported property/function 들의 컨테이너로 쓸 경우

    🔵
    // 정석
    module.exports.foo = "bar"
    // shortcut
    exports.foo = "bar"  
    // property 대신 함수
    module.exports.foo = function() { console.log("foo") }
    exports.foo = function() { console.log("foo") }
    🔵
    ❌
    exports = {foo : "bar"}
    exports = function() { console.log("foo") }

    ❌의 경우처럼 쓴다면 기존의 module.exports에 넣는 것이 아닌 아예 다른 exports라는 변수에 새로운 값을 넣는 것과 같다.


  • constructor function으로 쓰는 경우

    🔵
    // 정석
    module.exports = function() { console.log(“foo”)}
    🔵
    ❌
    // exported property/function 들의 컨테이너로 쓸 경우에서 안되는 경우가 같은 이유
    exports = function() { console.log(“foo”)}
    ❌
    🔺
    module.exports = function() { console.log(“foo”)}
    module.exports.one = 1 
    🔺

    🔺의 경우 코드가 컴파일이 안되거나 그러진 않지만 추천하지 않는다.

    다만 constructor function으로 쓰는 경우, 위 코드와 같이 연속적으로 두가지 constructor이 들어가는 것보다 하나만 들어가는 것이 가독성이 높기 때문이다.



3. 결론

결론적으로 정리해 보자면 다음과 같다.

  • require()는 module.exports를 리턴한다.

  • exports는 module.exports를 refer하고 있으며, shortcut에 불과하다.

  • exports와 module.exports는 용례를 익힐 필요가 있다.



4. 마무리

단순히 module.exports로 값을 보내고 require()함수로 값을 받을 줄 만 알았는데 이번 기회에 require()함수의 소스코드가 뭔지 exports라는 shortcut의 용도가 있었는지 그리고 가장 중요한 경우에 따른, 사용법과 주의할 점에 대해 배울 수 있었다.

항상 공부할 때마다 느끼는 거지만 원리에 대해 파악하는 것 만큼 중요한건 없다고 생각된다.


잘못된 점에 대한 지적과 조언은 언제든 환영입니다.

profile
front-end developer

0개의 댓글