번들링을 하지 않고 우선 진행을 해보려고 하다 문제가 발생했다.
공식 홈페이지에서는 npm으로 mapbox-gl 설치 후, js 파일에서 아래와 같이 임포트하여 사용하라고 돼있었다.
import mapboxgl from 'mapbox-gl'; // or "const mapboxgl = require('mapbox-gl');"
그러나, 에러가 발생했다.

구글링을 해보며 온갖 짓을 다 시도해봤으나, 모두 실패했다.

원인을 제대로 파악하기 위해 수많은 자료에서 공통적으로 나오는 키워드인 '모듈'에 대해서 더 공부해보았다.
웹 개발 시 자바스크립트 파일을 1개로만 만든다면 어떨까?
프로젝트 규모가 커질수록 코드가 길어지고 유지보수가 어려워질 것이다.
따라서 파일을 기능에 따라 분리하여 여러 개의 JS 파일들로 구성하는데, 그 분리된 각각의 파일들을 모듈이라고 한다. 모듈은 필요한 곳에서 언제든 가져다 쓸 수 있고(재사용), 코드의 중복이 줄어드므로 유지 보수를 용이하게 해준다.
또다른 모듈의 중요한 특성은 모듈마다 각각의 네임스페이스를 갖는다는 것이다.
그런데 JS 파일들을 html에서 각각 script 태그를 이용해서 가져오게 되면 그 파일들은 모두 하나의 JS 파일처럼 인식된다. 즉, 각 파일 내 모든 변수는 전역 스코프를 갖게 되어 서로 변수명이 같을 경우 충돌이 발생한다.
<script src="src/a.js"></script>
<script src="src/b.js"></script>
브라우저에 JS 파일을 각각의 네임스페이스를 갖는 모듈로서 인식시켜주기 위해서는 아래와 같이 type="module" 속성을 추가해주어야 한다.
<script src="src/a.js"></script> <!--그냥 JS 파일-->
<script type="module" src="src/b.js"></script> <!--모듈-->
이렇게 html에서 모듈로 지정하는 것까지 완료했다.
그렇다면 모듈은 어떻게 가져다 쓸까?
가장 대표적인 모듈 시스템 ES Module과 Common JS 두 가지 방식을 알아보자.
ES Module은 자바스크립트 ES6부터 도입된 모듈 시스템이다. export를 통해 변수/함수를 내보내고, import를 통해 가져온다.
Named Export와 Default Export 두 가지 방식이 있다.
가져온 모듈의 변수(함수)명을 그대로 따라야 함. 여러 개 export 가능.
// 내보내는 쪽
const num = 10;
const func = () => {};
export { num, func };
// or
export const num = 10;
export const func = () => {};
// 가져오는 쪽
import { num, func } from "utils.js";
// or
import * as 마음대로 from "utils.js"
console.log(마음대로.num);
가져온 모듈의 변수(함수)명을 바꿀 수 있음. 파일 당 한 번만 export 할 수 있음. (여러 개의 변수, 함수를 내보내려면 객체로 묶어서 한번에 보내면 됨)
// 내보내는 쪽
export default function() {};
export default class {};
export default {...};
export default [...];
// or
const myFunc = () => {};
export default myFunc;
// 가져오는 쪽
import 내마음대로이름지어도됨 from "utils.js";
내마음대로이름지어도됨();
NodeJS 환경에서 사용하기 위해 개발된 모듈 시스템으로, 브라우저에서는 이 방식을 인식하지 못한다. (NodeJS 버전 13.2부터는 ES Module 방식도 Babel 같은 변환 도구 없이 사용 가능.)
exports나 module.exports를 통해 변수/함수를 내보내고, require를 통해 가져온다.
// 내보내는 쪽
exports.num = 10;
exports.func = () => {};
// or
const num = 10;
const func = () => {};
module.exports = { num, func };
// 가져오는 쪽
const { num, func } = require("./utils.js");
// or
const 마음대로 = require("./utils/js");
console.log(마음대로.num);
그렇다면 여기서 다시 처음 발생한 문제로 돌아가보자.
나는 mapbox-gl이라는 자바스크립트 라이브러리를 설치하였고 그 라이브러리를 index.js 파일에서 가져와서 사용하려고 했으나 ES Module 방식이든 CommonJS 방식이든 모두 실패했다.
하나씩 그 이유를 분석해보자.
const mapboxgl = require("mapbox-gl");

