안녕하세요. IT 엘도라도 에 오신 것을 환영합니다.
글을 쓰는 것은 귀찮지만 다시 찾아보는 것은 더 귀찮습니다.
완전한 나만의 것으로 만들기 위해 지식을 차곡차곡 저장해 보아요.   포스팅 둘러보기 ▼

자바스크립트 (JavaScript)

[JavaScript] 모듈 내보내기/불러오기 (CommonJS vs ES6)

피그브라더 2020. 8. 25. 15:53

1. JavaScript 모듈 시스템

파일 단위의 모듈화는 코드의 재활용성을 극대화시킴으로써 애플리케이션 개발의 생산성을 엄청나게 향상시킨다. 그런데 초창기 JavaScript는 모듈 시스템이라는 것을 갖추고 있지 않았다. 그나마 가지고 있는 것이라고는 <script> 태그를 통해 또 다른 JavaScript 소스 파일들을 서버에게 요청하는 방식이었는데, 각각의 <script> 태그에 의해 로드된 JavaScript 코드들은 사실상 하나의 파일 안에 작성된 것처럼 동작했다. 즉, 로드된 각 JavaSript 소스 파일마다 독립적인 파일 스코프를 가지고 있는 것이 아니라 하나의 전역 객체를 공유하는 방식이었던 것이다. 이로 인해 서로 다른 JavaScript 소스 파일에 존재하는 변수나 함수들이 같은 이름을 가짐으로써 충돌하는 문제들이 발생하기 매우 쉬웠다. 즉 모듈 시스템이란 것이 사실상 없었던 것이다.

 

이러한 배경에서 JavaScript를 클라이언트 사이드에서뿐 아니라 서버 사이드에서도 범용적으로 사용하기 위한 움직임까지 일어나면서, 모듈화는 JavaScript가 풀어야 할 핵심 과제가 되어 버렸다. 그래서 등장한 것이 바로 CommonJSAMD(Asynchronous Module Definition)이었다. 결국 JavaScript의 모듈화 방식은 크게 CommonJS와 AMD로 나뉘게 되었다. 이때 서버 사이드 JavaScript 런타임인 Node.js의 경우 모듈 시스템으로서 CommonJS를 채택하였다. 따라서 Node.js 환경에서는 모듈별로 독립적인 파일 스코프를 가진다는 것을 유추할 수 있다. 그리고 이와 같은 방식으로 브라우저에서 JavaScript를 모듈화 하기 위해서는 CommonJS 혹은 AMD를 기반으로 구현된 별도의 모듈 로더 라이브러리를 사용해야 했다. 이러한 상황에서, ES6부터는 브라우저 단에서도 쉽게 JavaScript의 모듈화가 가능하도록 모듈 시스템이 추가되었다. 따라서 오늘날 많은 브라우저들은 ES6 기반의 모듈 내보내기 및 불러오기 방식을 지원하고 있다.

 

* 이번 포스팅에서는 CommonJS 기반의 모듈 시스템과 ES6 기반의 모듈 시스템에 대해서만 알아볼 것이다.

 

2. CommonJS 기반의 모듈 내보내기 및 불러오기 (module.exports, require)

2-1. 개요

앞서 배경 설명에서 언급했듯이, Node.js 환경에서 실행되는 JavaScript는 모듈 시스템으로서 CommonJS 방식을 지원한다. 이 방식에서는 module.exports 객체를 이용하여 자신의 데이터를 외부로 내보낼 수 있고, require() 함수를 이용하여 외부 모듈의 데이터를 불러올 수 있다. 만약 Babel 등의 컴파일러를 사용한다면 뒤에서 설명할 ES6 기반의 모듈 내보내기 및 불러오기 방식을 사용해도 알아서 module.exports 객체 및 require() 함수 기반의 방식으로 변환될 것이다.


2-2. 모듈 내보내기 (module.exports)

자신의 데이터를 외부로 내보내려면 module.exports 변수에 내보내고자 하는 데이터들을 담은 객체를 지정해주면 된다. 그러면 이것을 불러오는 쪽에서는 해당 객체에 접근하여 내보내진 데이터들을 사용할 수 있다. 참고로 exports라는 변수도 존재하는데, 이는 module.exports 객체를 가리킨다. 따라서 module.exports의 프로퍼티를 수정하든 exports의 프로퍼티를 수정하든 효과는 같다. 그러나 exports 자체에 다른 값을 대입하는 것은 안 된다. 더 이상 module.exports 객체를 가리키지 않게 되기 때문이다. 실제로 내보내지는 것은 module.exports 객체이다.

