1. webpack이란?

webpack은 공식사이트의 정의에 의하면 Module Bundler라고 정의 되어 있다. webpack은 프론트엔드 개발에서 각종 스크립트(js/jsx/coffee)와 스타일시트(css/less/scss) 레이아웃(html/jade/pug)를 통합하고 정리하는 역할을 하고 있다.

왜 이런 webpack을 통한 모듈 번들링 과정이 필요하냐면, 최근 프론트엔드의 개발 패러다임이 바뀌면서 프론트엔드 개발이 모듈화로 바뀌고 개발파일이 분산되고 있다. 하지만 아직까지 브라우저 엔진의 경우 원활한 모듈화 지원이 되지 않고, 이러한 모듈의 분산은 네트워크 레이턴시에 부하를 주는 상황을 줄 수 있다. 이러한 개발이 무너지고 멘탈이 무너지고 코드가 황폐화되는 이러한 상황 속에서 모듈화와 기존의 구조의 격차를 리소스를 하나로 묶어서 처리하는 방식으로 해결하고 있다.

webpack에서 이런 모듈 번들링을 위한 목표를 다음과 같이 정하고 있다.

  1. 의존 트리를 거대한 하나의 덩어리 안에서 쪼개지도록 한다.
  2. 최초 로딩 시간을 짧게 유지한다.
  3. 모든 정적 asset이 모듈이 될 수 있게 한다.
  4. 서드파티 라이브러리를 모듈로 통합하는 능력을 지닌다.
  5. 모듈 번들러의 거의 대부분을 커스터마이징하게 한다.
  6. 큰 프로젝트와 잘 맞게 한다.

2. webpack의 설정

이미 webpack을 고려하고 있는 사람이라면 webpack을 위한 환경설정과 설치정도는 충분히 할 것이라고 믿기때문에 굳이 환경설정과 설치에 관해서는 설명하지 않는다. 이 포스트에서는 webpack빌드를 위한 설정파일의 셋팅에 대해서만 설명한다.

webpack을 통하여 모듈 번들링을 하기 위해서는 설정파일이 필요하다. 보통 webpack설정파일의 이름은 ‘webpack.config.js’로 사용한다. 하지만 내가 진행중인 프로젝트의 경우 로컬테스트를 위한 모듈 번들링과 실제 배포를 위한 모듈 번들링을 구분하여 처리하고 있기 때문에 ‘webpack.config.debug.js’, ‘webpack.config.production.js’로 분리하여 관리하고 있다. 이렇게 파일을 별도로 한 이유는 테스트와 배포할 때의 옵션이 서로 다르기 때문이다. 하지만 일부 프로젝트들을 살펴보면 gulp등을 통하여 각각의 옵션에 맞게 설정파일을 객체로 뽑아내어 사용하고 있는 경우도 있다. 물론 귀찮으니까 여기에서 설명하지 않는다. 궁금하면 직접 찾아보자.

webpack의 설정파일은 js object를 반환하게 된다. 이 객체의 내부에는 webpack의 각종 설정들이 저장되게 되는데, 가장 중요한 부분은 context, entry, output, module, plugins 이렇게 5가지이다. 여기서는 이 5가지 부분만 다루도록 하며 나머지 설정과 좀 더 자세한 설정은 webpack사이트의 configuration페이지를 참조하도록 하자.

1. context

context는 번들링 시 기본이 되는 디렉토리의 절대경로이다.

2. entry

entry는 모듈 번들링을 위한 진입점이 되는 파일 또는 모듈을 지정한다. 이 진입점은 1개 이상으로 구성이 된다. 단순히 1개 파일만을 진입점으로 사용할 시에는 해당 파일의 위치를 스트링으로 지정하면 된다. 여러개의 진입점이 있을 시는 각각의 진입점의 위치를 배열로 지정하거나, 각각의 진입점에 이름을 지정하고 패스를 지정하여 오브젝트 형태로 지정할 수 있다. 전자의 경우 번들링된 파일이 하나만 나오지만 후자의 경우 번들링된 파일이 지정된 이름의 수만큼 나오게 된다.

