Webpack 소개

Out of Bedlam
27 min readFeb 26, 2017

--

JavaScript 모듈 번들링은 2009년에 RequireJS가 처음 커밋된 이후로 Browserify가 나왔고 그 이후로도 다수의 번들러bundler들이 인터넷상에 만들어졌습니다. 이들 중에서도 webpack은 돋보이는 기능으로 최근 많은 관심을 받고 있습니다.

모듈 번들러란?

대부분의 프로그래밍 언어에서는 코드를 여러 개의 파일에 나누고 이 파일들에 담겨있는 기능들을 사용하기 위해서 애플리케이션에서 임포트할 수 있습니다. 하지만 브라우저에서는 임포트를 사용할 수 없으며, 모듈 번들러가 기능을 대신 지원합니다. 비동기적으로 모듈들을 로딩하여 로딩이 완료되면 실행되도록 하거나 또는 필요한 파일들을 묶어서 하나의 자바스크립트로 만들어서 HTML에서 <script> 태그로 로딩될 수 있도록 만들어 줍니다.

모듈 로더나 번들러가 없이도 코드 파일을 수작업으로 하나의 파일로 만들거나 HTML에 수 많은 <script> 태그를 사용해서 하나씩 로딩할 수도 있습니다. 하지만 수작업에는 몇 가지 단점이 더 있습니다.

  • 각 파일이 어떤 파일들에 의존하고 있는지와 어떤 파일을 필요하지 않은지를 포함해서 파일들이 올바른 순서로 로드되도록 항상 신경을 써야 합니다.
  • 다수의 <script> 태그는 브라우저가 서버로부터 코드를 가져오기 위해서 최소한 태그의 수 만큼 호출을 해야한다는 뜻이므로 성능에 부정적인 영향을 끼칩니다.
  • 많은 수작업이 수반됩니다.

대부분의 모듈 번들러는 npm 이나 Bower와 직접적으로 통합되어 있어서 써드-파티 의존성을 쉽게 애플리케이션에 추가할 수 있도록 만들어 줍니다. 따라서 그냥 써드-파티 라이브러리를 설치하고 애플리케이션에서 import 코드 한 줄만 넣고 모듈 번들러를 실행하기만 하면 됩니다.

Webpack

개발자들이 다른 모듈 번들러에 비해서 webpack을 선호하는지는 몇 가지 이유가 있습니다.

  • 비교적 최신이어서 이전의 번들러에서 발생하던 문제점과 단점을 피할 수 있습니다.
  • 쉽게 시작할 수 있습니다. 그냥 평범한 자바스크립트 파일이므로 별도 형식의 환경설정 파일이 필요 없습니다.
  • 플러그인 시스템을 통해서 훨씬 많은 것을 할 수 있으며 강력한 기능들을 사용할 수 있으므로 webpack 하나로 끝낼 수 있습니다.

webpack과 맞먹을 수 있는 모듈 번들러나 빌드 도구는 별로 없으며 많은 커뮤니티 사용자로부터 도움을 받을 수 있습니다. Browserify 커뮤니티와 규모면에서 거의 비슷할 겁니다.

webpack 설치

webpack을 설치하려면 머저 Node.js와 npm이 필요합니다. Node.js 웹 사이트에서 내려받고 설치하기 바랍니다.

macOS에서는 homebrew를 통해서 팩키지를 설치하는 것이 편리합니다.

$ brew install node
$ brew install npm

webpack(뿐만 아니라 모든 CLI 팩키지에 해당되는)을 npm을 통해서 전역으로 설치할 수도 있지만 이렇게 하면 프로젝트별로 서로 다른 버전을 사용할 수가 없으며 프로젝트의 의존성에 포함될 수 없게됩니다. 따라서 CLI 팩키는 가능한 로컬에 설치해서 상대 경로를 사용하거나 npm 스크립트로 팩키지를 실행하는 것이 좋습니다. 이미 글로벌로 설치된 CLI 팩키지가 있다면 삭제하고 다시 로컬로 설치하기를 권합니다.

먼저 프로젝트 디렉터리를 만들고 해당 디렉터리에서 npm init을 실행해서 프로젝트 초기화를 합니다.

