이번 포스팅에서는 웹팩과 모듈시스템에 대해 더 구체적으로 정리해보려 한다.
그동안 다른 소스코드를 참고하면서 require 키워드를 많이 봐왔는데, 정확히 어떤 키워드이고 왜 쓰는지, import와 비슷한 역할을 하는 건데 무슨 차이인지, 그냥 Node.js의 문법일 뿐인지 잘 모르고 있었다.
마찬가지로 웹팩이 무엇인지 큰 개념만 알고 있을 뿐 어떻게 사용해야 하는지에 대해서는 아는 바가 없었는데,
이번에 새로운 프로젝트를 하면서 이 이슈와 직접적으로 맞닥뜨리게 되었는데, 이 기회에 평소 궁금하던 부분을 짚고 넘어가기로 했다.
CORS 이슈부터 웹팩 모듈 포맷 까지... 여러모로 시작을 고민했던 프로젝트였는데, 평소 궁금했거나 꼭 공부해야지 싶었던 부분을 직접 겪으며 공부할 수 있는 기회를 예상외로 많이 만나게 되어서, 시도 자체만으로 내겐 가치가 있는 프로젝트인 것 같다.
ts 파일에서 파이어베이스를 사용해 데이터베이스를 연동하고 싶었는데,
문제가 있었다.
cannot find module 'firebase/app' 오류가 떴다.
tsconfig에서 module 항목을 es2015에서 common js로 변경해주면 이 에러가 사라진다.
하지만 다른 오류가 뜬다.
Uncaught ReferenceError: exports is not defined
왜 이럴까??
일단, tsconfig 항목을 뜯어보자.
웹팩과 바벨 포스팅에서 모듈에 대해서 정리한 적이 있다.
요약하자면,
기존에는 html 파일에서 script 태그를 이용해 필요한 자바스크립트 파일들을 모두 불러와 사용했었는데, 여러 자바스크립트 파일이 의존적일 경우 로드하는 순서도 중요해졌을 뿐 아니라 용량이 커진다는 단점이 있었다.
이러한 단점을 보완하기 위해 모듈 시스템이 등장했는데,
외부에서 사용할 수 있도록 특정 함수나 오브젝트 등을 모듈화 하고, 해당 모듈을 사용하려는 쪽에서는 필요한 모듈만 불러와 사용할 수 있도록 한 것이다.
모듈을 정의하기 위한 문법을 모듈 포맷이라 한다.
여기에는 대표적으로 AMD, Common JS, ES6가 있는데, 처음 tsconfig 설정 시 접했던 ES6와 common JS에 대해 알아보았다.
CommonJS포맷은 2009년에 만들어진 Node.js의 표준이다.
자바스크립트 모듈을 만들기 위한 일종의 규칙으로 보면 된다.
내보낼 때는 exports / module.exports,
가져올 때는 require을 사용한다.
그러니까.. 처음에 궁금해했던 require는 NodeJS에서 사용되고 있는 CommonJS 키워드인 것이다. 반대로 import는 ES6(ES2015)에서 새롭게 도입된 키워드이다. 2
개는 모두 하나의 파일에서 다른 파일의 코드를 불러오는 동일한 역할을 한다.
//1. exports
function func1 (param) {
// ...
}
exports.func1 = func1
//2. module.exports
const obj = {
func1: function (num) {
// ...
},
func2: function (num) {
// ...
}
}
module.exports = obj
const obj = require('./func')
obj.func1(10)
obj.func2(20)
exports 와 module.exports의 차이점?
exports는 module.exports를 참조하고 있는 객체이다.
CommonJS 방식으로 모듈을 내보낼 때는 ES6처럼 명시적으로 선언하는 것이 아니라 특정 변수 혹은 그 변수의 속성으로 내보낼 객체를 세팅해주어야 한다
이 때 exports와 module.exports를 사용하는 경우가 조금씩 다르다.
const exchangeRate = 0.91;
function roundTwoDecimals(amount) {
return Math.round(amount * 100) / 100;
}
const canadianToUs = function (canadian) {
return roundTwoDecimals(canadian * exchangeRate);
};
function usToCanadian(us) {
return roundTwoDecimals(us / exchangeRate);
}
exports.canadianToUs = canadianToUs; // 내보내기 1
exports.usToCanadian = usToCanadian; // 내보내기 2
const exchangeRate = 0.91;
// 안 내보냄
function roundTwoDecimals(amount) {
return Math.round(amount * 100) / 100;
}
// 내보내기
const obj = {};
obj.canadianToUs = function (canadian) {
return roundTwoDecimals(canadian * exchangeRate);
};
obj.usToCanadian = function (us) {
return roundTwoDecimals(us / exchangeRate);
};
module.exports = obj;
정확히 맞는지는 모르겠지만, 일단 내가 파악한 바로는 해결책은 크게 2가지이다.
1. tsconfig.json에서 module 포맷을 commonJS를 사용할 것
2. tsconfig.json에서 moduleResulotion 설정을 node로 설정해줄 것
stackoverflow에서 제시했던 방법으로, 일단 왜 이렇게 하면 되는지 제대로 이해하지 못한 채로 무작정 시도해봤는데 firebase/app을 import할 수 없다는 에러를 없애는데는 성공했다.
하지만 이후 exports is not defined에러가 떴다.
//tsconfig.json
"moduleResolution": "node"
moduleResolution 항목은 모듈을 해석하는 전략을 설정하는 것인데,
이것을 Node로 설정해주면
Node.js에서 require()함수를 이용한 import를 해석하는 과정을 모방한다.
stackOverflow에서 보고 시도했던 방법인데,
아직 왜 commonJS를 사용했을 때 왜 이 항목을 설정해주면 exports is not defined에러가 해결되는지는 파악하지 못했다.
이후 실제 프로젝트에서는 ES6모듈 포맷을 사용했다.
앞에서 말했듯 common JS는 Node.js의 표준이다.
서버에서 요청해 로드하는 여러개의 파일의 의존성과 용량에 대한 문제를 해결하기 위해, 그리고
자바스크립트를 클라이언트 뿐 아니라 서버사이드에서도 범용적으로 사용하기 위해 commonJS와 AMD(Aysynchronous Module Definition)이 등장했고, Node.js의 경우 common JS를 채택해 사용했다.
브라우저에서는 자바스크립트를 모듈화하기 위해서 commonJS나 AMD를 기반으로 구현된 별도의 모듈 로더 라이브러리를 사용해야한다는 불편함이 있었는데, ES6부터는 브라우저 단에서도 쉽게 자바스크립트의 모듈화가 가능하도록 모듈 시스템이 추가되었다. 따라서 오늘날 많은 브라우저들은 ES6기반의 모듈 내보내기 및 불러오기 방식을 지원하고 있다.
그렇다면... 모듈 포맷은 굳이 commonJS를 사용할 필요 없이, ES6를 사용하면 될 것 같다.
<script>
태그에 type="module" 어트리뷰트를 추가해 모듈 시스템을 브라우저에서 사용한다.
이렇게 하면 그 안에 작성된 자바스크립트 코드들은 ES6기반의 모듈 내보내기 및 불러오기 방식을 지원하게 된다.
이 때 불러오는 파일이 모듈임을 명확히 하기 위해