entry: "./main.js" //1개의 entry파일 1개의 번들파일
entry: ["./entry1.js", "./entry2.js"] //2개의 entry파일 1개의 번들파일
entry: {entry1:"./entry1.js", entry2:"./entry2.js"} //2개의 entry파일 2개의 번들파일

3. output

output은 번들링이 된 결과 파일에 관한 셋팅이다. 이 output파일은 하나일 수도 또는 entry수만큼 나올 수도 있다.

output의 주로 사용하는 설정에는 filename, path, publicPath가 있다.

3.1. output.filename

번들링 된 결과 파일의 이름이다. 단순한 스트링으로 지정이 가능하지만 여러개의 output이 반환될 때에 유니크한 이름을 셋팅하기 위해 몇몇개의 옵션이 지원된다.

[name] : 해당부분이 entry에 지정된 이름으로 변환된다.
[hash] : 해당부분이 결과물의 hash로 변환된다.
[chunkhash] : 해당부분이 각 덩어리의 hash로 변환된다.

3.2. output.path

번들링 된 결과를 저장할 절대경로를 지정한다.

3.3. output.publicPath

publicPath는 결과물이 브라우저에서 동작할 시 파일을 찾아갈 경로를 지정한다. 간단하게 설명하자면 파일의 절대경로에서 context부분이 publicPath로 변환되어 번들링된다.

4. module

모듈은 번들링시에 동작하는 각종 모듈들을 설정할 수 있다. loaders, preLoders, postLoders, noParse 등을 설정할 수 있지만 loaders가 가장 중요하고 널리 사용되기 때문에 해당부분만 설명하도록 한다.

4.1. loaders

loaders는 webpack에서 가장 중요한 부분이라고 할 수 있다. loaders에서는 각종 로더의 설정을 하게 되는데 로더는 앱에서 사용되는 각종 파일들을 변환시키는 역할을 한다. 이는 브라우저에서 구동되지 않는 파일을 브라우저에서 구동되는 파일로 변환시키기도 하고, 파일들을 번들에 포함시키거나 연결시켜주는 역할을 수행한다.

로더에 대한 자세한 내용은 밑에서 다루기로 한다.

로더의 셋팅은 test, exclude, include, [loader, loaders]로 구성된다. 각각의 역할은 다음과 같다.

1.test : 주로 정규식으로 구성된다. 해당 정규식이 파일의 이름에 맞으면 해당 로더를 실행시킨다.
2.exlude : 로더실행의 예외로 둔다.
3.inlude : 로더실행에 포함시킨다. 4.loader : 실행할 로더들을 스트링으로 지정하고 로더에 옵션을 부여한다. 여러개의 로더를 셋팅할 수 있으며 로더간의 구분은 !로 한다.
5.loaders : 실행할 로더들을 각각의 로더이름의 배열로 지정한다.

5. plugins

번들링 과정에 사용될 플러그인들을 셋팅한다. Array로 구성되며 각각의 플러그인 객체들을 셋팅한다.

3. loader

로더는 특정한 리소스 파일들을 받아들여 결과물에서 사용할 수 있도록 가공을 한다. 이 과정에서 결과물의 js사용할 수 있는 형태로 로더에서는 결과를 제공한다.

로더의 특징은 다음과 같다.

  1. 로더들은 서로 연계가 가능하다. 이러한 로더들의 연계로 리소스의 파이프라인을 만들 수 있다. 마지막 로더는 결과를 자바스크립트로 반환하게 된다. 각각의 로더는 개별의 포맷으로 된 소스들을 반환하게 되고 이는 다음의 로드로 전달된다.
  2. 로더는 동기적으로 동작하거나 비동기적으로 동작한다.
  3. 로더는 Node.js에서 동작하며 해당환경에서 모든 동작이 가능하다.
  4. 로더는 query parameter를 사용 가능하다. 이러한 query parameter로 로더의 설정을 전달할 수 있다.
  5. 로더는 설정에서 확장자 또는 정규식을 통하여 소스에 바인딩 된다.
  6. 로더는 npm을 통해 설치할 수 있다.
  7. 일반적인 모듈들은 package.json의 loader설정을 통해 main외의 로더를 반환할 수 있다.
  8. 로더들은 설정에서 접근 가능하다.
  9. 플러그인은 로더에게 더 많은 기능을 줄 수 있다.
  10. 로더는 임의적으로 추가적인 파일을 더 내보낼 수 있다.
  11. 그외 등등 (webpack 사이트에 이렇게 적혀있다.)