이제 package.json이 생겼을 것이고 여기에 필요한 의존성들을 설정할 수 있습니다. npm을 사용해서 webpack을 의존성으로 설치합니다. npm install webpack -D (-D는 개발 의존성으로 package.json에 저장하라는 뜻으로 --save-dev와 같습니다.) package.json을 확인해 보면 devDependencieswebpack이 추가되어 있는 것을 확인할 수 있습니다.

webpack을 사용해서 간단한 애플리케이션을 만들어 보도록 하겠습니다. 애플리케이션에서 의존성으로 로드할 Lodash를 설치하겠습니다. npm install lodash -S (여기서 -S--save와 같습니다.) 다음으로, src 디렉터리를 만들고 main.js에 다음과 같이 코드를 입력합니다.

var map = require('lodash/map');

function square(n) {
return n*n;
}

console.log(map([1,2,3,4,5,6], square));

1부터 6까지의 정수 배열을 만들고 Lodash의 map으로 거듭제곱을 출력하는 예입니다. node src/main.js와 같이 실행하면 [ 1, 4, 9, 16, 25, 36 ]이 출력됩니다.

이제 이 간단한 스크립트와 Lodash 코드를 하나의 번들로 만들어 브라우저에서 쉽게 사용할 수 있도록 만들어 보겠습니다.

Webpack 명령어 사용하기

환경설정 파일을 만드느라 시간을 쓰지 말고 가장 빠르게 webpack을 사용하려면 커맨드 라인으로 실행하는 것입니다. 환경설정 파일 없이 가장 단순하게 사용하는 방법은 입력 파일의 경로와 출력 파일의 경로만 지정하는 것입니다. webpack은 입력 파일을 읽어서 의존성 트리를 확인해서 모든 파일들을 지정된 출력 파일 경로에 하나의 단일 파일로 만들어 줍니다. 이 예에서는 입력 파일 경로는 src/main.js이고 출력 파일의 경로는 dist/bundle.js로 하겠습니다. 아래와 같이 npm 스크립트를 작성합니다.

...
"scripts": {
"build": "webpack src/main.js dist/bundle.js",
}
...

이제 npm run build를 실행하면 webpack이 실행되어 dist/bundle.js가 생성됩니다. 이 파일은 node dist/bundle.js와 같이 실행할 수 있으며 간단한 HTML 페이지를 작성해서 브라우저로 실행하면 콘솔을 통해서 결과를 확인할 수 있습니다.

webpack에 대해서 더 알아보기 전에 빌드 스크립트를 만들어서 다시 빌드하기 전에 dist 디렉터리를 먼저 지우고 번들 작업을 진행하도록 스크립트를 추가하여 보겠습니다. 윈도우즈 사용자들을 위해서 del-cli를 사용하도록 하겠습니다. npm install del-cli -D을 실행해서 설치하고 npm 스크립트를 다음과 같이 추가합니다.

...
"scripts": {
"prebuild": "del-cli dist -f",
"build": "webpack src/main.js dist/bundle.js",
"execute": "node dist/bundle.js",
"start": "npm run build -s && npm run execute -s"
}
...

"build"는 기존과 동일하며 "prebuild"에서 dist 디렉터리를 삭제하도록 만들었습니다. 이제 "build"가 실행되기 전에 항상 "prebuild"가 실행됩니다. "execute"는 만들어진 번들을 실행하는 스크립트이며 모든 과정을 한 번에 실행하려면 "start"를 사용합니다.

환경설정 파일

처음에는 webpack 명령어를 직접 사용하였지만 webpack의 더 많은 기능들을 사용하기 시작하면서 명령행으로 많은 옵션들을 넘기면 아무래도 환경설정 파일을 별도로 사용하는 것이 가독성면에서 좋습니다. webpack의 환경설정 파일은 자바스크립트로 작성됩니다.

환경설정 파일을 프로젝트 루트 디렉터리에 webpack.config.js로 만듭니다. 이 파일이 webpack이 디폴트로 찾는 환경설정 파일 이름이며 다른 이름이나 다른 디렉터리에 만들려면 webpack에 --config [filename]과 같이 옵션으로 지정하면 됩니다.

환경설정 파일에 다음과 같이 코드를 작성합니다.

