📌 이 포스터는 CommonJS vs. ES modules in Node.js
를 번역한 것입니다.
📌 잘못된 부분이 있으면 알려주세요
현대 sw개발에서, module은 소프트웨어 코드를 독립된 청크로 구성하고 청크들로 크고 복잡한 application을 구성합니다.
브라우저 JS 환경에서, JS 모듈은 import
, export
에 따라 EMCA Script modules(또는 ES modules)를 가져오기, 내보내기를 합니다.
ES module 형태는 재사용을 위한 JS 코드 패키징의 공식 표준 형식이고 대부분의 최신 웹 브라우저는 기본적으로 모듈을 지원합니다.
그러나, Node.js는 기본적으로 CommonJS 모듈을 지원합니다.
CommonJS 모듈은 require()
를 사용해서 가져오고 변수와 함수를 내보낼 때는 module.exports
를 사용합니다.
ES module 형태는 JS module system이 표준화되면서 Node.js v8.5.0에 도입되었습니다.
실험적인 모듈이기 떄문에 Node.js 환경에서 ES 모듈을 성공적으로 실행하려면 --experimental-modules
flag가 필요했습니다.
그러나, 13.2.0버전이 나오고부터, Node.js는 ES module을 안정적으로 지원할 수 있게 되었습니다.
이 글은 두 모듈 형태를 다 다루지는 못하지만 CommonJS와 ES 모듈과 비교해서 어떤 모듈이 왜 더 나은지에 대해 설명합니다.
기본적으로, Node.js는 CommonJS modules로 JS 코드를 다룹니다.
이 때문에, CommonJS 모듈은 module 가져오기를 위해서 require()
, 내보내기 위해 module.exports
사용하는 것이 특징입니다.
예를 들어, 두 함수를 내보내는 CommonJS module을 봅시다
module.exports.add = function(a, b) {
return a + b;
}
module.exports.subtract = function(a, b) {
return a - b;
}
또한, 아래와 같이 require()
를 사용해서 다른 Node.js sciprt에 있는 퍼블릭 함수를 가져올 수 있습니다.
const {add, subtract} = require('./util')
console.log(add(5, 5)) // 10
console.log(subtract(10, 5)) // 5
CommonJS에 대해 더 자세히 알고 싶다면 이것을 확인하세요.
반면에, 라이브러리 작성자는 파일 확장자를 .js
에서 .mjs
로 변경하여 Node.js 패키지에서 ES 모듈을 활성화할 수도 있습니다.
예를 들어, .mjs
확장자와 함께 public사용을 위해 두 함수를 내보내는 간단한 ES모듈을 봅시다.
// util.mjs
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
우리는 이제 두 함수를 import
를 사용해서 가져올 수 있습니다.
// app.mjs
import {add, subtract} from './util.mjs'
console.log(add(5, 5)) // 10
console.log(subtract(10, 5)) // 5
당신의 프로젝트에 ES modules을 가능하게 하는 다른 방법은 가장 가까운 package.json
파일 안에 "type: module"
을 추가하는 것입니다.
{
"name": "my-library",
"version": "1.0.0",
"type": "module",
// ...
}
이를 포함하면, Node.js는 해당 패키지 내의 모든 파일을 ES 모듈로 취급하므로 파일을 .mjs
확장자로 변경할 필요가 없습니다.
당신은 ES 모듈에 대해 여기서 더 배울 수 있습니다.
대안적으로, 당신은 Babel같은 트랜스파일러를 설치+설정을 통해 ES모듈을 CommonJS 문법으로 바꿀 수 있습니다
React나 Vue같은 프로젝트들은 코드 컴파일을 위해 내부에서 Babel을 사용하기 때문에 ES을 지원합니다.
ES module 형태는 JS module system을 표준화하기 위해서 만들어졌다. 재사용을 위해 JS코드를 캡슐화하는 표준형식이 되었습니다.
반면에 CommonJS module 시스템은 Node.js에 내장되어있다. Node.js에서의 Es module이 도입되기 전에는 CommonJS가 Node.js 모듈의 표준이었습니다.
결과적으로, 많은 양의 Node.js 라이브러리와 모듈은 CommonJS로 작성되어있다.
브라우저 지원에서, 모든 주요 브라우저들은 ES module문법을 지원하고 React와 Vue.js 같은 프레임워크에서 import/export
를 사용할 수 있다. 이 프레임워크들은 바벨과 같은 트랜스파일을 사용해서 import/export
를 이전 Node.js버전이 기본적으로 지원하는 require()
로 바꿔서 컴파일합니다.
JS 모듈의 표준이 되는 것 외에도 ES 모듈 문법은 require()
에 비해서 더 읽기 쉽습니다. 클라이언트에서 JS를 작성하는 웹 개발자는 동일한 문법 덕에 Node.js모듈을 사용하는데에 문제가 없습니다.
ES modules이 JS에서 표준 모듈 형태가 되었지만, 개발자들은 Node.js의 이전 버전이 지원하지 못하는 것을 고려해야한다.(Node.js v9부터 그 이전의 버전들)
즉, ES 모듈을 사용하면 CommonJS 모듈만 지원하는 이전 버전의 Node.js와 호환되지 않는 애플리케이션이 렌더링됩니다.(require()
)
그러나 새로운 조건부 export를 사용하여 dual-mode 라이브러리를 구축할 수 있습니다. 이 라이브러리들은 최신 ES 모듈로 구성된 라이브러리이지만 이전 Node.js 버전에서 지원하는 CommonJS 모듈 형식과도 이전 버전과 호환됩니다.
즉, import
와 require()
둘다 지원하는 라이브러리를 구축해서 비호환성 문제를 해결 할 수 있습니다.
다음과 같은 Node.js 프로젝트가 있다고 합시다.
my-node-library
├── lib/
│ ├── browser-lib.js (iife format)
│ ├── module-a.js (commonjs format)
│ ├── module-a.mjs (es6 module format)
│ └── private/
│ ├── module-b.js
│ └── module-b.mjs
├── package.json
└── …
package.json
내부에서, 우리는 exports
를 사용해서 public 모듈(module-a)을 두가지의 다른 모듈 형태로 내보내고 private 모듈(module-b)에 대한 접근을 제한할 수 있습니다.
// package.json
{
"name": "my-library",
"exports": {
".": {
"browser": {
"default": "./lib/browser-module.js"
}
},
"module-a": {
"import": "./lib/module-a.mjs"
"require": "./lib/module-a.js"
}
}
}
my-library
패키지에 대한 다음과 같은 정보가 주어지면, 우리는 이제 다음과 같이 지원되는 모든 곳에서 사용할 수 있습니다.
// For CommonJS
const moduleA = require('my-library/module-a')
// For ES6 Module
import moduleA from 'my-library/module-a'
// This will not work
const moduleA = require('my-library/lib/module-a')
import moduleA from 'my-awesome-lib/lib/public-module-a'
const moduleB = require('my-library/private/module-b')
import moduleB from 'my-library/private/module-b'
exports
경로 때문에 절대 경로를 지정하지 않고 public 모듈을 import(reuire()
)할 수 있습니다.
.js
와 .mjs
에 대한 경로를 포함시켜서 비호환성 문제를 해결할 수 있습니다.
우리는 private 모듈에 접근 제한을 하면서 브라우저와 Node.js와 같은 다른 환경에 대해 패키지 모듈을 매핑할 수 있습니다.
대부분의 Node.js의 낮은 버전에서 ES module은 실험용으로 표시됩니다. 이는 모듈이 몇몇 기능이 부족하고 --experimental-modules
flag 아래에 있다는 것을 뜻합니다.Node.js의 새로운 버전은 ES module을 안정적으로 지원합니다.
그러나, Node.js가 모듈을 ES모듈로 다루려면 다음 중 하나가 발생해야한다는 것을 기억해야합니다. 모듈의 파일 확장자가 .js(CommonJS의 경우)에서 .mjs(ES 모듈의 경우)로 변환되어야 합니다. 또는, 가장 가까운 package.json 파일에 {"type": "module"} 필드를 설정해야 합니다.
이 경우에 패키지에 있는 모든 코드는 ES modules로 취급되고 require()
대신 import/export
를 사용할 수 있습니다.
ES module에서, import는 파일의 맨 처음에서만 호출되어 사용할 수 있습니다. 어디서 호출하든 파일 맨 처음으로 자동으로 옮겨지거나 에러가 뜹니다.
반면에, require()
를 함수로 사용하면 런타임때 구문 분석이 됩니다.결과적으로 require()
는 코드 어디에서든 호출 할 수 있습니다. 이를 사용해서 if문,조건부 루프, 함수에서 조건적으로 또는 동적으로 모듈을 불러올 수 있습니다.
예를 들어, 다음과 같이 조건문에서 require()
를 사용할 수 있습니다.
if(user.length > 0){
const userDetails = require(‘./userDetails.js’);
// Do something ..
}
user가 존재할 때 userDetail 모듈을 불러올 수 있다.
require()
사용의 제한 중 하나는 동기적으로 모듈을 불러온다는 것이다. 이는 모듈이 불러와지고 실행되는게 각각(하나씩) 실행된다는 뜻이다.
짐작할 수 있듯이, 수백개의 모듈이 있는 대규모 어플리케이션의 경우에 몇가지 성능 문제를 발생할 수 있습니다. 이런 경우 import
가 비동기 동작에 근거해서 require()
보다 성능이 좋을 수 있습니다.
그러나, require()
의 동기적인 특성은 몇개의 모듈을 쓰는 작은 어플리케이션에서는 큰 문제가 안될것이다.
오래된 버전의 Node.js를 아직도 사용하고 있는 개발자들은 새로운 ES module을 사용하는 것은 실용적이지 못하다.
불확실한 지원 때문에 기존 프로젝트를 ES 모듈로 변환하면 CommonJS 모듈(즉,require()
)만 지원하는 이전 버전의 Node.js와 애플리케이션이 호환되지 않게 됩니다.
따라서, ES 모듈을 사용하도록 프로젝트를 바꾸는 것은 이점이 아닐 수 있습니다.
이제 막 시작한다면, ES 모듈이 점점 클라이언트 사이드(브라우저)와 서버 사이드(Node.js) 둘 다를 위해 JS모듈을 정의하는데 표준형태가 되고 있기 때문에 ES 모듈을 배우는 것이 유익하고 편할 수 있습니다.
새로운 Node.js projects인 경우 CommonJS의 대안으로 ES module을 제공한다.(CommonJS말고 ES module로 써봐라) ES 모듈 형태는 브라우저나 서버에서 둘 다 실행할 수 있는 동형 JS를 작성하는 더 쉬운 방법을 제공합니다.
결과적으로 EMCAScript modules이 자바스크립트의 미래입니다.
.
.
.
새로운 프로젝트에 commonJS로 작성할지 ES 모듈로 작성할지 고민이 되서 찾아봤는데, 이 아티클을 보고 ES 모듈을 사용해보기로 결정했다.