module.exports = 128;

module.exports = { name: '홍길동', height: 180 };

exports.name = '홍길동';

exports = '홍길동';  // Not allowed

2-3. 모듈 불러오기 (require)

외부 모듈의 데이터를 불러오려면 require("경로") 함수의 반환 값을 변수에 대입하면 된다. require() 함수가 반환하는 것은 해당 모듈의 module.exports 객체이다.

const obj = require("./currency-object");  // obj는 module.exports 객체를 가리킨다.

 

3. ES6 기반의 모듈 내보내기 및 불러오기 (export, import)

3-1. 개요

앞서 배경 설명에서 언급했듯이, 이는 브라우저 단에서도 쉽게 JavaScript의 모듈화가 가능하도록 ES6부터 도입된 방식이다. 모듈화 시스템답게 각각의 모듈(파일)마다 독립적인 파일 스코프를 가지고 있어서, 모듈 내에 var로 선언한 변수는 더 이상 window 객체의 프로퍼티가 아닌 파일 스코프의 변수로 존재하게 된다. 즉 기본적으로는 다른 모듈의 데이터를 참조할 수 없기 때문에 충돌도 발생하지 않는다. 이때 다른 모듈의 데이터를 참조하거나 자신의 데이터를 노출시키고 싶을 때 사용하는 것이 바로 export, import 키워드이다. export 키워드로 자기 자신의 데이터를 외부로 내보낼 수 있고, import 키워드로 외부 모듈의 데이터를 불러올 수 있다.

 

이러한 모듈 시스템을 브라우저에서 사용하려면 <script> 태그에 type="module" 어트리뷰트를 추가해야 한다. 그러면 그 안에 작성된 JavaScript 코드들은 ES6 기반의 모듈 내보내기 및 불러오기 방식을 지원하게 된다. 이때 불러오는 파일이 모듈임을 명확히 하기 위해 <script type="module"> 태그로 불러오는 JavaScript 파일의 확장자는 mjs로 설정하도록 권장되고 있다.

 

ES6 기반의 모듈 시스템은 CommonJS 방식에 비해 코드의 직관성이 좋고, 비동기 방식으로 작동하면서 불러오는 모듈의 실제로 사용되는 부분들만 로드하기 때문에 성능적으로도 효율적이라고 할 수 있다. 그러나 이는 아래와 같은 단점들을 가지고 있어서 아직까지는 Webpack 등의 모듈 번들러를 이용하여 미리 의존성이 해결된 형태의 번들 JavaScript 파일을 제공하는 방식이 더 선호되는 경향이 있다.

 

  • IE(인터넷 익스플로러)를 포함한 몇몇 구형 브라우저는 ES6 모듈 시스템을 지원하지 않는다.
  • 브라우저의 ES6 모듈 시스템을 사용하더라도 어차피 트랜스파일링이나 번들링은 필요하다.
  • 아직 지원하지 않는 기능(Bare import 등)들이 꽤 있다. (ECMAScript modules in browsers 참고)
  • 점차 해결되고는 있지만 아직 몇 가지 이슈가 있다. (ECMAScript modules in browsers 참고)

 

Babel 및 Webpack의 간단한 동작 원리에 대해서는 이 포스팅을 참고하자. 단 이는 간단한 개념 설명일 뿐이니, 보다 자세한 내용은 다음 링크를 읽어보자. 개인적으로 감명 깊게 읽은 매우 매우 감사한 포스팅이다.

 

Babel | PoiemaWeb

현재 브라우저는 ES6를 완전하게 지원하지 않는다. ES6+(ES6 이상의 버전)를 사용하여 프로젝트를 진행하려면 ES6+로 작성된 코드를 IE를 포함한 모든 브라우저에서 문제 없이 동작시키기 위한 개발

poiemaweb.com

 

Webpack | PoiemaWeb