module.exports = {
entry: './src/main.js',
output: {
path: './dist',
filename: 'bundle.js'
}
};

입력 파일과 출력 파일을 지정하였습니다. 이 파일은 JSON이 아닌 자바스크립트 파일이므로 환경설정 객체를 익스포트해야 하므로 module.exports로 하였습니다.

이제 package.json에서 webpack에 넘겼던 옵션들을 제거해서 아래와 같이 만듭니다.

"scripts": {
...............
"build": "webpack",
...............
},

npm start를 실행하면 이전과 동일한 과정으로 빌드와 실행 결과가 출력됩니다.

로더 사용하기

webpack에 기능을 추가하는 방식에는 크게 로더loaders와 플러그인plugins 두 가지가 있습니다. 플러그인에 대해서는 다음에 살펴보기로 하고 먼저 로더에 대해서 알아 보겠습니다. 로더는 주어진 타입의 파일을 변환transformation하거나 작업operation을 수행할 수 있습니다. 예를 들어 .js 확장자를 가진 파일에 ESLint를 실행하고 Babel로 ES2015를 ES5로 다운 컴파일하게 지정할 수 있습니다. ESLint 과정에서 경고가 발생하면 콘솔로 출력되며 에러가 발생하면 webpack이 더 진행하지 않고 중단됩니다.

이 예에서는 ESLint는 사용하지 않고 Babel만 사용해서 ES5 코드로 컴파일해 보겠습니다. 먼저 다음과 같이 ES2015 코드를 준비해서 main.js를 수정합니다.

import { map } from 'lodash';

console.log(map([1,2,3,4,5,6], n => n*n));

이 코드의 내용은 이전과 동일하지만 square 기명 함수named function대신에 화살표 함수를 사용하였다는 점과 ES2015의 import를 사용해서 lodashmap을 로딩하였다는 점이 이전과 다릅니다. 이 코드의 첫 번째 라인은 Lodash 전체 파일을 번들에 포함하기 때문에 파일의 크기가 이전의 lodash/map으로 했을 때 보다 더 커집니다. 원한다면 이전과 동일하게 import map from 'lodash/map'로 할 수도 있습니다. 여기서는 예제 코드와 같이 전체 모듈을 임포트했는데 webpack의 tree-shaking이라는 기능을 사용하면 모듈에서 사용되지 않는 부분을 제거할 수 있으므로 결국은 큰 차이가 없게 만들 수 있습니다.

이제 ES2015 코드를 작성하였으므로 과거 버전의 브라우저에서도 실행될 수 있도록 ES5로 컴파일해보겠습니다. 컴파일하려면 먼저 Babel과 Babel이 webpack에서 실행될 때 필요한 것들을 준비해야 합니다. 최소한 babel-core (Babel의 핵심 기능), babel-loader (babel-core의 인터페이스가 되는 webpack 로더), babel-preset-es2015 (ES2015를 ES5로 컴파일하는 규칙)이 필요합니다. 추가적으로 babel-plugin-transform-runtimebabel-polyfill을 설치하였는데 둘 다 Pollyfill과 Helper 기능들을 Babel에 추가해 줍니다. 두 가지가 약간 다르기는 하지만 동일한 기능을 하므로 보통은 둘 중 하나만 사용하거나 전혀 사용하지 않아도 됩니다. 이 예에서는 설명을 위해 둘 다 설치하였습니다.

$ npm install -D babel-core babel-loader babel-preset-es2015 babel-plugin-transform-runtime babel-polyfill

위와 같이 설치한 후 webpack.config.js를 다음과 같이 로더를 지정할 섹션을 추가하여 수정합니다.

module.exports = {
entry: './src/main.js',
output: {
path: './dist',
filename: 'bundle.js'
},
module: {
rules: [
...
]
}
};

rules가 포함된 module을 추가하였습니다 rules에는 사용할 로더에 대한 설정 사항들이 배열로 들어갑니다. 각각의 로더는 최소한 testloader 두 개의 옵션이 있어야 합니다. test는 보통 각 파일의 절대 경로로 평가하는 정규 표현식을 사용합니다. 예를 들어 /\.js$/와 같은 파일의 확장자를 평가하도록 정규 표현식을 지정합니다. 여기 예에서는 /\.jsx?$/와 같이 하여 .js.jsx와 일치하도록 하겠습니다. 다음으로 loader를 사용해서 test를 통과한 파일에 적용할 로더를 지정합니다.

