기본적으로 JS는 약간의 상호작용만을 위해 만들어진 간단한 언어이므로 큰 스크립트가 필요하지 않았으나, 프로젝트가 커지면서 모듈 분할을 위한 메커니즘이 필요하게 되었다.
따라서 모던JS에서는 모듈화를 위한 import/export 기능을 제공한다. 이것을 통한 모듈을 ESM(Ecma Script Module)이라고 한다.
하지만 NodeJS가 사용하는 CommonJS(CJS)에서 사용하는 모듈 생태계를 위한 시스템은 이미 존재하고 있었으며 이것은 require과 exports로 import/export의 그것과는 다르다.
테스트 모듈인 JEST의 경우 @jest/globals 모듈등의 존재로 import/export를 지원하기는 하나, 공식 문서에서 mock(테스트 대상이 아니나 테스트를 위해 필요한 가상 유닛)등을 완전하게 사용할 수 없다고 기재되어 있다.
(공식 문서 번역) ESM과 CommonJS의 차이점
JSET 공식 문서
차이점의 대부분은 Node의 문서에 설명되어 있지만 거기에 언급된 것 외에도 Jest는 실행되는 모든 파일에 특수 변수(jest)를 삽입합니다 . ESM에서 이 개체에 액세스하려면@jest/globals모듈 에서 가져와야 합니다 .
현재 jest.mock은 ESM에서 깔끔한 방식으로 지원하지 않지만 향후 적절한 지원을 추가할 예정입니다.
다음과 같은 질문을 통해 ESM의 모듈을 자주 사용하는지에 대한 의견을 살펴볼 수 있다.
ESM에 대해 긍정적으로 보이지 않는 반응들
따라서 당장은 ESM보다는 CommonJS를 통하는 방법이 효율적이라 볼 수 있다. 따라서 exports/require에 관하여 기재하는 것이 당장 지금까지의 모듈을 사용하는 것에 있어서는 옳다.
CommonJS(CJS)은Node.js에서 사용하는모듈 생태계에 대한 규칙을 수립하는 것을 목표로 하는 프로젝트이다.
보통은Node.js를 사용한서버측 JavaScript프로그래밍에 널리 사용된다.브라우저측 JavaScript개발에도 사용되긴 하지만,브라우저는CommonJS를 지원하지 않아서, 코드를트랜스파일러(모던ES코드를 오래된 브라우저도 사용할 수 있게 예전 버전의 ES코드로 바꾸어준다.Babel이 유명하다.)로 패키징해야 한다.
단, 이것은 이전까지의 이야기이지 현재의 이야기가 아니다.
ESM는 [ES 공식]이기 때문에 그 힘은 매우 강력하며, 점점 더 많은 module들이 ESM를 지원하고 있다.
Three.js처럼 ESM만을 지원하는 모듈도 있기 때문에, ESM을 기본으로 보는 것이 현재로서는 조금 더 옳다.
다음과 같이 사용한다.
//모듈들의 Parent. exports에 넣지 않았으므로 이걸 직접 사용할 수는 없다.
class BaseModule {
constructor(moduletype){
this.moduletype = moduletype;
}
printModuleType(){
console.log(this.moduletype);
}
}
//exports에 FirstModule이라는 이름으로
//이 파일 내의 'MyFirstModule' 클래스를 export한다.
exports.FirstModule = class MyFirstModule extends BaseModule{
constructor(name){
super("FIRST");
this.name = name;
}
printLowerName(){
console.log(this.name.toLowerCase());
}
}
//exports에 SecondModule이라는 이름으로
//이 파일 내의 'MySecondModule' 클래스를 export한다.
exports.SecondModule = class MySecondModule extends BaseModule{
constructor(name){
super("SECOND");
this.name = name;
}
printUpperName(){
console.log(this.name.toUpperCase());
}
}
const { FirstModule, SecondModule } = require("./first_module")
//exports에 FirstModule이라고 적어놓았으니
//MyFirstModule이 아닌 FirstModule로 사용할 수 있다.
local_firstmodule = new FirstModule("my FIRST module");
//exports에 SecondModule이라고 적어놓았으니
//MySecondModule이 아닌 SecondModule로 사용할 수 있다.
local_secondmodule = new SecondModule("my SECOND module");
local_firstmodule.printModuleType(); //FIRST
local_firstmodule.printLowerName(); //my first module
local_secondmodule.printModuleType(); //SECOND
local_secondmodule.printUpperName(); //MY SECOND MODULE
const { FirstModule } = require("../first_module")
test('First Module', () => {
let modulename = "my FIRST module"
let test_firstmodule = new FirstModule(modulename);
expect(test_firstmodule.moduletype === "FIRST").toBe(true);
expect(test_firstmodule.moduletype === "SECOND").toBe(false);
expect(test_firstmodule.name === modulename).toBe(true);
});
//result : PASS
const { SecondModule } = require("../first_module")
test('Second Module', () => {
let modulename = "my SECOND module"
let test_secondmodule = new SecondModule(modulename);
expect(test_secondmodule.moduletype === "FIRST").toBe(false);
expect(test_secondmodule.moduletype === "SECOND").toBe(true);
expect(test_secondmodule.name === modulename).toBe(true);
});
//result : PASS