위 방식은 NodeJS에서 사용하는 CommonJS 방식으로, 브라우저에서는 인식하지 못한다. 나는 NodeJS 서버로 웹앱을 띄워주는 게 아니라 live-server를 통해 바로 브라우저에 띄우기 때문에 브라우저에서 인식 가능한 ES Module 방식을 사용해야 한다.
import mapboxgl from 'mapbox-gl';

ES Module 방식으로 모듈을 가져왔음에도 위와 같은 에러가 뜬 이유는 html에서 해당 스크립트를 가져올 때 type="module"을 추가하지 않았기 때문이다. 🔗
즉 해당 스크립트 파일을 모듈로서 인식하지 않기 때문에 import문을 사용할 수 없다는 에러이다.
import mapboxgl from 'mapbox-gl';

스크립트에 type="module"을 추가하여 모듈로 인식해줬더니 이번에는 이런 에러가 발생했다.
공식 홈페이지에 있던 코드를 그대로 가져온 것인데, 경로 없이 라이브러리 이름만 가지고 import 하도록 되어있다. 그러나 이것은 이전 게시글에서 봤듯이 모듈 번들러를 사용한다는 가정 하에 가능한 것이다. 왜냐하면 브라우저에서는 반드시 import 하는 모듈이 절대 또는 상대경로로 돼 있어야 하기 때문이다.
모듈 번들러는 라이브러리 이름만 보고도 어떤 모듈인지 찾아내어 브라우저에서 인식 가능하게 번들링 해주기 때문에 가능하다. (아마도 import 하는 모듈에서 필요한 부분만 가져와 합치고 import 문은 없애는 것 같다)
import mapboxgl from "../node_modules/mapbox-gl/dist/mapbox-gl.js";

위와 같이 경로를 직접적으로 입력해주었음에도, 에러가 발생했다.
해당 경로의 파일에 default export가 없다는 말인 것 같다. 혹시나 하여 named로도 import 해보았지만 'mapboxgl'이라는 이름의 named export가 없다는 에러가 떴다. (mapbox-gl.js 를 열어보니 이것 자체도 번들링되어 있었다.)
결국은 공식 홈페이지 말대로 (그냥 좀 믿어) 반드시 번들러를 써야만 사용할 수 있는 것 같다.
나의 경우에는 외부 라이브러리를 사용하기 위해 번들러 사용이 불가피했지만 그 외에도 다른 필요성들을 정리해보면 다음과 같다.
모듈 시스템에서 프로젝트의 규모가 커져 모듈의 개수가 매우 많아지면 어떻게 될까? 각 파일마다 브라우저에서 서버로 네트워크 요청이 발생하므로 매우 비효율적이며 초기 로딩 속도에 지장을 줄 수 있다.
-> 모듈 번들러 사용 시, 여러 개의 모듈을 하나(또는 적은 수)로 묶어주기 때문에 네트워크 요청 횟수가 줄어든다.
외부 라이브러리를 사용할 경우, 라이브러리 용량 자체가 크기 때문에 서버에 그대로 배포하는 것이 비효율적일 수 있다.
-> 모듈 번들러 사용 시, 불필요한 것들을 모두 쳐내어 용량도 줄여준다.
마지막으로 모듈 번들러를 사용하면 위에서 본 것처럼 모듈을 어떻게 가져오고 내보낼지 복잡하게 이것저것 신경 쓸 필요가 없어진다. 라이브러리를 사용할 때는 그냥 라이브러리 이름만 가지고 import 하면 된다 !
정리하면, 개발이 편리한 모듈 시스템을 유지하면서도 필요한 부분만 뭉쳐서 효율적으로 배포 및 실행할 수 있게 Webpack, Parcel, Rollup 같은 모듈 번들러를 사용하게 된 것이다. 요즘은 웹 개발에서 번들러를 사용하지 않는 경우는 거의 없는 것 같다.
지금까지 여러 웹앱 프로젝트를 진행하면서는 번들러가 자체 내장된 리액트나 넥스트를 사용했기에 별 생각없이 번들러를 사용해왔었는데, 이렇게 바닐라 자바스크립트 프로젝트를 진행하다보니 그 기능과 필요성을 직접 이해하게 되어 아주 유익했다.