webpack2 입문 가이드

이 글은 webpack을 다루어 보지 않은 사람들을 위한 가이드다. 다만 webpack2를 기준으로 하며, 기본적인 웹 지식(CommonJS, ES2015 등)은 알고있다고 가정한다.

webpack

webpack은 JavaScript 모듈 번들러(Bundler)다. 번들러는 말 그대로 번들링을 하는 친구를 말한다. 그러면 번들링은 뭘까? webpack 공식 페이지에 있는 그림을 보면 이해에 도움이 될 것이다.

번들러가 하는 것

webpack은 별도의 파일로 분리되어 있는 JavaScript 모듈들을 의존성을 통해 하나 혹은 여러 개의 파일로 묶는다. 이것을 번들링이라고 한다. 여기에 여러가지 설정을 더하면 CSS나 이미지도 JavaScript 파일로 번들링할 수 있다.

다른 번들러로는 browserify, rollup.js 등이 있다. 하지만 요즘 프론트엔드 세계에서는 webpack으로 대세가 굳어진 느낌이다.

Hello, world!

먼저 webpack을 가지고 놀기 위한 놀이터 디렉토리를 만들자.

1
$ mkdir webpack-playground && cd webpack-playground

다음으로, webpack을 설치한다.

1
$ yarn add --dev webpack

yarn이 없다면 npm을 사용해서 설치해도 되지만 package.json이 없기 때문에 npm init이 선행되어야 한다.

또한 글로벌로 설치해서 CLI를 사용해도 되지만 어차피 실제 협업 환경에서는 로컬로 설치한뒤 npm 스크립트를 이용하는 경우가 일반적이므로 로컬 설치를 권장한다.

그 다음으로는 webpack이 로드할 모듈을 구현한다.

1
2
// hello.js
module.exports = 'Hello';
1
2
// world.js
module.exports = 'world';

모듈을 만들어놓기만 하고 쓰지 않는다면 아무 소용이 없다. 위의 모듈을 사용하는 코드를 작성한다.

1
2
3
4
// entry.js
var hello = require('./hello');
var world = require('./world');
document.write(hello + ', ' + world + '!');

평소에 Node.js를 자주 사용해서 CommonJS에 익숙하다면 위의 코드가 어떻게 동작할지 잘 알고 있을 것이다. hello는 아까 구현한 hello.js를 로드하므로 'Hello'라는 문자열이 되고, world 역시 마찬가지다.

이제 파일을 번들링하기 위해서 npm 스크립트를 써준뒤, 사용한다.

1
2
3
4
5
6
7
8
{
"scripts": {
"build": "webpack entry.js bundle.js"
},
"devDependencies": {
"webpack": "^2.2.1"
}
}

이 상태에서 아래의 명령어를 입력한다.

1
$ yarn build

혹은 npm 사용자라면,

1
$ npm run build

bundle.js라는 파일이 생겼음을 확인할 수 있다. 이제 이 파일을 HTML 파일에서 로드하면 된다.

1
2
3
4
5
6
7
8
9
<!-- index.html -->
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript" src="bundle.js"></script>
</body>
</html>

이 HTML 파일을 웹 브라우저에서 열어보면 Hello, world!라는 문자열이 출력되는 것을 확인할 수 있다.

Configuration

CLI만 이용해서 webpack을 사용할 수도 있지만 명령어가 금방 복잡해지고 한계가 생기므로 설정 파일을 사용하는 것이 일반적이다.

webpack의 설정파일은 복잡하기로 악명이 높지만.. 모든 것이 늘 그렇듯이 처음에는 단순하게 시작할 수 있다.

1
2
3
4
5
6
7
8
9
// webpack.config.js
module.exports = {
entry: {
'entry': './entry.js'
},
output: {
filename: 'bundle.js'
}
};

여기까지 왔다면 이 설정파일이 이해 안될 분은 없으리라 생각한다. 정말 최소한의 설정만 해놓은 파일이다. 물론 아래에서 계속해서 덩치가 커질 예정이지만 일단 이 설정파일만 가지고도 번들링을 할 수 있다.

설정파일을 만들었다면 webpack이라는 명령어만 쳐도 파일을 번들링할 수 있다.

1
2
3
4
5
6
7
8
{
"scripts": {
"build": "webpack"
},
"devDependencies": {
"webpack": "^2.2.1"
}
}