다음은 흔히 사용하는 loader들이다. 다른 로더들을 알고 싶다면 여기를 참고하도록 하자.

  1. babel-loader : 바벨을 실행시킨다. es6코드를 es5코드로 변환시키며, react의 jsx코드역시 js로 변환시킨다. 물론 babel은 별매이므로 babel-core등은 따로 설치해줘야 된다.
  2. style-loader : 스타일을 js코드에 포함시킨다.
  3. css-loader : css파일을 읽어서 코드로 반환하도록 한다. 주로 style-loader와 연계하여 사용한다.
  4. sass-loader : sass파일을 css로 변환한다. 주로 css-loader/style-loader와 연계하여 사용한다.
  5. file-loader : 파일을 읽어서 output폴더에 복사하고 상대경로를 반환한다.
  6. url-loader : file-loader와 비슷하게 동작하지만 제한된 크기보다 파일이 작을 시에는 DataURL로 만들어 반환하여 js에 포함되게 한다.

4. 주로 사용하는 plugin

다음은 흔히 사용하는 plugin들이다. 다른 플러그인들을 알고 싶다면 여기를 참고하도록 하자.

  1. webpack.optimize.DedupePlugin : 코드에서 중복되는 모듈들을 제거한다.
  2. webpack.optimize.UglifyJsPlugin : 코드난독화 / 압축을 시켜준다.
  3. webpack.optimize.OccurrenceOrderPlugin : 모듈과 덩어리에 발생수에 따른 아이디를 부여한다. 이 아이디를 사용하여 파일 크기를 감소시킨다.
  4. HtmlWebpackPlugin : 베이스가 되는 html에 생성된 js와 css파일의 링크를 걸어준다.

5. 샘플

다음은 react + scss 를 사용한 프로젝트에서 사용되는 예제이다.

var path = require('path');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
	context: __dirname+"/app",
	entry: "./jsx/main.jsx",
	output: {
		path: path.join(__dirname, "/deploy"),
		filename: "bundle.js",
		publicPath: "/"
	},
	module: {
		loaders: [
  			{test: /\.jsx?$/, exclude: /node_modules/, loaders: ["babel"]},
  			{test: /\.scss$/, loader: "style!css?root="+rootDir+"!resolve-url!sass"},
  			// Load images
  			{ test: /\.jpg/, loader: "url-loader?limit=10000&mimetype=image/jpg" },
  			{ test: /\.gif/, loader: "url-loader?limit=10000&mimetype=image/gif" },
  			{ test: /\.png/, loader: "url-loader?limit=10000&mimetype=image/png" },
  			{ test: /\.svg/, loader: "url-loader?limit=10000&mimetype=image/svg" },
  			// Load fonts
  			{ test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader?limit=10000&mimetype=application/font-woff" },
  			{ test: /\.(ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "file-loader" }
		]
	},
	plugins:[
		new HtmlWebpackPlugin({
			template: path.join(__dirname, "/app/index.tmpl.html")
		}),
		new webpack.optimize.DedupePlugin(),
		new webpack.optimize.UglifyJsPlugin({output:{comments:false},
			compressor:{screw_ie8: true, keep_fnames: true, warnings: false},
			mangle: {screw_ie8: true, keep_fnames: true}}),
		new webpack.optimize.OccurrenceOrderPlugin(),
		new webpack.optimize.AggressiveMergingPlugin()
	]
};