포스트

노드 학습 3일차

ECMAScript 모듈 vs CommonJS

ECMA에서 정한 표준 모듈 사용법이라 할 수 있습니다.
CommonJS 와 문법적으로도 차이가 있으며 근래에는 ECMAScript 가 자바스크립트의 표준 문법으로 널리 사용되고 있습니다.

차이점CommonJSECMAScript
문법require(‘./a’);
module.exports = A
const A = require(‘./a’);
exports.C = D;
const E = F; exports.E = E;
const {C,E} = require(‘./b’)
import ‘./a.mjs’
export default A;
import A from ‘./a.mjs’;
export const C = D;
const E = F; export {E};
import {C,E} from ‘./b.mjs’
확장자js
cjs
mjs
확장자 생략가능불가능
다이나믹 임포트가능불가능
인덱스 생략가능불가능
top level await불가능가능
__filename,
__dirname ,
require,
module.exports,
exports
사용 가능사용 불가능
(__filename 대신 import.meta.url 사용)
서로 간 호출가능가능

확장자 비교

CommonJS 확장자 .cjs

일반적인 자바스크립트 파일은 .js 확장자를 가집니다. CommonJS 의 확장자는 .cjs 를 사용합니다. 하지만 CommonJS 의 확장자는 .js 로 생략이 가능하며 일반적으로 사용하는 .js 파일은 전부 CommonJS 파일이라고 볼 수 있습니다.
ECMAScript.mjs 확장자 파일과 구분짓기 위해 명시적으로 .cjs 라고 사용하기도 합니다.

ECAMScript 모듈의 확장자 .mjs

ECMAScript 의 확장자는 .mjs 를 사용합니다. 이는 별도의 설정이 없다면 반드시 지켜야 합니다.

모듈화 비교

CommonJS

내보내기

ECMAScript 모듈처럼 named exportdefault export 가 구분되어 있지 않으며 기능적으로 이와 유사한 기능을 하도록 사용할 수 있습니다.
exports에 값을 그대로 담는 방법을 통해 default export 와 유사한 기능을 구현할 수 있습니다. 그리고 exports 에 객체를 담는 방법을 통해 named export 와 유사한 기능을 구현할 수 있습니다.

객체 형식으로 exports 하는 예시

1
2
3
const foo = "hello :)";
const bar = "bye :("
module.exports = {foo, bar};

값을 exports 하는 예시

1
2
const foobar = "Hello world";
module.exports = foobar;
불러오기

CommonJS 에서는 모듈을 불러올 때 require() 를 사용합니다. 객체 형식으로 exports 한 경우에는 구조 분해 할당을 사용 할 수 도 있습니다.

1
2
3
4
const helloWorld require("파일경로");

// 이 경우 객체의 속성에 맞는 이름을 사용해야합니다.
const {foo, bar} require('파일경로'); 

ECMAScript

내보내기

ECMAScript 에서는 모듈을 내보낼 때 named exportdefault export 를 구분 지어 사용합니다.

named export 예제

1
2
export const foo = "foo";
export const bar = "bar";

default export 예제

1
2
3
4
5
const sayHello(){
	console.log("Hello!");
}

export default sayHello;
불러오기

import ... from 문법을 사용합니다.
별도의 설정이 없는 경우 확장자까지 명시합니다.
named export 한 모듈과 default export 한 모듈의 불러오는 방법이 다릅니다. named export 모듈의 경우 변수명에 맞게 import 해야하며 default export 모듈은 변수명과 무관하게 import 할 수 있습니다.

1
import foo from "파일.mjs";

다이나믹 임포트

다이나믹 임포트는 조건에 따라 임포트를 하는 것입니다.
CommonJS에서는 사용이 가능합니다.

1
2
3
4
const a = true;
if(a){
	require('./b');
}

위의 예제와 같이 조건에 따라 require 하는 것을 다이나믹 임포트라 합니다.

하지만 ECMAScript에서 import 는 일반적으로 지원되지 않으며 이를 구현하기 위해서는 import 키워드가 아닌 import() 함수를 실행하여 사용합니다.

1
2
3
4
5
6
const a = true;

if(a){
	const b = await import('./b.mjs');
	const c = await import('./c.mjs');
}

module.exports vs export default

module.exportsexport default 는 언뜻 기능이 같은 것으로 보입니다. 하지만 내부적으로 이는 조금 다른 구조를 가집니다.

hello! 라는 문자열을 내보내는 두 가지의 경우를 비교해보겠습니다.

module.exports

이 경우에는 코드를 천천히 보면

1
2
3
const 변수 = "안녕";
module.exports = 변수;
console.log(module);

이러한 형식을 가집니다. 곧 moduleexports 의 값이 곧 뒤에 오는 값을 그대로 가지게 됨을 의미합니다. 그렇기 때문에 위의 경우 로그에는 다음과 같이 나오게 됩니다.

1
2
3
4
5
6
7
8
9
Module {
  id: '.',
  path: 'C:\\', // 파일 경로
  exports: 'hello!', // module.exports 에 저장한 값
  filename: 'C:\\moduleExportsTest.js', // 파일명
  loaded: false,
  children: [],
  paths: [ 'C:\\node_modules' ]
}

즉 CommonJS에서 모듈을 받기 위해 사용하는 require() 은 Module 객체의 exports 속성의 값을 가져온다는 것을 알 수 있습니다.

export default

export default 의 경우를 로그로 남겨보기 위해 다음의 모듈을 만들어봤습니다.

1
export default "hello";

이제 이 모듈을 로그에 남겨보겠습니다.

1
2
import * as moduleObj from './exportDefaultTest.mjs';
console.log(moduleObj);

이 경우 로그에는 다음과 같이 남습니다.

1
[Module: null prototype] { default: 'hello' }

Module: null prototype 구문은 프로토타입에 대한 로그이니 여기서는 생략하겠습니다.
바로 다음을 보면 default에 데이터가 담겨있는 것을 알 수 있습니다.
이 부분이 차이입니다. module.exportsexport default 가 완전히 동일한 것은 아님을 알고 넘어가면 좋겠습니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.