타입스크립트에는 esmoduleinterop라는 컴파일러 옵션이 있는데 이 옵션을 켜면 Commonjs방식으로 내보낸 모듈을 es모듈 방식의 import로 가져올 수 있게 해준다.
모듈 방식이 왜이렇게 많아?
ES6에 정식으로 자바스크립트에 모듈 시스템이 도입 되기 전부터 사람들은 자바스크립트 파일을 여러개로 쪼개고 다시 합치는 번들러가 필요했다. 그래서 직접 라이브러리를 만들어서 사용하곤 했는데 그때 나온 유명한 모듈 시스템들이 CommonJS와 AMD이다. 그래서 ES6에 모듈 시스템이 도입되고 나서도 구형 브라우저를 지원하기 위해 바벨로 ES6 -> ES5로 변환하고 난 다음, ES모듈을 CommonJS방식으로 트랜스파일해서 기존 번들러가 잘 동작하도록 변환하여 사용했었다. 이미 CommonJS방식에 맞춰 개발된 번들러를 ES6에도 동작하도록 변경하는것보다 기존 모듈들을 CommonJS방식에 맞춰 변경하는것이 더 간단했나보다. 결국에 이렇게 모듈 방식이 많아진 문제의 원인은 자바스크립트에 표준 모듈 시스템이 너무 늦게 출현한것 때문이다.
그러니까 최종 결과물은 최신 문법이 섞이지 않은 CommonJS방식의 자바스크립트 모듈들을 웹팩과 같은 번들러로 합쳐서 서비스 했왔었던것이다.
CommonJS는 module.exports 객체를 치환하는 방식으로 모듈을 내보내고 require이라고 하는 키워드로 모듈을 가져온다.
반면, ES모듈 방식은 잘 알다시피 import와 export(named, default)로 모듈을 가져오고 내보낸다.
서로 비슷하듯 다르다.
그동안 왜 아무런 에러가 없이 잘 사용했지?
프로젝트를 하다보면 node_modules에 있는 외부 모듈을 ES모듈 방식인 import로 잘 가져왔었다. 전혀 에러가 나지 않았다. 그 이유는 웹팩이 CommonJS와 ES모듈을 섞어 쓸 수 있도록 추가 처리를 해주기 때문이다.
예제코드(출처)
import bold, {boldTagName} from './bold';
import italic, {italicTagName} from './italic';let isBold = true;export function sayHello(name) {
const formatter = isBold ? bold : italic; isBold = !isBold; return `Hello! ${formatter(name)}!`;
}
여기서 bold와 italic 두가지 모듈을 es모듈 방식으로 가져오고 있다.
bold는 commonjs 방식으로 내보내졌다.
module.exports = function(name) {
return '<b>' + name + '</b>';
};
module.exports.boldTagName = 'b'
italic은 es모듈 방식으로 내보내졌다.
export const italicTagName = 'i';
export default function(name) {
return '<i>' + name + '</i>';
}
하지만 두 모듈을 불러올때는 둘다 es모듈 방식으로 import하고 있다.
import bold, {boldTagName} from './bold';
import italic, {italicTagName} from './italic';
그런데 전혀 에러 없이 두 모듈을 잘 불러오고 있다. 웹팩이 도대체 어떤 처리를 해주길래 가능한걸까?
번들된 코드
{
"./src/index.js": (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "sayHello", function() { return sayHello; });
/* harmony import */ var _bold__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./bold */ "./src/bold.js");
/* harmony import */ var _bold__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_bold__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _italic__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./italic */ "./src/italic.js");
웹팩이 번들링한 코드를 좀 더 자세히 들여다 보자.
var _bold__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/bold.js");
var _bold__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(_bold__WEBPACK_IMPORTED_MODULE_0__);
웹팩의 보일러 플레이트 코드를 제거하고 깔끔하게 코드를 다시 한번 살펴보자.
bold 모듈은 commonjs방식으로 내보낸뒤, es모듈 방식으로 default import되었다는 사실을 상기하자.
여기서 진짜 중요한 부분이 나온다.
웹팩은 두가지 모듈 방식을 서로 호환시키기 위해서 추가 작업을 처리한다. 그부분은 아래와 같다.
var _bold__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(“./src/bold.js”);
var _bold__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(_bold__WEBPACK_IMPORTED_MODULE_0__);
유심히 보아야 할곳을 볼드 처리 해봤다. 자세히 보면, bold 모듈이 commonjs 방식으로 내보내졌기 때문에 __webpack_require__.n이라고 하는 함수로 모듈을 한번더 감싼다음 새로운 변수명(_bold__WEBPACK_IMPORTED_MODULE_0___default)에 그 모듈을 다시 할당했다.
이런식으로 서로 다른 모듈방식을 웹팩만의 하나의 방식으로 통일한것이다.
이후 코드라인에서는 웹팩은 _bold__WEBPACK_IMPORTED_MODULE_0___default 변수에 담긴 bold 모듈을 사용할것이다.
__webpack_require__n함수는 매개변수로 전달된 모듈을 a라는 속성에 저장한다.
const __webpack_require__ = (저장할 모듈) => {
const n = () => {
a = 저장할 모듈;
return {
a;
}
}
};
아마도 webpack의 require 함수는 위와 같이 구현되어 있지 않을까 추측해본다.
따라서 웹팩은 require.n함수로 저장한 모듈을 다음과 같이 꺼내서 사용한다.
_bold__WEBPACK_IMPORTED_MODULE_0___default.a
위에서 선언했던 sayHello 함수는 원래 이런 코드였는데
function sayHello(name) {
const formatter = isBold ? bold : italic; isBold = !isBold; return `Hello! ${formatter(name)}!`;
}
여기서 아래 부분이
const formatter = isBold ? bold : italic;
이렇게 변경된다.
const formatter = isBold ?
_bold__WEBPACK_IMPORTED_MODULE_0___default.a
:_italic__WEBPACK_IMPORTED_MODULE_1__["default"];
이런식으로 _bold__WEBPACK_IMPORTED_MODULE_0___default.a 로 모듈을 가져오고 있는걸 확인할 수 있다.