1. 배경
문법 수준에서 모듈을 지원하기 시작한 것은 ES2015부터입니다.
import / export 구문이 없던 모듈 이전 상황을 살펴보면 웹팩의 등장 배경을 이해하는데 수월할 것입니다.
// math.js
function sum(a,b){
return a + b;
}
// app.js
sum(1, 2);
- 위 코드는 모두 하나의 HTML 파일 안에서 로딩해야만 실행됩니다.
- math.js가 로딩되면 app.js는 이름 공간에서 ‘sum’을 찾은 뒤 이 함수를 실행합니다.
- 문제는 ‘sum’이 전역 공간에 노출된다는 것입니다. 즉, 다른 파일에서도 ‘sum’이란 이름을 사용한다면 충돌이 일어납니다.
1.1 IIFE 방식의 모듈
- 이러한 문제를 예방하기 위해 스코프를 사용합니다.
- 함수 스코프를 만들어 외부에서 안으로 접근하지 못하도록 공간을 격리하는 것입니다.
var math = math || {} //math의 네임스페이스
(function(){
function sum(a, b){
return a + b;
}
math.sum = sum
})()
- 코드를 즉시 실행함수로 감쌌기 때문에 다른 파일에서 이 안으로 접근할 수 없으며, 동일한 파일일지라도 접근 불가합니다.
- 이는 자바스크립트의 함수 스코프의 특징입니다. ‘sum’이란 이름은 즉시실행함수 안에 감추어졌기 때문에 외부에서는 같은 이름을 사용해도 괜찮습니다.
- 전역에 등록한 ‘math’라는 이름 공간만 잘 활용하면 됩니다.
1.2 다양한 모듈 스펙
- 이러한 방식으로 자바스크립트 모듈을 구현한 대표적 명세가 AMD와 CommonJS입니다.
1.2.1 CommonJS
- CommonJS는 자바스크립트를 사용하는 모든 환경에서 모듈을 사용하는 것이 목표입니다.
- exports 키워드로 모듈을 만들고 require() 함수로 불러들이는 방식입니다.
- 대표적으로 Node.js에서 이를 사용합니다.
// math.js
exports function sum(a, b) { return a + b; }
// app.js
const math = require('./math.js');
math.sum(1, 2); // 3
1.2.2 AMD
- AMD(Asynchronous Module Definition)은 비동기로 로딩되는 환경에서 모듈을 사용하는 것이 목표이다.
1.2.2 UMD
- UMD(Universal Module Definition)는 AMD기반으로 CommonJS 방식까지 지원하는 통합 형태입니다.
이렇게 각 커뮤니티에서 각자의 스펙을 제안하다 ES2015에서 표준 모듈 시스템을 내놓았습니다.
지금은 웹팩과 바벨을 이용해 모듈 시스템을 사용하는 것이 일반적입니다.
1.2.3 ES2015 모듈 시스템
// math.js
export function sum(a, b){
return a + b;
}
// app.js
import * as math from './math.js'
math.sum(1, 2);
- export 구문을 모듈로 만들고 import 구문으로 가져올 수 있습니다.
1.3 브라우저의 모듈 지원
- 안타깝게도 모든 브라우저에서 모듈 시스템을 지원하진 않습니다.
- IE를 포함한 몇 브라우저에서는 여전히 모듈을 사용하지 못합니다.
- 가장 많이 사용하는 크롬브라우저를 보자면 type=”text/javascript” 대신 type=”module”을 사용합니다.
- 즉, app.js는 모듈을 사용할 수 있습니다.
- <script type="module" src="app.js"></script>
- 그러나 위에서 이야기 했던 것처럼 IE등 몇몇 브라우저는 모듈 사용이 불가합니다.
- 이제 웹팩이 나올 차례입니다.
2. 엔트리/아웃풋
- 웹팩은 여러 개의 파일을 하나로 합쳐주는 번들러입니다.
- 하나의 시작점 (entry point)로부터 의존적인 모듈을 전부 찾아내서 하나의 결과물을 만들어냅니다.
- app.js부터 시작해 math.js 파일을 찾은 뒤 하나의 파일로 만드는 방식입니다.
2.1 웹팩으로 번들링 해보자
1. webpack-test 디렉터리를 만들고 그 안에서 예제를 실행하도록 하겠습니다.
$ mkdir webpack-test
$ cd webpack-test
2. HTML 파일 작성하기
- index.html
// index.html
<!DOCTYPE html>
<html>
<head>
<title>Webpack Example</title>
</head>
<body>
<script type="text/javascript">
function currentTime() {
const date = new Date();
const hours = date.getHours();
const minutes = date.getMinutes();
const seconds = date.getSeconds();
return `${hours}:${minutes}:${seconds}`;
}
const div = document.createElement("div");
document.body.appendChild(div);
setInterval(() => (div.innerText = currentTime()), 1000);
</script>
</body>
</html>
브라우저 화면
- 위 html 파일을 liveserver을 이용해 열어본다면 1초마다 화면을 업데이트해 현재 시각을 나타내 보여줍니다.
3. HTML / JavaScript 분리하기
- HTML 태그와 JavaScript 코드를 하나의 파일에 섞는 것은 좋은 코드가 아닙니다.
- 따라서 index.html 파일에서 Javascript 코드만 뽑아내어 index.js 파일을 만들고 이 둘을 script 태그를 활용해 연결합니다.
- Index.html
<!DOCTYPE html>
<html>
<head>
<title>Webpack Example</title>
</head>
<body>
<script src="./index.js"></script>
</body>
</html>
- Index.js
function currentTime() {
const date = new Date();
const hours = date.getHours();
const minutes = date.getMinutes();
const seconds = date.getSeconds();
return `${hours}:${minutes}:${seconds}`;
}
const div = document.createElement("div");
document.body.appendChild(div);
setInterval(() => (div.innerText = currentTime()), 1000);
4. 외부라이브러리 사용하기
- Moment.js라는 라이브러리를 사용해 위 코드를 다시 작성해보겠습니다.
- index.html 파일에는 Moment.js의 소스파일에 대한 CDN 링크를 추가했고, index.js 파일에는 currentTime() 함수 대신 Moment.js의 format() 함수를 호출하는 1줄짜리 코드로 변경했습니다.
- index.html
<!DOCTYPE html>
<html>
<head>
<title>Webpack Example</title>
<script src="https://unpkg.com/moment@2.22.2/moment.js"></script>
</head>
<body>
<script src="./index.js"></script>
</body>
</html>
- index.js
function currentTime() {
return moment().format("H:m:s");
}
const div = document.createElement("div");
document.body.appendChild(div);
setInterval(() => (div.innerText = currentTime()), 1000);
이것이 전통적으로 웹사이트에서 외부 자바스크립트 라이브러리를 사용하는 전형적인 방법입니다.
이러한 상황에서 웹에서도 자바스크립트를 통한 자체적인 외부 모듈 import 기능이 절실해지는 시점입니다.하지만 여기는 여러 단점이 있습니다.
- Javascript 파일이 HTML파일로부터 완전히 독립할 수 없다.
- 여전히 HTML 파일에서 외부 자바스크립트 라이브러리에 대한 의존성이 관리되고 있기 때문에 정작 Javascript 파일은 HTML 파일 없이 정상작동이 불가합니다.
- 외부 자바스크립트 라이브러리에 들어있는 모든 변수, 함수, 객체들이 사용여부에 관계 없이 글로벌 네임스페이스를 오염시키게 됩니다.
- 외부 라이브러리를 추가적으로 사용할 때마다 HTML 파일을 수정해주어야 합니다.
2.2 NPM으로 외부 라이브러리 설치하기
- 좀 더 체계적으로 예제 프로젝트의 외부라이브러리를 관리하기 위해 Node.js의 패키지 매니저인 NPM을 도입하도록 하겠습니다.
$ npm init -y
{
"devDependencies": {
"webpack": "^5.92.1",
"webpack-cli": "^5.1.4"
},
"name": "webpack_test",
"version": "1.0.0",
"main": "index.js",
"dependencies": { /*중략*/ },
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": ""
}
- 그리고 NPM을 활용해 Moment.js 패키지를 설치하도록 하겠습니다.
$ npm i -S moment
+ moment@2.22.2
added 1 package in 6.833s
2.3 ES6 Import/Export
- ES6는 다른 프로그래밍 언어처럼 하나의 자바스크립트 모듈에서 다른 자바스크립트 모듈을 사용할 수 있도록 import와 export 키워드를 제공합니다.
1. 우선 index.html의 CDN 링크를 제거합니다.
<!DOCTYPE html>
<html>
<head>
<title>Webpack Example</title>
</head>
<body>
<script src="./index.js"></script>
</body>
</html>
2. index.js 파일의 맨 위에 Moment.js 패키지를 임포트 해줍니다.
import moment from "moment";
function currentTime() {
return moment().format("H:m:s");
}
const div = document.createElement("div");
document.body.appendChild(div);
setInterval(() => (div.innerText = currentTime()), 1000);
3. 크롬 브라우저에서 index.html을 실행시킵니다.
그럼 아래처럼 에러가 뜹니다.
- 이를 통해 일반 브라우저는 ES6의 import 키워드를 해석할 수 없다는 것을 알게되었습니다.
- 이를 웹팩을 통해 해결할 수 있습니다.
2.4 Webpack과 Webpack CLI 패키지 설치
- webpack은 웹팩의 핵심 패키지이며, webpack-cli는 터미널에서 webpack 커맨드를 실행할 수 있게 해주는 커맨드라인 도구입니다.
- 두 패키지 모두 개발환경에서만 필요한 것이기에 -D 옵션을 사용해 설치해줍니다.
$ npm install -D webpack webpack-cli
2.5 Webpack Bundle 만들기
- 프로젝트 디렉터리 안에 소스 디렉터리 (src)와 배포 디렉터리(dist)를 생성합니다.
- 그리고 index.js를 src 폴더 내로 이동시킵니다.
$ mkdir src dist
$ mv index.js src/
- 이후 npx webpack 커맨트를 실행시키면, 웹팩이 소스 디렉터리 안의 index.js 파일과 그 안의 import 되어있던 Moment.js 패키지까지 읽어들여 하나의 번들로 묶은 후 배포 디렉터리 안에 main.js 파일로 떨궈줍니다.
- 이 과정을 우리는 번들링이라 이야기 합니다.
- 실제로 dist/main.js 파일을 열어보면 웹팩이 소스코드를 최적화 해놓음을 알 수 있습니다.
- 브라우저가 해석해야 코드의 크기를 줄여주기 때문에 웹 애플리케이션의 성능이 좋아지며, 사람이 읽기 어렴기 때문에 약간의 보안적 이점도 있습니다.
2.6 Webpack Bundle 참조하기
- 이제 index.html 파일이 우리가 작성한 소스코드가 아닌 웹팩이 번들링해준 번들 파일을 참조하도록 수정합니다.
- index.html
<!DOCTYPE html>
<html>
<head>
<title>Webpack Example</title>
</head>
<body>
<script src="./dist/main.js"></script>
</body>
</html>
3. Javascript 모듈화하기
- 이제 ES6 모듈 키워드 (import, export)를 사용할 수 있으니 비단 외부 모듈을 사용할 때 뿐만 아니라 프로젝트를 모듈화할 때에도 웹팩을 활용할 수 있을 것입니다.
- 큰 규모의 프로젝트를 진행할 경우 자바스크립트 코드를 모듈화하면 재활용성, 테스트성, 유지보수성이 향상됩니다.
- 이렇듯 currentTime 함수를 time.js 파일에 넣고, index.js 파일에서는 currentTime 함수를 Import 하는 방식으로 수정하게 된다면, index.js를 제외한 다른 파일에서도 currentTime 함수를 가져와 사용하는 등 재사용성이 높아집니다.
// time.js
import moment from "moment";
export function currentTime() {
return moment().format("H:m:s");
}
-------------------------------------------------------
// index.js
import moment from "moment";
import { currentTime } from "./time.js";
const div = document.createElement("div");
document.body.appendChild(div);
setInterval(() => (div.innerText = currentTime()), 1000);
- 소스코드의 모듈 간의 의존성을 정리하면 다음과 같습니다.따라서 HTML 파일은 이 최종 번들파일만 참조할 수 있으며, 이것이 웹팩이 우리에게 가져다 주는 힘입니다.
- index.js -> time.js -> moment.js
- 웹팩은 이러한 모듈간의 의존관계를 트리로 구성하며, 하나의 번들 파일로 제공하게 됩니다.
'⚓️ 개발환경' 카테고리의 다른 글
[⚓️Webpack] CRA 없이 웹팩 프로젝트 만들기 | 웹팩의 시대는 끝났나 ? | 웹팩 커스터마이징 (0) | 2025.03.11 |
---|---|
[개발환경] npm, pnpm, yarn let’s go (0) | 2024.09.02 |
[⚓️개발환경] 바벨이란 무엇인가? (2) | 2024.08.12 |
[⚓️개발환경] 프론트엔드 개발에 Node.js가 필요한 이유 (8) | 2024.06.25 |
[⚓️개발환경] Package.json vs Package-lock.json 그리고 Caret, Tilde 등등.. (4) | 2024.06.25 |