pnpm init
명령어 실행 후 index.js
파일에서 작업을 하다 보면 라이브러리나 다른 파일을 import
를 했을 때 package.json
의 type
을 module
로 설정하라는 오류가 뜬다.
오늘은 package.json
에서 type
의 값에 따라 모듈 시스템을 어떻게 인식하는지, 그리고 각각의 차이점은 무엇인지에 대해 알아보겠다. 시간이 없다면 요약만 봐도 좋다.
{
"type": "commonjs"
}
index.js
package.json
package.json
에 있는 type
의 기본 값은 commonjs
이다. 이때는 CommonJS 모듈 시스템으로 인식하고 파일명을 index.js로 지어도 index.cjs로 인식
하게 된다.
반면 type
의 값을 module
로 설정하게 되면 ESM 모듈 시스템으로 인식하고 파일명을 index.mjs
로 인식하게 된다.
그렇다면 CommonJS와 ESM의 동작 방식은 어떨까?
// math.cjs
module.exports.math = {
add(a, b) {
return a + b;
},
};
// index.cjs
const { math } = require("./math.cjs");
console.log(math.add(1, 2)); // 3
require
를 사용하여 모듈을 불러오고 module.exports
를 이용하여 모듈을 내보낸다.
또한 동적으로 모듈을 가져올 수 있다.
// add.cjs
module.exports = {
add(a, b) {
return a + b;
},
};
// index.cjs
const fileName = "add.cjs";
const { add } = require(`./${fileName}`);
console.log(add(1, 2)); // 3
이는 CJS가 런타임에서 모듈 관계를 파악하기 때문이다.
// math.mjs
export function add(a, b) {
return a + b;
}
// index.mjs
import { add } from "./math.mjs";
console.log(add(1, 2));
import
를 사용하여 모듈을 불러오고 export
를 이용하여 모듈을 내보낸다.
CJS와는 다르게 ESM은 컴파일 타임에 분석(정적 분석)되기 때문에 동적으로 모듈을 가져오지 못한다. 하지만 정적 분석을 하기 때문에 Tree-shaking을 할 수 있어 자바스크립트 번들의 크기는 줄어든다.
또한 ESM에서는 CJS를 import 할 수 있지만 CJS에서는 ESM을 require 할 수 없다. 이는 ESM만 Top-level Await를 지원하기 때문이다.
분석 시점 | Tree-shaking 여부 | 동작 | 모듈 가져오기/내보내기 | |
---|---|---|---|---|
CJS | 런타임 타임 | O | 동기 | require/module.exports |
ESM | 컴파일 타임 | X | 비동기 | import/export |
오류가 나면 그냥 무심코 package.json
의 type
을 지정해 주고 넘어갔었는데, 이번 기회에 둘의 차이에 대해 공부해 보는 계기가 되어 좋았다. 토스의 글이 정말 잘 정리되어 있으니 한 번씩 읽어보는 것을 추천한다.