로더는 babel-loader!eslinkt-loader와 같이 느낌표로 구분된 로더의 이름을 문자열로 나열해서 지정합니다. webpack이 오른쪽에서 왼쪽으로 읽어들이므로 eslint-loaderbabel-loader보다 먼저 실행됩니다.

로더에 옵션을 지정하려면 쿼리 스트링 형식으로 지정할 수 있습니다. 예를 들어 fakeoption 옵션으로 true를 넒기려면 babel-loader?fakeoption=true!eslint-loader와 같이 지정합니다.

로더를 배열로 넘기려면 loader 대신에 use를 사용할 수도 있습니다. 예를 들어 use: ['babel-loader?fake[option=true', 'eslint-loader']와 같은 방식입니다. 이렇게 하면 여러 라인으로 풀어서 기술 할 수 있으므로 가독성이 좋아집니다.

여기서는 Babel만 사용하므로 로더 환경설정은 다음과 같습니다.

...
rules: [
{ test: /\.jsx?$/, loader: 'babel-loader' }
]
...

만약 예와 같이 하나의 로더만 사용하는 경우에는 옵션을 별도의 options에 키-밸류 쌍으로 설정할 수 있습니다.

...
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
options: {
fakeoption: true
}
}
]
...

다음과 같이 Babel에 두 가지 옵션을 지정합니다.

...
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
options: {
plugins: ['transform-runtime'],
presets: ['es2015']
}
}
]
...

ES2016의 모든 기능을 ES5로 변경하기위한 프리셋을 설정하고 설치한 transform-runtime을 사용하도록 하였습니다. 이 플러그인은 여기서 꼭 필요한 것은 아니지만 어떻게 설정하는지 보여주기 위해 표시했습니다.

로더를 추가로 사용하기 위해서 한 가지 더 설정을 해야합니다. Babel에게 node_module 폴더에 들어 있는 파일에 대해서는 처리하지 않도록 해야 번들을 만드는 작업의 속도가 빨라집니다. exclude 속성을 추가해서 로더에게 해당 폴더에 대해서 처리하지 않도록 지정합니다.

...
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
plugins: ['transform-runtime'],
presets: ['es2015']
}
}
]
...

반대로 include를 사용해서 src 디렉털만 사용하도록 지정할 수도 있습니다.

이제 npm start 명령을 실행해서 브라우저에서 동작하는 ES5 코드를 만들 수 있습니다. transform-runtime 플러그인 대신에 polyfill을 사용하려면 두 가지를 수정해야 합니다. 먼저 plugin: ['transform-runtime']을 삭제하고 webpack의 entry 섹션을 다음과 같이 추가해야합니다.

entry: [
'babel-polyfill',
'./src/main.js'
],

Handlebars 로더 사용하기

여기에 다른 로더를 추가해 보겠습니다. Handlebars 로더는 템플릿을 함수로 컴파일해서 자바스크립트에서 임포트할 수 있도록 만들어 줍니다. 자바스크립트 파일이 아닌 것도 임포트할 수 있게되어 하나의 번들로 만들어지는 것입니다. 이미지 파일을 base64로 인코딩된 URL 문자열로 변환해서 자바스크립트 내에서 사용할 수 있습니다. 다수의 로더를 연결해서 이미지 파일의 크기를 최적화 하는 작업을 수행할 수도 있습니다.

일단 로더를 인스톨 합니다.

$ npm install -D handlebars-loader

이 명령을 실행하면 경고가 발생해서 Handlebar 모듈 자체도 필요하다는 것을 알 수 있습니다.

$ npm install -D handlebars

이렇게 로더와 모듈이 분리되어 관리되므로 Handlebars의 버전에 상관없이 로더의 버전을 선택할 수 있습니다.

이제 둘 다 설치하였으므로 사용할 Handlebars 템플릿을 준비해야 합니다. 다음과 같이 src 디렉터리 안에 numberlist.hbs를 만듭니다.

