JavaScript

Webpack2 시작하기

La.place 2017. 4. 8. 18:41



Webpack2 시작하기


 오늘날 javascript를 빌드하지 않고 사용하는 경우는 드물다. 때문에 js를 빌드할때 사용하는 빌드툴이 매우 다양한데 최근에는 grunt, gulp, browserify를 거쳐 webpack을 이용해서 빌드를 많이 하는 것 같다. 이번 기회에 webpack2에 대한 소개를 해 보고자 한다. 또 새로운 프로젝트를 만들 때마다 빌드 환경을 설정해 주고 있는데, 그때마다 공식 문서를 찾아보며 빌드 세팅을 하다보니 너무 귀찮아져서 나만의 boilerplate를 만들어 보기로 했다.


 먼저 webpack에 대해 간단히 소개하자면, 웹 프로젝트에서 asset들을 bundle(하나의 단일 파일)로 만들어 배포하기 쉽게 빌드해주는 도구이다. js를 예를 들면, 여러 개로 쪼개진 스크립트 파일들을 하나로 모아서 환경에 따른 분기, minify(최소화), uglify(난독화)등을 자동으로 해서 하나의 파일로 만들어준다. 개발자는 이렇게 만들어진 하나의 파일을 배포해주면 된다.



Webpack2 Install


 말로 설명하는것보다 webpack2을 설치해보자. 간단하게 npm을 이용해서 쉽게 설치 할 수 있다. 프로젝트를 만든 후 Commend Line에서 다음 명령어를 실행하자. (다음 사이트에서 npm을 설치할 수 있다. nodejs를 설치하면 함께 설치된다. https://nodejs.org/ko/)

$ npm install webpack --save-dev

 이제 Webpack을 사용할 준비가 되었다. webpack을 사용하기 위해선 빌드 설정을 해주어야한다. webpack 명령어를 실행하면 webpack은 webpack.config.js란 파일을 읽어와서 설정에 맞게 파일들을 빌드한다. 최상위 디렉토리에 webpack.config.js 파일을 만든 후 아래와 같이 코드를 넣어보자.
const path = require('path');

module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};

 entry는 말그대로 번들링 될 파일의 진입점이 되는 스크립트를 지정 해주면 된다. output에는 만들어질 하나의 js파일명과 path를 지정해준다. 위와 같이 지정해주면 src폴더안의 index.js를 진입점으로 지정해서 빌드 한 후 프로젝트 상위에 dist/bundle.js 경로에 결과물을 저장한다.


 또한 webpack을 사용하면 ECMA2015 spec중 하나인 module을 사용할 수 있다. C나 JAVA처럼 import, export 문법을 사용하여 모듈화 하고 가져다 쓸 수 있게 되는 것이다. 모듈 문법은 다음과 같이 사용할 수 있다. 자세한 설명은 import-javscript|MDN을 참고하자.

import name from "module-name";
import * as name from "module-name";
import { member } from "module-name";
import { member as alias } from "module-name";
import { member1 , member2 } from "module-name";
import { member1 , member2 as alias2 , [...] } from "module-name";
import defaultMember, { member [ , [...] ] } from "module-name";
import defaultMember, * as alias from "module-name";
import defaultMember from "module-name";
import "module-name";



Example


 webpack build 설정하는 방법과 module 문법을 사용하는 법을 알았으니 간단한 예제를 만들어 보자. src폴더안에 index.js파일과 src/module 폴더 안에 calculation.js 파일을 만들자. 폴더 구조는 다음과 같다.



[그림 1] folder structure


src/index.js 

import * as calculation from './module/calculation'

var x = 10;
var y = 20;

console.log('sum', calculation.sum(x,y));
console.log('subtract', calculation.subtract(x,y));

 index.js에서는 calculation.js란 다른 파일에서 함수를 가져와서 실행한다. 이처럼 진입점이 되는 파일에서 다양한 모듈(파일)들을 import해서 사용할 수 있다.


src/module/calculation.js

export function sum(x, y) {
return x+y;
}

export function subtract(x, y) {
return x-y;
}

calculation.js는 sum과 subtract 함수를 가지고 있다. 



Webpack Build


이제 commend line에서 webpack 명령어를 실행해보자.

$ webpack

명령어를 실행하면 다음과 같이 build log를 볼 수 있다.



[그림 2] build log


이제 dist 경로로 이동해보면 다음과 같이 하나로 번들링 된 js파일을 볼 수 있다.