앞에서 테스트해 본 바와 같이 ES6 모듈을 현재의 브라우저에서 사용하려면 [RequireJS](http://requirejs.org/) 또는 [SystemJS](https://github.com/systemjs/systemjs)와 같은 모듈 로더가 필요하다. [Webpack](https://webpac

poiemaweb.com


2-2. 모듈 내보내기 (export)

  • Named Export : 정해진 이름으로 내보내기
export 변수/함수/클래스 선언문;

export { 변수명/함수명/클래스명 };

export { 변수명/함수명/클래스명 as 다른 이름 }; 
  • Default Export : 기본 내보내기 (이름을 정하지 않음. 최대 하나만 가능.)
export default 선언문 또는 값;

export { 변수명/함수명/클래스명 as default };

2-3. 모듈 불러오기 (import)

import A, { B, C } from 경로;  // A는 Default Export, B와 C는 Named Export

import { B as b, C as c } from 경로;  // 원하는 이름으로 로드

import * as obj from 경로;  // Export 된 모든 것들을 하나의 객체 형태로 로드 (불필요한 것도 가져오면 번들링 시 비효율을 야기)

import { default as A } from 경로;  // "import A from 경로"와 동일 (default : Default Export를 참조하는 용도로 사용하는 키워드)

 

3. 참고 : ReactJS에서 NPM 패키지 모듈 불러오기

앞서 말했듯 NPM 패키지 모듈들은 CommonJS를 기본 모듈 시스템으로 채택한다. 즉, 모듈을 내보내고 불러오는 것에 있어 require, module.exports 등을 사용한다는 말이다. 그러나 실제로 ReactJS 등의 라이브러리를 활용하여 프론트 엔드 개발을 할 때는 NPM 패키지 모듈을 불러오기 위해 ES6 문법의 코드를 작성하는 경우가 많다(import, export 등). 그런데 왜 문제가 발생하지 않을까? 이는 Babel 등의 컴파일러가 import, export 등의 코드를 CommonJS 기반의 코드로 변환해주기 때문이다. 그러고 나면 Webpack에 의해 JavaScript 모듈들의 번들링이 가능해진다. 변환 규칙은 대략 다음과 같다(실제로는 더 복잡할 수 있다).

// ES6
export { A };

// CommonJS
Object.defineProperty(exports, "__esModule", { value: true });
module.exports.A = A;
// ES6
export default A;

// CommonJS
Object.defineProperty(exports, "__esModule", { value: true });
module.exports.default = A;
// ES6
import * as A from "moduleA";

// CommonJS
var A = _interopRequireWildcard(require("moduleA"));
function _interopRequireWildcard(obj) {
    // obj가 ES6 모듈로부터 왔다면, obj를 그대로 반환한다.
    // 아니라면, obj의 프로퍼티들을 그대로 가지고 있으며
    // default 프로퍼티로는 obj 자체를 가리키는
    // 또 다른 객체를 만들어 반환한다.
    //
    // (tsc의 __importStar() 함수와 유사)
}
// ES6
import A from "moduleA";

// CommonJS
var A = _interopRequireDefault(require("moduleA")).default;
function _interopRequireDefault(obj) {
    // obj가 ES6 모듈로부터 왔다면, obj를 그대로 반환한다.
    // 아니라면, { default: obj }를 반환한다.
    //
    // (tsc의 __importDefault() 함수와 유사)
}
// ES6
import { A } from "moduleA";

// CommonJS
var moduleA = require("moduleA");
var A = moduleA.A;
Summary
ES6의 모듈 내보내기 코드를 CommonJS의 모듈 내보내기 코드로 변환할 때 디폴트 내보내기는 default 프로퍼티에 대응된다. 그리고 ES6의 모듈 불러오기 코드를 CommonJS의 모듈 불러오기 코드로 변환할 때의 핵심은, “ES6의 모듈 내보내기 코드는 내가 약속한 형태대로 올 것이니 그대로 믿고, CommonJS의 모듈 내보내기 코드는 믿지 않고 내가 약속한 형태가 되도록 가공하는 것”이다. 이때 주의해서 생각해봐야 하는 경우는 바로 스타(*) 불러오기와 디폴트 불러오기이다.

 

 

 

 

 

 

본 글은 아래 링크의 내용을 참고하여 학습한 내용을 나름대로 정리한 글임을 밝힙니다.

https://ko.javascript.info/import-export 

https://www.daleseo.com/js-module-require/ 

https://www.daleseo.com/js-module-import/ 

https://stackoverflow.com/questions/34278474/module-exports-and-es6-import

https://poiemaweb.com/es6-module