<ul>
{{#each numbers as |number i|}}
<li>{{number}}</li>
{{/each}}
</ul>

이 템플릿은 배열(numbers)을 인자로 받아서 <li>로 출력합니다.

다음으로 자바스크립트 파일을 수정해서 템플릿을 사용해서 출력하도록 수정합니다. 이 때 webpack은 numberlist.hbs가 자바스크립트가 아니므로 어떻게 임포트할지 모릅니다. 따라서 import문에 webpack이 Handlebars 로더를 사용해서 임포트하도록 알려줘야 합니다.

import { map } from 'lodash';
import template from 'handlebars-loader!./numberlist.hbs';

let numbers = map([1,2,3,4,5,6], n => n*n);

console.log(template({numbers}));

로더 이름 다음에 느낌표로 구분한 파일 경로를 지정해서 webpack이 해당 파일에 대해서 지정된 로더를 사용하도로록 만듭니다. 이렇게 하면 환경설정 파일에 아무 것도 지정하지 않아도 됩니다. 하지만 대규모 프로젝트에서는 여러 개의 템플릿을 로딩할 수 있기 대문에 환경설정 파일을 사용하는 것이 handlebars-loader!를 반복해서 쓰지 않아도 되므로 더 좋을 것입니다.

환경설정을 다음과 같이 수정합니다.

...
rules: [
{/* babel loader config... */},
{ test: /\.hbs$/, loader: 'handlebars-loader' }
]
...

이 예는 .hbs 확장자의 모든 파일을 handlebars-loader가 처리하도록 합니다. 이제 npm start로 실행하면 다음과 같이 출력됩니다.

<ul>
<li>1</li>
<li>4</li>
<li>9</li>
<li>16</li>
<li>25</li>
<li>36</li>
</ul>

플러그인 사용하기

플러그인과 로더의 차이점은 사용자화 기능을 webpack에 추가한다는 것입니다. webpack의 워크플로우에 더 많은 자유도를 줄 수 있습니다. 플러그인에는 특정 파일 타입에만 적용되는 로더와 같은 제약이 없기 때문입니다. 플러그인은 아무데나 끼워 넣을 수 있습니다. npm의 팩키지 검색 웹 페이지에서 “webpack-plugin”으로 검색해서 어떤 기능의 플러그인이 있는지 확인해 보기 바립니다.

여기서는 두 가지 플러그인을 사용하겠습니다. 첫 번째는 HTML webpack 플러그인으로 HTML 파일을 생성해 줍니다.

이 플러그인을 사용해 보기 전에 테스트를 위해 간단한 웹 서버를 실행하도록 스크립트를 수정하겠습니다. 먼저 웹 서버를 인스톨 합니다.

$ npm install -D http-server

그 다음으로 execution 스크립트를 server 스크립트로 변경하고 start 스크립트를 수정합니다.

...
"scripts": {
"prebuild": "del-cli dist -f",
"build": "webpack",
"server": "http-server ./dist",
"start": "npm run build -s && npm run server -s"
},
...

npm start가 webpack 빌드가 완료된 후 웹 서버를 시작하면 http://localhost:8080 으로 접속이 가능합니다. 물론 아직은 플러그인으로 생성한 페이지가 없습니다. 이제 HTML 파일을 만들어 보겠습니다. 먼저 HTML 플러그인을 설치합니다.

$ npm install -D html-webpack-plugin

설치가 완료되면 webpack.config.js을 다음과 같이 수정합니다.

var HtmlwebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry: [
'babel-polyfill',
'./src/main.js'
],
output: {
path: './dist',
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/,
options: { plugins: ['transform-runtime'], presets: ['es2015'] }
},
{ test: /\.hbs$/, loader: 'handlebars-loader' }
]
},
plugins: [
new HtmlwebpackPlugin()
]
};

두 가지를 수정했습니다. 파일 첫 줄에서 새로 설치한 플러그인을 임포트하고 plugins 섹션을 파일 끝에 추가해서 플러그인의 새로운 인스턴스를 만들었습니다.