/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // identity function for calling harmony imports with the correct context
/******/ __webpack_require__.i = function(value) { return value; };
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 1);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
/* harmony export (immutable) */ __webpack_exports__["a"] = sum;
/* harmony export (immutable) */ __webpack_exports__["b"] = subtract;
function sum(x, y) {
return x+y;
}

function subtract(x, y) {
return x-y;
}

/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__module_calculation__ = __webpack_require__(0);


var x = 10;
var y = 20;

console.log('sum', __WEBPACK_IMPORTED_MODULE_0__module_calculation__["a" /* sum */](x,y));
console.log('subtract', __WEBPACK_IMPORTED_MODULE_0__module_calculation__["b" /* subtract */](x,y));

/***/ })
/******/ ]);



Loader


  webpack은 loader를 통해 다양한 형식의 확장자들(.css/.json/.png등)을 함께 빌드 할 수 있도록 도와준다. 또한 babel과 같이 트랜스파일러를 사용해서 ECMA2015 문법을 ES5 문법으로 바꿔주는 경우에도 사용할 수 있다. 여기서는 babel을 사용해서 ECMA2015 문법을 사용한 js파일을 빌드해보자. ECMA2015스펙과 바벨에 대한 자세한 내용은 babel official site를 참고하자.


babel을 사용하기 위해 npm을 이용해서 babel-core와 babel-preset-env를 설치한다. 또 webpack에서 babel을 사용하기 위해 babel-loader를 설치하자.

$ npm install babel-loader babel-core babel-preset-env --save-dev


이제 webpack.config.js에 babel-loader를 추가하자.


webpack v1에서는 loaders를 사용해서 loader들을 추가 할 수 있었다.

const path = require('path');

module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
loaders: [ // webpack v1 pattern
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel-loader',
query: {
presets: ['env']
}
}
]
}
};

 webpack2 에서부터는 rules system을 이용해서 loader를 사용할 수 있다. 기존 v1방법은 계속 사용할 수 있지만, webpack2에서는 rules를 사용하는걸 권장하고 있다. 아래는 rules를 사용해서 loader를 추가하는 방법이다.

const path = require('path');
const webpack = require('webpack');

module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
exclude: [/node_modules/],
use: [{
loader: 'babel-loader',
options: {
presets: ['env']
}
}],
}
]
}
};

 사용방법은 거의 같아 보인다. 하지만 체이닝 방법등 좀 더 세부적인 내용을 들여다보면 훨씬 더 직관적으로 변했다는 것을 알 수 있다. 여기서는 해당 내용은 다루지 않도록 한다. 자세한 내용은 다음 사이트를 참고하자. (https://webpack.js.org/guides/migrating/)


이제 ECMA2015 문법 몇가지를 사용해서 실제로 빌드해보도록 하자. ECMA2015 스펙중 하나인 class를 사용해보자. Cat.js와 index.js를 만든 후 다음과 같이 수정한다.


src/Cat.js

// Cat.js
export default class {
constructor(name){ // 생성자
this.name = name;
}

// 함수
getName(){
return `Name is ${this.name}`; // ECMA2015 templates
}

bawl(){
return "야옹~~~~~~";
}
}

src/index.js

import Cat from './Cat'

let myCat = new Cat("Momo");
console.log(myCat.getName()); // Name is Momo
console.log(myCat.bawl()); // "야옹~~~~~~"


이제 webpack명령어로 빌드 후 실행해보면 Name is Momo와 야옹~~~~~~ 이 출력되는 걸 확인할 수 있다.


해당 소스들은 아래 github에서 확인할 수 있다.


Webpack Plugin


 마지막으로 webpack의 강력한 기능 중 하나인 plugin에 대해서 설명하고 이번 포스팅을 마무리하려고 한다. plugin기능을 사용하면 환경에 따른 분기라던가, minify, uglify등을 쉽게 할 수 있다. 여러가지 plugin중 몇가지를 들여다 보자.


Define plugin


Define plugin을 사용하면 환경에 따라 complie time에 다른 값들을 설정 해 줄 수 있다. 아래와 같이 webpack.config.js를 수정해주자


webpack.config.js

const path = require('path');
const webpack = require('webpack');

module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [{ // v2 loader pattern
test: /\.jsx$/,
loader: "babel-loader",
options: {
presets: ['env']
}
}]
},
plugins: [
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true),
VERSION: JSON.stringify("1.0.0")
})
]
};

src/index.js

console.log(PRODUCTION, VERSION);


