개발일지

WEBPACK + BABEL

서재원 • • javascript

아 왜 서버 모듈을 브라우져에서 쓰려고 이 난리야.

일찍이 브라우져에서의 자바스크립트 모듈화를 구현했었던 require.js로 대표되는 AMD방식 모듈은,
디버깅이 복잡하고, 모듈을 AMD방식으로 작성해야 한다는 단점 때문에 외면받고 있고
본디 node.js 에서 사용되던 CommonJs 방식의 모듈이 브라우져에 역 수입되고 있는 상황입니다.
CommonJs모듈을 브라우져에서 사용할 수 있도록 도와주는것이 ‘모듈 번들러’이며
현재의 대표 주자로는 Browserfy와 Webpack이 있습니다.
특히 1년여전부터 유행하고 있는 React.js는 가본적으로 CommonJs 방식의 모듈만 지원하는군요.
최근 들어서 npm만 가지고 프로젝트를 수행했다는 소식이 심심찮게 들리는 걸 보면,
이제는 Browserfy든 Webpack이든 툴 하나를 잡아야 따라잡을 수 있을 것 같습니다.
어차피 모듈화는 해야 할 것 아니겠습니까?
이렇게 복잡하게 상황이 전개될줄은 상상하지도 못했습니다만,
요즘의 최신 SPA프레임웍들은 es6기반으로 튜토리얼이 작성되고 있기에 어쩔 수가 없습니다.
특히 요즘 추세는 Webpack인 듯 하네요. 속도가 나와야 실무에서 써먹지요.
아래의 모든 장황한 작업들은 오직 es6와 CommonJs모듈을 사용하기 위함 입니다.

WebPack 셋업

  1. npm저장소 초기화
    의존성 정보를 기록하고, 실행 스크립트를 작성하기 위해,
    다음 커맨드를 입력해서 워킹 디렉토리를 초기화.
    npm init -y

  2. Webpack 설치
    npm i webpack -S -D

  3. Webpack과 연결할 babel-loader 설치
    npm i babel-loader babel-core -S -D

  4. Babel용 es6 preset 설치
    npm i babel-preset-es2015 -S- D

  5. npm에 webpack실행용 숏컷 작성

...전략...
"scripts": {
	"build": "webpack",
	"start": "webpack-dev-server"
}
...후략...

6.Babel과 Webpack을 연결하는 webpack.config.js 작성

//require함수로 임포트한 모듈들은 const 제한자로 수정을 막는 것이 좋다.
const webpack = require('webpack');
const path = require('path');
//merge함수를 임포트. _.extend와 비슷한 함수이다.
const merge = require('webpack-merge');
//현재 npm이 어떤 모드로 동작 중인지 식별한다.
const TARGET = process.env.npm_lifecycle_event;
/*
* `__dirname` 은 현재 이 파일이 속한 절대 경로이다.
* 따라서 repl에서는 직접 사용할 수 없다.
*
* `path.join` 메소드는 해당 OS에 맞는 구분자로 path명을 연결한다.
* */
const PATHS = {
	app: path.join(__dirname,'app'),
	build: path.join(__dirname,'build')
};

//배포용 설정과 빌드용 설정을 분리하기 위해, 공통 설정을 추출한다.
const common = {
	/*
	 소스파일 경로 정보.
	 **아래와 같이 경로를 넘길 경우에는, 반드시 path에 index.js가 있어야 한다.**
	 */
	entry: PATHS.app,
	/*
	 출력파일 경로 정보.
	 **webpack-dev-server를 사용할 경우에는, 반드시 path에 html 파일이 있어야 한다.**
	 * index.html를 놓아두면 자동으로 로드되므로 편리하다.
	 * HMR을 사용할 경우에는 index.html을 반드시 위치시킬것.
	 */
	output: {
		path: PATHS.build,
		filename: 'bundle.js'
	},
	/*소스 맵 작성 옵션
	* 이 옵션을 키면, 자동으로 uglyfy된다.
	* */
	//devtool: 'eval-source-map',
	//resolve 플러그인 활성화
	resolve: {
		extensions: ['', '.js', '.jsx']
	},
	module: {
		loaders: [{
			//js혹은 jsx를 트랜스파일 함
			test: /\.jsx?$/,
			//`cacheDirectory` : 바벨의 트랜스파일 성능을 끌어올리기위한 캐시 디렉토리 설정
			//시스템의 기본 임시 디렉토리가 기본값
			//`es2015` : es6 트랜스파일 사용
			loaders: [
				'babel?cacheDirectory,presets[]=es2015'
			],
			include: PATHS.app
		}]
	}
}

if(TARGET === 'start' || !TARGET) {
	//개발용 설정 export
	/*
	 `devServer['contentBase']`은 다음 커맨드로 webpack-dev-server를 동작시킨 것과 동일하다.
	 `webpack-dev-server --content-base build`
	 아래의 설정을 사용하면 HMR을 사용할 수 있다.
	 즉 실행중에 소스가 변경되면, 변경된 소스만 교체할 수 있다.
	 HMR이 아직 완벽하지 않기 때문에 가끔씩 강제로 새로고침을 해야 할 경우가 생긴다.
	 HMR소스가 함께 번들링되기에 출력물이 장황해지는 단점이 있다.
	 HMR은 module.export로 익스포트 된 소스만 교체할 수 있다.
	 * */
	module.exports = merge(common, {
		devServer: {
			contentBase: PATHS.build,
			historyApiFallback: true,
			//모듈을 사용하지 않을 생각이면 아래의 옵션을 비 활성화 시켜야 한다.
			//hot: true,
			inline: true,
			progress: true,
			stats: 'errors-only',
			host: process.env.HOST,
			port: process.env.PORT
		},
		/*
		* HMR을 활성화시키려면 아래 플러그인을 기동
		* */
		plugins: [
			new webpack.HotModuleReplacementPlugin()
		]
	});
}
if(TARGET === 'build') {
	//build용 설정 export
	module.exports = merge(common, {});
}

아 힙듭니다.
es6하고 서버모듈을 쓰겠다고 이 고생이라니.
이제야 최신 SPA를 따라잡을 수 있게 되었습니다.

참고

일본어로 작성된 튜토리얼
survivejs

comments powered by Disqus