여기서는 플러그인에 아무런 옵션도 넘기지 않았습니다. 따라서 플러그인은 표준 템플릿을 사용할 것이기 때문에 생성되는 파일에 별 내용은 없지만 번들된 스크립트는 포함되어 있을 것입니다. npm start를 실행하고 브라우저로 접속해보면 빈 페이지가 보여질 것입니다. 터미널에서 dist/index.html이 생성된 것을 확인할 수 있습니다.

원하는 내용을 출력하도록 하려면 src 디렉터리에 index.html 파일을 만들어 템플릿을 정의해야합니다. 디폴트로 HTML webpack 플러그인은 EJS 템플릿을 사용하지만 다른 템플릿 언어를 사용하도록 플러그인을 설정할 수 있습니다. EJS를 사용해여 템플릿을 src/index.html에 다음과 같이 만듭니다.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<h2>This is my Index.html Template</h2>
<div id="app-container"></div>
</body>
</html>

이 템플릿 예에서는 다음의 사항을 유심히 살펴봐야 합니다.

  • 플러그인 설정의 옵션을 통해 전달되는 타이틀을 사용할 것입니다.
  • 어디에 스크립트를 추가할지 지정하지 않았습니다. 플러그인은 디폴트로 body 태그의 마지막 부분에 스크립트가 추가됩니다.
  • 임의의 divid가 지정되어 있습니다.

이제 템플릿이 준비되었으니 main.js에서 HTML의 div에 내용을 추가하도록 해야합니다.

document.getElementById("app-container").innerHTML = template({numbers});

그리고 webpack 환경 설정에서 플러그인에 필요한 옵션을 지정합니다.

var HtmlwebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry: [
'babel-polyfill',
'./src/main.js'
],
output: {
path: './dist',
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/,
options: { plugins: ['transform-runtime'], presets: ['es2015'] }
},
{ test: /\.hbs$/, loader: 'handlebars-loader' }
]
},
plugins: [
new HtmlwebpackPlugin({
title: 'Intro to webpack',
template: 'src/index.html'
})
]
};

template 옵션은 템플릿을 어디서 찾아야 하는지 설정합니다. title 옵션은 템플릿으로 전달됩니다. 이제 npm start를 하고 브라우저로 접속하면 다음과 같은 화면을 확인할 수 있습니다.

각각의 플러그인마다 처리하는 일과 목적이 다르므로 서로 다른 옵션이 있지만 결국은 webpack.config.jsplugins에 설정됩니다.

지연-로딩Lazy-Loading

번들링을 통해서 전체 스크립트들을 커다란 하나의 파일로 만들면 필요한 HTTP 요청의 수를 줄일 수 있다는 장점이 있습니다. 하지만 애플리케이션에 따라서는 어떤 방문자의 세션에는 불필요한 코드까지 다운로드하게 만드는 단점도 존재합니다.

webpack은 하나의 번들을 지연 로딩lazy-load이 가능한 몇 개의 덩어리chunk로 나눌 수 있습니다. 개발자가 해야할 일은 다음에 설명할 두 가지 방법 중의 한 가지 방식으로 코드를 작성하면 나머지는 webpack이 알아서 합니다. 두 가지 방식은 CommonJS를 기반으로 한 것과 AMD를 기반으로 한 것입니다.

CommonJS을 사용해서 모듈을 지연 로딩하려면 다음과 같이 하면 됩니다.

require.ensure(["module-a", "module-b"], function(require) {
var a = require("module-a");
var b = require("module-b");
// ...
});

require.ensure를 사용하면 전달된 모듈 이름의 배열에 포함된 모듈들이 가용한지 (실행은 하지 않고) 확인합니다. 콜백에서 그 모듈을 실제로 사용하려면 명시적으로 require를 콜백의 인자로 넘겨서 사용해야 합니다.

AMD 방식은 다음과 같습니다.

require(["module-a", "module-b"], function(a, b) {
// ...
});

AMD에서는 require에 모듈 명의 배열과 콜백을 전달합니다. 콜백의 인자는 각 모듈의 레퍼런스로 전달한 모듈 이름의 배열과 같은 순서로 배치됩니다.

참고: webpack 2에서는 System.import를 지원하는데 콜백 대신 프라미스promise를 사용합니다. 하지만 System.import 이미 사용 중지 표시deprecated되고 import()의 새로운 규격에 포함되었습니다.