이제 webpack 명령어를 실행 후, bundle.js를 실행시켜보면 console에 찍히는 값이 치환된 걸 확인할 수 있다.

true '1.0.0'  // console.log 결과 값

실제 bundle.js를 열어 소스를 확인해보면, DefinePlugin에 의해 compile time에 값이 치환된 것을 볼 수 있다.

/* 0 */
/***/ (function(module, exports, __webpack_require__) {

console.log(true, "1.0.0");

/***/ })
/******/ ]);


Environment Plugin


 실제로 Define Plugin을 사용하다보면 JSON.stringify도 해줘야하고 여러모로 불편하다. 그래서 나온게 Environment Plugin이다. Define Plugin 보다 간편하단 것을 확인 할 수 있다.

plugins: [
new webpack.EnvironmentPlugin({
PRODUCTION: true,
VERSION: '1.0.0'
})
]


UglifyjsWebpack Plugin


 webpack에서 compile time에 uglifyjs를 사용해서 최적화를 해주는 plugin이다. minify를 통해 소스 용량을 줄여주고, uglify를 통해 난독화 및 console.log를 제거해 주는등 다양한 기능이 있다.


 해당 plugin을 사용하기 위해선 npm을 통해 설치한다.

$ npm install uglifyjs-webpack-plugin --save-dev


 install이 끝난 후에 webpack.config.js plugin에 추가해주자.


webpack.config.js

const path = require('path');
const webpack = require('webpack');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
exclude: [/node_modules/],
use: [{
loader: 'babel-loader',
options: {presets: ['env']}
}],
}
]
},

plugins :[
new UglifyJSPlugin({
beautify: false,
mangle: {
screw_ie8: true,
keep_fnames: true
},
compress: {
screw_ie8: true
},
comments: false
})
]
};

 build 설정 후, 두번째 예제였던 Momo를 빌드해보자. 아래와 같이 빌드되는 걸 확인 할 수 있다. 

!function(e){function __webpack_require__(n){if(r[n])return r[n].exports;var _=r[n]={i:n,l:!1,exports:{}};
return e[n].call(_.exports,_,_.exports,__webpack_require__),_.l=!0,_.exports}var r={};__webpack_require__.m=e,
__webpack_require__.c=r,__webpack_require__.i=function(e){return e},__webpack_require__.d=function(e,r,n){__
webpack_require__.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:n})},__webpack_require_
_.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return __webpack_requi
re__.d(r,"a",r),r},__webpack_require__.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},__web
pack_require__.p="",__webpack_require__(__webpack_require__.s=1)}([function(e,r,n){"use strict";function _clas
sCallCheck(e,r){if(!(e instanceof r))throw new TypeError("Cannot call a class as a function")}Object.definePro
perty(r,"__esModule",{value:!0});var _=function(){function defineProperties(e,r){for(var n=0;n<r.length;n++){v
ar _=r[n];_.enumerable=_.enumerable||!1,_.configurable=!0,"value"in _&&(_.writable=!0),Object.defineProperty(e
,_.key,_)}}return function(e,r,n){return r&&defineProperties(e.prototype,r),n&&defineProperties(e,n),e}}(),t=f
unction(){function _class(e){_classCallCheck(this,_class),this.name=e}return _(_class,[{key:"getName",value:fu
nction(){return"Name is "+this.name}},{key:"bawl",value:function(){return"야옹~~~~~~"}}]),_class}();r.default=t
},function(e,r,n){"use strict";var _=n(0),t=function(e){return e&&e.__esModule?e:{default:e}}(_),u=new t.defau
lt("Momo");console.log(u.getName()),console.log(u.bawl())}]);

용량도 적어지고, 코드보안레벨이 올라간 것을 확인 할 수 있다. 


 이상으로 webpack2를 이용해서 javascript를 빌드하는 방법을 알아보았다. boilerplate를 만들어서 두고두고 쓰려했는데, 막상 정리해보니 굳이 만들 필요가 없을 것 같다는 생각이 들기도 했다.(포스트 작성한후에 귀찮아짐 ㅠ ㅅㅠ) 언제가 될지 모르겠지만 다음번엔 webpack2에서 많이 쓰는 꿀 기능들을 정리해 보아야 겠다.

'JavaScript' 카테고리의 다른 글

Javascript ES6 Proxy  (1) 2017.04.16
Javascript Image Filter 만들기  (5) 2017.04.15
ECMA 2015를 공부해보자  (1) 2016.08.07
자바스크립트 배열  (0) 2015.11.01
자바스크립트 변수  (0) 2015.10.30