Stylesheet

webpack은 Stylesheet도 JavaScript 파일로 번들링할 수 있다. 물론 CSS는 CommonJS 규격에 맞지 않지만, 자체적인 @import 문도 존재하므로 모듈로 볼 수 있고, 번들링할 수 있는 대상이다.

webpack에서 번들링된 CSS를 사용하는 방법은 크게 두 가지가 있다. 첫 번째로는 HTML의 <style> 태그 안에 CSS를 직접 쑤셔넣는(!) 방법. 두 번째로는 별도의 엔트리 포인트를 만들어서 또 하나의 CSS 파일로 빌드하는 방법. 두 가지 방법 모두 장단이 있으나 이 글에서는 첫 번째 방법만을 소개한다. 만약 두 번째 방법에 대해서 궁금하다면 공식 가이드를 참고하자.

style-loader는 정확히 위에 설명한 첫 번째 방식으로 동작하는 로더(Loader)다. 우선 사용하기 위해서 style-loader와 css-loader를 설치하자.

1
$ yarn add --dev style-loader css-loader

css-loader는 CSS의 @importurl()문을 CommonJS의 require 처럼 해석할 수 있도록 만들어주는 로더다. CSS를 번들링 하기 위해서는 기본적으로 이 두 가지 로더를 사용해야 한다.

먼저, 로더를 사용하려면 설정파일을 수정해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// webpack.config.js
module.exports = {
entry: {
'entry': './entry.js'
},
output: {
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
}
};

module 프로퍼티 아래에 약간의 설정을 추가했다. 간단하게 설명하면 .css 확장자로 끝나는 파일을 로드하는 경우 css-loader와 style-loader를 거치도록 하겠다는 의미다. 같은 파일에 대해서 로더는 아래쪽부터 순서대로 동작한다.

이제 불러올 CSS를 작성한다.

1
2
3
4
5
/* text.css */
body {
color: blue;
font-size: 24px;
}
1
2
3
4
5
6
/* common.css */
@import 'text.css';
body {
background-color: gray;
}

이제 이 CSS를 일반적인 CommonJS 모듈 사용하듯이 불러오면 된다.

1
2
3
4
5
// entry.js
require('./common.css');
var hello = require('./hello');
var world = require('./world');
document.write(hello + ', ' + world + '!');

웹 브라우저에서 새로고침을 해보면 스타일이 적용된 것을 확인할 수 있다. 특히, 개발자 도구를 켜서 <style> 태그 안에 CSS가 들어간 것을 확인해보자.

ES2015

IE환경을 지원하면서도 ES2015를 쓰기 위해서는 babel 같은 트랜스파일러(Transpiler)가 필수다. webpack에서는 이를 위해 babel-loader에 통과시켜서 ES5 이하의 JavaScript로 만든다.

babel-loader를 사용하기 위해서 먼저 babel-loader를 설치한다.

1
$ yarn add --dev babel-loader babel-core babel-preset-env

webpack의 설정도 바꿔야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// webpack.config.js
module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [[
'env', {
targets: {
browsers: ['last 2 versions']
}
}
]]
}
}
// ...
]
}
};

설명을 하고 넘어가야 할 것 같다. 먼저, babel을 사용하기 위해서는 preset을 설치해야 한다. 그 중 babel-preset-env는 설정된 환경에 알맞게 preset을 자동으로 설정해준다. 이 라이브러리를 사용하기 위해서 presets 라는 속성을 사용해서 환경을 설정할 수 있다. 위의 파일에서 설정된 환경은 브라우저 별로 최신의 두 개 버전만을 고려하는 환경이다. 지원하는 브라우저의 리스트는 이 곳에서 확인할 수 있다.

이제부터 프로젝트에 포함된 모든 .js 확장자 파일은 babel-loader를 거치면서 ES5로 트랜스파일된다. 코드를 ES2015로 변경하여 테스트 해보자.

1
2
// hello.js
export default 'Hello';
1
2
// world.js
export default 'world';
1
2
3
4
5
// entry.js
import './common.css';
import hello from './hello';
import world from './world';
document.write(`${hello}, ${world}!`);

빌드를 뒤 결과를 보면, ES5로 컴파일 된 것을 볼 수 있다.

Lint