예제 코드 mian.js를 수정해서 2초를 기다린 후에 Handlebars 템플릿을 지연 로딩lazy-load하도록 만들겠습니다. 먼저 코드의 최상단에서 템플릿을 임포트하는 문장을 제거하고 하단의 코드를 setTimeout으로 감싸서 AMD 방식으로 템플릿을 require로 불러 들이겠습니다.

import { map } from 'lodash';

let numbers = map([1,2,3,4,5,6], n => n*n);

setTimeout( () => {
require(['./numberlist.hbs'], template => {
document.getElementById("app-container").innerHTML = template({numbers});
})
}, 2000);

이제 npm start를 실행하면 dist/0.bundle.js와 같은 형식을 새로운 파일이 추가로 생성된 것을 확인할 수 있습니다. 브라우저로 접속을 해보면 2초의 지연 후에 이 새로운 파일이 실행된다는 것을 확인할 수 있습니다. 구현이 복잡하지 않으면서도 파일 사이즈를 줄여서 사용자 경험을 개선할 수 있습니다.

새로 생성된 하위 번들sub-bundles은(또는 덩어리chunk) 각각의 의존성을 모두 담고 있습니다.

벤더 청크Vendor Chunk

한 가지 더 추가로 이야기할 최적화는 벤더 청크vendor chunk입니다. 공통 코드나 써드파티 코드를 모아 서 별도의 번들로 정의할 수 있습니다. 이렇게 하면 애플리케이션 코드와 분리된 파일로 구성된 라이브러리를 방문자가 캐시cache하도록 해서 애플리케이션이 업데이트되어도 라이브러리는 다시 다운로드되지 않도록 만들 수 있습니다.

이렇게 하려면 CommonsChunkPlugin이라는 webpack 플러그인을 사용해야 합니다. 이 플러그인은 이미 webpack과 함께 설치되어 있으므로 webpack.config.js만 수정하면 됩니다.

var HtmlwebpackPlugin = require('html-webpack-plugin');
var UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
var CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');

module.exports = {
entry: {
vendor: ['babel-polyfill', 'lodash'],
main: './src/main.js'
},
output: {
path: './dist',
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/,
options: { plugins: ['transform-runtime'], presets: ['es2015'] }
},
{ test: /\.hbs$/, loader: 'handlebars-loader' }
]
},
plugins: [
new HtmlwebpackPlugin({
title: 'Intro to webpack',
template: 'src/index.html'
}),
new UglifyJsPlugin({
beautify: false,
mangle: { screw_ie8 : true },
compress: { screw_ie8: true, warnings: false },
comments: false
}),
new CommonsChunkPlugin({
name: "vendor",
filename: "vendor.bundle.js"
})
]
};

세 번째 라인에서 이 플러그인을 임포트하고 entry에서 이전과 달리 두 개의 엔트리를 지정하였고 vendor 엔트리에서 벤더 청크에 들어갈 것들을 표시하였습니다. (여기 예에서는 polyfill과 Lodash) 그리고 main엔트리에 메인 엔트리에 들어갈 파일을 지정하였습니다. 그런 다음 plugins 섹션에서 CommonsChunkPlugin을 추가하고 벤더 청크가 저장될 파일로 vendor.bundle.js로 지정하였습니다.

벤더 청크를 지정하면 CommonsChunkPlugin 플러그인은 이 청크에 지정된 모든 의존성 파일들을 모아서 이 벤더 청크에 저장합니다. 이렇게 청크 이름을 지정하지 않으면 엔트리간에 공유되는 의존성에 따라 별도의 파일을 생성하게 됩니다.

이제 webpack을 실행하면 bundle.js, 0.bundle.js, vendor.bundle.js가 생성된 것을 알 수 있습니다. 웹 브라우저로 접속해서 벤더 청크에 들어 있는 모듈들이 로딩되고 처리되는 것을 확인할 수 있습니다.

마무리

많은 내용을 다루다 보니 글이 길어졌지만 여기서 다룬 내용은 webpack으로 할 수 있는 것들의 아주 일부 맛을 본 수준입니다. webpack에는 CSS를 쉽게 다루기 위한 모듈이나 이미지 최적화와 같은 훨씬 많은 기능들이 있습니다.

--

--