webpack은 번들러지만 JavaScript 파일을 로드하기 때문에 Lint 작업도 수행할 수 있다. Linter로는 ESLint를 사용한다.

1
$ yarn add --dev eslint-loader

ESLint가 글로벌 설치되어있는 상태라면 ESLint의 CLI를 사용하여 쉽게 룰을 생성할 수도 있다. 여기서는 AirBnb 기본 룰을 사용한다.

1
$ eslint --init

혹은 다음의 룰을 .eslintrc.js 라는 이름의 파일로 저장한다.

1
2
3
4
5
6
7
8
9
module.exports = {
"extends": "airbnb-base",
"plugins": [
"import"
],
"env": {
"browser": true
}
};

ESLint를 돌리기 위해서는 eslint-loader를 webpack 설정 파일에 추가해야 한다. 빌드된 파일을 Lint하는 건 소용없으므로 반드시 babel보다 먼저 eslint를 거치도록 만들어야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// webpack.config.js
module.exports = {
// ...
module: {
rules: [
// ...
{
enforce: 'pre',
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader',
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
// ...
}
// ...
]
}
};

그리고 바로 enforce 속성이 그 역할을 한다. 이 속성을 'pre'로 지정하면 JavaScript 파일들이 babel-loader보다 먼저 eslint-loader를 거치게 된다.

ESLint가 제대로 동작하는 지 확인해보기 위해서, 코드를 추가한다.

1
2
3
4
5
6
7
8
// entry.js
import './common.css';
import hello from './hello';
import world from './world';
var a = 0; // 에러가 발생할 것이다.
document.write(`${hello}, ${world}!`);

제대로 설정했다면 아래처럼 에러가 표시될 것이다.

1
2
error Unexpected var, use let or const instead no-var
error 'a' is assigned a value but never used no-unused-vars

npm 모듈 사용하기

webpack은 CommonJS 방식의 모듈 로드를 지원하므로, npm 모듈을 브라우저에서 그대로 사용할 수 있다. 여기서는 유명한 JavaScript 라이브러리인 Lodash를 사용할 것이다.

1
$ yarn add lodash

이제 Lodash의 random 함수를 사용하기 위해서 다음과 같이 코드를 수정한다.

1
2
3
4
5
6
7
8
// entry.js
import random from 'lodash/random';
import './common.css';
import hello from './hello';
import world from './world';
document.write(`${hello}, ${world}!`);
document.write(`Random: ${random(0, 100)}`);

빌드하고 브라우저에서 확인해보면 새로고침할 때마다 랜덤한 숫자가 노출되는 것을 확인할 수 있다.

사족으로, Lodash는 스크립트 용량이 워낙 크기 때문에 import {random} from 'lodash'; 같은 식으로 로드하면 Lodash의 완전한 구현을 번들링한다. 따라서 번들링된 스크립트 크기도 매우 커진다. 그러므로 별도의 엔트리 파일을 로드해서 스크립트 사이즈를 최적화하자.

또한, webpack2 에서는 스크립트 사이즈 최적화 기법으로 Tree Shaking을 사용할 수 있다. 직접 짠 코드는 Tree Shaking을 사용하면 되지만, 다른 라이브러리를 불러와서 사용할 때는 Tree Shaking이 안되는 경우가 있으므로 항상 별도의 엔트리 파일을 포함하는 방법을 찾아보자. Tree Shaking에 대해서는 나중에 별도의 글로 쓸 예정이다. 과연

마치며

webpack2의 베타가 끝내고 정식 버전도 릴리즈된지 꽤 시간이 흘렀지만 게으름으로 마이그레이션을 차일피일 미루다가, 최근에야 webpack2를 사용하게 되었다. 일찍이 블로그에 webpack 실전 가이드라는 글을 쓴 적이 있는데, 이 글은 webpack2를 다루지 않으며, 오래된 정보1를 담은 글임에도 아직 많은 사람들이 해당 글로 유입되고 있기 때문에 내용상 거의 중복되지만 이 글을 새로 작성하게 되었다.

webpack1에서의 마이그레이션에 대해서는 공식 문서가 잘 정리되어 있으므로 참고하도록 하자.

여기에 사용된 코드는 GitHub 레포지토리 webpack-guide에 올라가 있으니 참고하시기 바란다.


  1. 1.물론 그래봤자 1년도 채 안됐다.